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:
@@ -265,16 +265,51 @@
|
|||||||
const suave = (x) => { x = Math.max(0, Math.min(1, x)); return x * x * (3 - 2 * x); };
|
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
|
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.
|
// Mão de desenho animado: palma, quatro dedos (o do meio maior), polegar para o lado do rosto
|
||||||
function mao(c, o, x, y, s, ang, espelho) {
|
// e um punho de robô. dobra (0–1) fecha os dedos; mexe (0–1) agita os dedos ao acenar.
|
||||||
const escura = o.cor.map((v) => v * 0.36);
|
// 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.save();
|
||||||
c.translate(x, y); c.rotate(ang); c.scale(espelho, 1);
|
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.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
|
formaMao(c, s, dobra, mexe, o.t || 0, indicador);
|
||||||
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.strokeStyle = css(o.cor); c.lineWidth = s * 0.17; c.stroke(); // contorno (metade some sob o preenchimento)
|
||||||
c.lineWidth = s * 0.07;
|
c.fillStyle = css(escura); c.fill();
|
||||||
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
|
// 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();
|
c.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,17 +326,17 @@
|
|||||||
const treme = Math.sin(o.t * 24) * s * 0.025; // tímido: a mão treme um pouco
|
const treme = Math.sin(o.t * 24) * s * 0.025; // tímido: a mão treme um pouco
|
||||||
let afasta = 0;
|
let afasta = 0;
|
||||||
if (maos.modo === 'espiar') afasta = s * 1.35 * (faixaDe(k, 0.3, 0.42) - faixaDe(k, 0.66, 0.76));
|
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, 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.15), s, 0.12, -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') {
|
} else if (maos.modo === 'acenar') {
|
||||||
const x = d.cx + d.ry * 2.1, y0 = d.cy + d.ry * 0.7;
|
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 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;
|
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') {
|
} else if (maos.modo === 'pensar') {
|
||||||
const x = (e.cx + d.cx) / 2 + e.ry * 1.05, y0 = e.cy + e.ry * 1.4;
|
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;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+47
-12
@@ -265,16 +265,51 @@
|
|||||||
const suave = (x) => { x = Math.max(0, Math.min(1, x)); return x * x * (3 - 2 * x); };
|
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
|
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.
|
// Mão de desenho animado: palma, quatro dedos (o do meio maior), polegar para o lado do rosto
|
||||||
function mao(c, o, x, y, s, ang, espelho) {
|
// e um punho de robô. dobra (0–1) fecha os dedos; mexe (0–1) agita os dedos ao acenar.
|
||||||
const escura = o.cor.map((v) => v * 0.36);
|
// 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.save();
|
||||||
c.translate(x, y); c.rotate(ang); c.scale(espelho, 1);
|
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.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
|
formaMao(c, s, dobra, mexe, o.t || 0, indicador);
|
||||||
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.strokeStyle = css(o.cor); c.lineWidth = s * 0.17; c.stroke(); // contorno (metade some sob o preenchimento)
|
||||||
c.lineWidth = s * 0.07;
|
c.fillStyle = css(escura); c.fill();
|
||||||
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
|
// 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();
|
c.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,17 +326,17 @@
|
|||||||
const treme = Math.sin(o.t * 24) * s * 0.025; // tímido: a mão treme um pouco
|
const treme = Math.sin(o.t * 24) * s * 0.025; // tímido: a mão treme um pouco
|
||||||
let afasta = 0;
|
let afasta = 0;
|
||||||
if (maos.modo === 'espiar') afasta = s * 1.35 * (faixaDe(k, 0.3, 0.42) - faixaDe(k, 0.66, 0.76));
|
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, 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.15), s, 0.12, -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') {
|
} else if (maos.modo === 'acenar') {
|
||||||
const x = d.cx + d.ry * 2.1, y0 = d.cy + d.ry * 0.7;
|
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 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;
|
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') {
|
} else if (maos.modo === 'pensar') {
|
||||||
const x = (e.cx + d.cx) / 2 + e.ry * 1.05, y0 = e.cy + e.ry * 1.4;
|
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;
|
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