feat(rosto): mao com dedos, polegar e punho no lugar da luva
Quatro dedos separados (medio maior, levemente abertos), polegar para o lado do rosto, punho de robo e contorno so na borda externa. Ao acenar os dedos balancam; ao pensar o punho fecha com o indicador no rosto; para esconder e espiar as maos cobrem o olho inteiro. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+47
-12
@@ -265,16 +265,51 @@
|
||||
const suave = (x) => { x = Math.max(0, Math.min(1, x)); return x * x * (3 - 2 * x); };
|
||||
const faixaDe = (k, a, b) => suave((k - a) / (b - a)); // 0→1 entre a e b
|
||||
|
||||
// Luva simples: polegar atrás da palma e dois vincos marcando os dedos.
|
||||
function mao(c, o, x, y, s, ang, espelho) {
|
||||
const escura = o.cor.map((v) => v * 0.36);
|
||||
// Mão de desenho animado: palma, quatro dedos (o do meio maior), polegar para o lado do rosto
|
||||
// e um punho de robô. dobra (0–1) fecha os dedos; mexe (0–1) agita os dedos ao acenar.
|
||||
// O contorno é traçado antes do preenchimento: assim só a borda externa aparece.
|
||||
function capsula(c, x0, y0, x1, y1, r) {
|
||||
const a = Math.atan2(y1 - y0, x1 - x0) + Math.PI / 2;
|
||||
c.moveTo(x0 + Math.cos(a) * r, y0 + Math.sin(a) * r);
|
||||
c.arc(x0, y0, r, a, a + Math.PI);
|
||||
c.arc(x1, y1, r, a + Math.PI, a + 2 * Math.PI);
|
||||
c.closePath();
|
||||
}
|
||||
|
||||
function formaMao(c, s, dobra, mexe, t, indicador) {
|
||||
c.beginPath();
|
||||
// punho (manga do robô)
|
||||
caixa(c, -0.42 * s, 0.5 * s, 0.84 * s, 0.38 * s, 0.12 * s);
|
||||
// palma
|
||||
caixa(c, -0.5 * s, -0.36 * s, 1.0 * s, 0.95 * s, 0.34 * s);
|
||||
// dedos: x da base, comprimento e abertura
|
||||
// mindinho, anelar, médio e indicador (o indicador fica do lado do polegar)
|
||||
const dedos = [[-0.355, 0.5, -0.16], [-0.12, 0.68, -0.05], [0.12, 0.74, 0.05], [0.355, 0.66, 0.14]];
|
||||
dedos.forEach(([x, comp, abre], i) => {
|
||||
const onda = mexe ? Math.sin(t * 14 + i * 0.9) * 0.06 * mexe : 0;
|
||||
const fecha = indicador && i === 3 ? 0 : dobra; // "pensando": só o indicador esticado
|
||||
const ang = abre + onda, l = comp * (1 - 0.7 * fecha) * s;
|
||||
const bx = x * s, by = -0.26 * s;
|
||||
capsula(c, bx, by, bx + Math.sin(ang) * l, by - Math.cos(ang) * l, 0.115 * s);
|
||||
});
|
||||
// polegar, saindo para o lado
|
||||
capsula(c, 0.38 * s, 0.2 * s, (0.38 + 0.42 * (1 - 0.4 * dobra)) * s, (0.2 - 0.34) * s, 0.13 * s);
|
||||
}
|
||||
|
||||
function mao(c, o, x, y, s, ang, espelho, dobra, mexe, indicador) {
|
||||
dobra = dobra || 0; mexe = mexe || 0;
|
||||
s *= 0.82;
|
||||
const escura = o.cor.map((v) => v * 0.34);
|
||||
c.save();
|
||||
c.translate(x, y); c.rotate(ang); c.scale(espelho, 1);
|
||||
c.fillStyle = css(escura); c.strokeStyle = css(o.cor); c.lineWidth = s * 0.1; c.lineJoin = 'round'; c.lineCap = 'round';
|
||||
c.beginPath(); c.ellipse(0.55 * s, 0.12 * s, 0.24 * s, 0.4 * s, -0.55, 0, Math.PI * 2); c.fill(); c.stroke(); // polegar
|
||||
caixa(c, -0.6 * s, -0.75 * s, 1.2 * s, 1.55 * s, 0.55 * s); c.fill(); c.stroke(); // palma e dedos
|
||||
c.lineWidth = s * 0.07;
|
||||
for (const dx of [-0.2, 0.2]) { c.beginPath(); c.moveTo(dx * s, -0.72 * s); c.lineTo(dx * s, -0.34 * s); c.stroke(); } // vincos
|
||||
c.lineJoin = 'round'; c.lineCap = 'round';
|
||||
formaMao(c, s, dobra, mexe, o.t || 0, indicador);
|
||||
c.strokeStyle = css(o.cor); c.lineWidth = s * 0.17; c.stroke(); // contorno (metade some sob o preenchimento)
|
||||
c.fillStyle = css(escura); c.fill();
|
||||
// detalhes por dentro, finos: linha da palma, vincos dos dedos e a costura do punho
|
||||
c.strokeStyle = css(o.cor, 0.55); c.lineWidth = s * 0.045;
|
||||
c.beginPath(); c.moveTo(-0.3 * s, 0.12 * s); c.quadraticCurveTo(0.02 * s, 0.26 * s, 0.3 * s, 0.06 * s); c.stroke();
|
||||
c.beginPath(); c.moveTo(-0.36 * s, 0.6 * s); c.lineTo(0.36 * s, 0.6 * s); c.stroke();
|
||||
c.restore();
|
||||
}
|
||||
|
||||
@@ -291,17 +326,17 @@
|
||||
const treme = Math.sin(o.t * 24) * s * 0.025; // tímido: a mão treme um pouco
|
||||
let afasta = 0;
|
||||
if (maos.modo === 'espiar') afasta = s * 1.35 * (faixaDe(k, 0.3, 0.42) - faixaDe(k, 0.66, 0.76));
|
||||
mao(c, o, e.cx - afasta + treme, altura(e.cy + e.ry * 0.15), s, -0.12 - afasta / s * 0.3, 1);
|
||||
mao(c, o, d.cx - treme, altura(d.cy + d.ry * 0.15), s, 0.12, -1);
|
||||
mao(c, o, e.cx - afasta + treme, altura(e.cy + e.ry * 0.3), s * 1.18, -0.12 - afasta / s * 0.3, 1);
|
||||
mao(c, o, d.cx - treme, altura(d.cy + d.ry * 0.3), s * 1.18, 0.12, -1);
|
||||
} else if (maos.modo === 'acenar') {
|
||||
const x = d.cx + d.ry * 2.1, y0 = d.cy + d.ry * 0.7;
|
||||
const y = k < 0.8 ? subir(y0, 0, 0.2) : y0 + (fora - y0) * faixaDe(k, 0.82, 1);
|
||||
const balanco = k > 0.2 && k < 0.8 ? Math.sin((k - 0.2) * Math.PI * 7) * 0.38 : 0;
|
||||
mao(c, o, x, y, s * 0.9, balanco, -1);
|
||||
mao(c, o, x, y, s * 0.95, balanco, -1, 0, 1);
|
||||
} else if (maos.modo === 'pensar') {
|
||||
const x = (e.cx + d.cx) / 2 + e.ry * 1.05, y0 = e.cy + e.ry * 1.4;
|
||||
const y = (k < 0.8 ? subir(y0, 0, 0.2) : y0 + (fora - y0) * faixaDe(k, 0.82, 1)) + Math.sin(o.t * 9) * s * 0.03;
|
||||
mao(c, o, x, y, s * 0.75, -0.35, -1);
|
||||
mao(c, o, x, y, s * 0.85, -0.5, -1, 0.95, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user