Enhanced Omoggle face renderer with sharp jaw control
Tính đến
// ==UserScript==
// @name Kuro’s Helper
// @namespace http://tampermonkey.net/
// @version 5.0
// @description Enhanced Omoggle face renderer with sharp jaw control
// @license 9w9
// @author ChatGPT + Grok
// @match *://omoggle.com/*
// @match *://*.omoggle.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
if (window.__kuroHelperLoaded) return;
window.__kuroHelperLoaded = true;
let active = true;
let jitterAmount = 0.15;
let jawSharpness = 8; // 0 to 10
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 640;
canvas.height = 480;
const faceConfig = {
eyeTilt: 20,
eyeWidth: 26,
eyeHeight: 8,
eyeSpacing: 52,
faceWidth: 85,
faceHeight: 620,
jawWidth: 74,
jawDepth: 214,
mouthY: 44,
philtrumY: 16,
headX: 0,
headY: 0,
rotateZ: 0,
rotateX: 0,
rotateY: 0,
};
function updateJaw() {
const sharpnessFactor = jawSharpness / 10;
faceConfig.jawWidth = 72 + sharpnessFactor * 8; // wider at top when sharp
faceConfig.jawDepth = 195 + sharpnessFactor * 55; // deeper chin when sharp
}
// Initial update
updateJaw();
// ... (rotatePoint3D, project, renderFace functions remain the same) ...
function rotatePoint3D(x, y, z, rx, ry, rz) {
let x1 = x * Math.cos(rz) - y * Math.sin(rz);
let y1 = x * Math.sin(rz) + y * Math.cos(rz);
let z1 = z;
let y2 = y1 * Math.cos(rx) - z1 * Math.sin(rx);
let z2 = y1 * Math.sin(rx) + z1 * Math.cos(rx);
let x2 = x1;
let x3 = x2 * Math.cos(ry) + z2 * Math.sin(ry);
let z3 = -x2 * Math.sin(ry) + z2 * Math.cos(ry);
return { x: x3, y: y2, z: z3 };
}
function project(x, y, z, focalLen = 600) {
const scale = focalLen / (focalLen + z);
return { x: x * scale, y: y * scale, scale };
}
function renderFace(targetCtx, W, H, config, jitter) {
targetCtx.clearRect(0, 0, W, H);
targetCtx.fillStyle = "#000";
targetCtx.fillRect(0, 0, W, H);
const t = Date.now() * 0.015;
const jx = Math.sin(t) * jitter;
const jy = Math.cos(t * 1.1) * (jitter * 0.67);
const drawScale = Math.min(W / 640, H / 480);
const baseCX = 320 + config.headX * 80 + jx;
const baseCY = 212 + config.headY * 80 + jy;
const rx = config.rotateX * Math.PI / 180;
const ry = config.rotateY * Math.PI / 180;
const rz = config.rotateZ * Math.PI / 180;
function tp(dx, dy, dz = 0) {
const r = rotatePoint3D(dx, dy, dz, rx, ry, rz);
const p = project(r.x, r.y, r.z);
return { x: baseCX + p.x, y: baseCY + p.y, s: p.scale };
}
targetCtx.save();
targetCtx.translate(W / 2, H / 2);
targetCtx.scale(drawScale, drawScale);
targetCtx.translate(-320, -240);
// Face
targetCtx.beginPath();
const steps = 48;
for (let i = 0; i <= steps; i++) {
const a = (i / steps) * Math.PI * 2;
const p = tp(Math.cos(a) * config.faceWidth, Math.sin(a) * config.faceHeight + 12);
i === 0 ? targetCtx.moveTo(p.x, p.y) : targetCtx.lineTo(p.x, p.y);
}
targetCtx.closePath();
const g = targetCtx.createRadialGradient(baseCX, baseCY, 20, baseCX, baseCY, 170);
g.addColorStop(0, "#c4a484");
g.addColorStop(0.75, "#2a1b10");
g.addColorStop(1, "#000");
targetCtx.fillStyle = g;
targetCtx.fill();
// Eyes
[-1, 1].forEach(side => {
const ep = tp(config.eyeSpacing * side, -16);
targetCtx.save();
targetCtx.translate(ep.x, ep.y);
targetCtx.rotate((side * config.eyeTilt + ry * 15 * side) * Math.PI / 180);
targetCtx.scale(ep.s, ep.s);
targetCtx.fillStyle = "black";
targetCtx.fillRect(-config.eyeWidth / 2, -config.eyeHeight / 2, config.eyeWidth, config.eyeHeight);
targetCtx.restore();
});
// Philtrum
const pp = tp(0, config.philtrumY);
targetCtx.fillStyle = "black";
targetCtx.beginPath();
targetCtx.arc(pp.x, pp.y, 8 * pp.s, 0, Math.PI * 2);
targetCtx.fill();
// Mouth
const mL = tp(-28, config.mouthY);
const mR = tp(28, config.mouthY);
const mC = tp(0, config.mouthY - 3);
targetCtx.strokeStyle = "black";
targetCtx.lineWidth = 6;
targetCtx.beginPath();
targetCtx.moveTo(mL.x, mL.y);
targetCtx.quadraticCurveTo(mC.x, mC.y, mR.x, mR.y);
targetCtx.stroke();
// Jaw (now dynamic)
const jL = tp(-config.jawWidth, config.mouthY);
const jR = tp(config.jawWidth, config.mouthY);
const bottomWidth = 42 - (jawSharpness * 2.4); // sharper = narrower bottom
const jCL = tp(-bottomWidth, config.jawDepth);
const jCR = tp(bottomWidth, config.jawDepth);
targetCtx.lineWidth = 14;
targetCtx.strokeStyle = "black";
targetCtx.lineJoin = "round";
targetCtx.beginPath();
targetCtx.moveTo(jL.x, jL.y);
targetCtx.lineTo(jCL.x, jCL.y);
targetCtx.lineTo(jCR.x, jCR.y);
targetCtx.lineTo(jR.x, jR.y);
targetCtx.stroke();
targetCtx.restore();
}
function drawPerfectFace() {
renderFace(ctx, 640, 480, faceConfig, jitterAmount);
}
// WebGL Hook
const hook = (proto) => {
const original = proto.texImage2D;
proto.texImage2D = function (...args) {
if (active && args[args.length - 1] instanceof HTMLVideoElement) {
drawPerfectFace();
return original.apply(this, [args[0], args[1], args[2], args[3], args[4], canvas]);
}
return original.apply(this, args);
};
};
if (window.WebGLRenderingContext) hook(window.WebGLRenderingContext.prototype);
if (window.WebGL2RenderingContext) hook(window.WebGL2RenderingContext.prototype);
// ==================== UI ====================
function injectUI() {
if (document.getElementById('kuro-ui')) return;
const ui = document.createElement('div');
ui.id = 'kuro-ui';
ui.innerHTML = `
<div id="kuro-window">
<div id="kuro-header">
<span>Kuro’s Helper</span>
<button id="kuro-close">✕</button>
</div>
<div id="kuro-content">
<div class="kuro-section">
<button id="kuro-toggle">Deactivate</button>
</div>
<div class="kuro-section">
<label>Jitter <span id="val-jitter">0.15</span></label>
<input type="range" id="kuro-jitter" min="0" max="2" step="0.01" value="0.15">
</div>
<div class="kuro-section">
<label>Eye Tilt <span id="val-tilt">20</span></label>
<input type="range" id="kuro-eyeTilt" min="0" max="40" step="1" value="20">
</div>
<div class="kuro-section">
<label>Jaw Sharpness <span id="val-sharp">8</span>/10</label>
<input type="range" id="kuro-sharpness" min="0" max="10" step="0.1" value="8">
</div>
<canvas id="kuro-preview"></canvas>
</div>
</div>
`;
const style = document.createElement('style');
style.textContent = `
#kuro-window {
position: fixed;
top: 20px;
left: 20px;
width: 280px;
background: rgba(15,15,20,0.97);
border: 1px solid rgba(255,215,0,0.2);
border-radius: 16px;
overflow: hidden;
z-index: 999999999;
color: white;
font-family: Arial, sans-serif;
box-shadow: 0 10px 40px rgba(0,0,0,0.6);
backdrop-filter: blur(12px);
}
#kuro-header {
background: #1a1a1a;
padding: 12px 16px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
cursor: move;
border-bottom: 1px solid rgba(255,215,0,0.2);
}
#kuro-close {
background: none;
border: none;
color: #ff6666;
font-size: 18px;
cursor: pointer;
}
.kuro-section {
padding: 12px 16px;
}
.kuro-section label {
display: block;
margin-bottom: 6px;
font-size: 13.5px;
}
.kuro-section input[type="range"] {
width: 100%;
}
#kuro-toggle {
width: 100%;
padding: 12px;
border: none;
border-radius: 10px;
background: #ffd700;
color: black;
font-weight: bold;
cursor: pointer;
}
#kuro-preview {
width: 100%;
height: 160px;
background: #000;
border-radius: 12px;
margin-top: 8px;
}
`;
document.head.appendChild(style);
document.body.appendChild(ui);
// Controls
const preview = document.getElementById('kuro-preview');
const pctx = preview.getContext('2d');
preview.width = 320; preview.height = 180;
function previewLoop() {
drawPerfectFace();
pctx.drawImage(canvas, 0, 0, preview.width, preview.height);
requestAnimationFrame(previewLoop);
}
previewLoop();
// Toggle
document.getElementById('kuro-toggle').onclick = () => {
active = !active;
document.getElementById('kuro-toggle').textContent = active ? 'Deactivate' : 'Activate';
};
// Sliders
document.getElementById('kuro-jitter').oninput = (e) => {
jitterAmount = parseFloat(e.target.value);
document.getElementById('val-jitter').textContent = jitterAmount.toFixed(2);
};
document.getElementById('kuro-eyeTilt').oninput = (e) => {
faceConfig.eyeTilt = parseFloat(e.target.value);
document.getElementById('val-tilt').textContent = faceConfig.eyeTilt;
};
document.getElementById('kuro-sharpness').oninput = (e) => {
jawSharpness = parseFloat(e.target.value);
document.getElementById('val-sharp').textContent = jawSharpness.toFixed(1);
updateJaw();
};
// Close button
document.getElementById('kuro-close').onclick = () => {
document.getElementById('kuro-content').style.display =
document.getElementById('kuro-content').style.display === 'none' ? 'block' : 'none';
};
// Draggable
const windowEl = document.getElementById('kuro-window');
const header = document.getElementById('kuro-header');
let dragging = false, ox = 0, oy = 0;
header.addEventListener('mousedown', e => {
dragging = true;
ox = e.clientX - windowEl.offsetLeft;
oy = e.clientY - windowEl.offsetTop;
});
document.addEventListener('mousemove', e => {
if (!dragging) return;
windowEl.style.left = `${e.clientX - ox}px`;
windowEl.style.top = `${e.clientY - oy}px`;
});
document.addEventListener('mouseup', () => dragging = false);
}
setInterval(() => {
if (document.body) injectUI();
}, 1000);
})();