diff --git a/android/app/assets/rosto-motor.js b/android/app/assets/rosto-motor.js index 204bca7..c9ff88b 100644 --- a/android/app/assets/rosto-motor.js +++ b/android/app/assets/rosto-motor.js @@ -296,46 +296,73 @@ 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) { + function mao(c, o, x, y, s, ang, espelho, dobra, mexe, indicador, pulso, costas) { 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); + if (pulso) c.translate(0, -0.7 * s); // (x, y) é o punho: gira como uma mão de verdade 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(); + if (costas) { // dorso da mão: nós dos dedos em vez da linha da palma + for (const nx of [-0.355, -0.12, 0.12, 0.355]) { c.beginPath(); c.arc(nx * s, -0.2 * s, 0.075 * s, Math.PI * 1.15, Math.PI * 1.85); c.stroke(); } + } else { + 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(); } + // Sobe com um leve "passa e volta" no fim (como um braço que freia). + const voltinha = (x) => { x = Math.max(0, Math.min(1, x)); const k = 1.6; return 1 + (k + 1) * Math.pow(x - 1, 3) + k * Math.pow(x - 1, 2); }; + function desenharMaos(c, o, olhos, H) { if (!maos.modo) return; if (maos.t0 === null) maos.t0 = o.t; const k = (o.t - maos.t0) / DUR_MAOS[maos.modo]; if (k >= 1) { maos.modo = null; return; } const [e, d] = olhos.slice().sort((a, b) => a.cx - b.cx); // olho da esquerda e da direita na tela - const s = e.ry * 0.95, fora = H + s * 1.6; - const subir = (y, a, b) => fora + (y - fora) * faixaDe(k, a, b); + const s = e.ry * 0.95, fora = H + s * 1.8; if (maos.modo === 'esconder' || maos.modo === 'espiar') { - const altura = (y) => (k < 0.8 ? subir(y, 0, 0.2) : y + (fora - y) * faixaDe(k, 0.82, 1)); - 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.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); + // Cada mão vem do canto de baixo, por fora, num arco, girando até ficar inclinada para dentro. + // A da direita chega um pouco depois: duas mãos nunca se mexem igualzinho. + const cobrir = (olho, lado, atraso) => { + const ida = voltinha((k - atraso) / 0.24), volta = faixaDe(k, 0.8 + atraso * 0.5, 1); + const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta); // 0 = fora da tela, 1 = cobrindo + const alvoX = olho.cx - lado * olho.ry * 0.05, alvoY = olho.cy + olho.ry * 0.38; + const x = alvoX - lado * olho.ry * 1.6 * (1 - Math.min(1, v)); + const y = fora + (alvoY - fora) * v; + const respira = Math.sin(o.t * 3 + lado) * s * 0.03 * Math.min(1, v); + const ang = lado * (0.42 + 0.55 * (1 - Math.min(1, v))); // gira enquanto sobe, termina a ~24° + return { x, y: y + respira, ang }; + }; + const pe = cobrir(e, 1, 0), pd = cobrir(d, -1, 0.05); + if (maos.modo === 'espiar') { + // a mão da esquerda gira pelo punho para fora, como uma porta, e o olho aparece + const abre = faixaDe(k, 0.3, 0.44) - faixaDe(k, 0.64, 0.76); + pe.ang -= abre * 1.05; pe.x -= abre * e.ry * 0.35; pe.y += abre * e.ry * 0.2; + } + const treme = Math.sin(o.t * 22) * s * 0.012; // tímido: tremidinha + // dorso virado para quem olha: polegar para fora, perto da bochecha + mao(c, o, pe.x + treme, pe.y, s * 1.1, pe.ang, -1, 0, 0, false, true, true); + mao(c, o, pd.x - treme, pd.y, s * 1.1, pd.ang, 1, 0, 0, false, true, true); } 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.95, balanco, -1, 0, 1); + const ida = voltinha(k / 0.22), volta = faixaDe(k, 0.82, 1); + const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta); + const x = d.cx + d.ry * 2.2 + d.ry * 0.6 * (1 - Math.min(1, v)), y0 = d.cy + d.ry * 1.55; + const y = fora + (y0 - fora) * v; + const balanco = k > 0.22 && k < 0.82 ? Math.sin((k - 0.22) * Math.PI * 7) * 0.32 : 0; + mao(c, o, x, y, s * 0.95, -0.18 + balanco, -1, 0, 1, false, true); } else if (maos.modo === 'pensar') { + const ida = voltinha(k / 0.22), volta = faixaDe(k, 0.82, 1); + const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta); 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 = fora + (y0 - fora) * v + Math.sin(o.t * 9) * s * 0.03; mao(c, o, x, y, s * 0.85, -0.5, -1, 0.95, 0, true); } } diff --git a/rosto-motor.js b/rosto-motor.js index 204bca7..c9ff88b 100644 --- a/rosto-motor.js +++ b/rosto-motor.js @@ -296,46 +296,73 @@ 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) { + function mao(c, o, x, y, s, ang, espelho, dobra, mexe, indicador, pulso, costas) { 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); + if (pulso) c.translate(0, -0.7 * s); // (x, y) é o punho: gira como uma mão de verdade 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(); + if (costas) { // dorso da mão: nós dos dedos em vez da linha da palma + for (const nx of [-0.355, -0.12, 0.12, 0.355]) { c.beginPath(); c.arc(nx * s, -0.2 * s, 0.075 * s, Math.PI * 1.15, Math.PI * 1.85); c.stroke(); } + } else { + 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(); } + // Sobe com um leve "passa e volta" no fim (como um braço que freia). + const voltinha = (x) => { x = Math.max(0, Math.min(1, x)); const k = 1.6; return 1 + (k + 1) * Math.pow(x - 1, 3) + k * Math.pow(x - 1, 2); }; + function desenharMaos(c, o, olhos, H) { if (!maos.modo) return; if (maos.t0 === null) maos.t0 = o.t; const k = (o.t - maos.t0) / DUR_MAOS[maos.modo]; if (k >= 1) { maos.modo = null; return; } const [e, d] = olhos.slice().sort((a, b) => a.cx - b.cx); // olho da esquerda e da direita na tela - const s = e.ry * 0.95, fora = H + s * 1.6; - const subir = (y, a, b) => fora + (y - fora) * faixaDe(k, a, b); + const s = e.ry * 0.95, fora = H + s * 1.8; if (maos.modo === 'esconder' || maos.modo === 'espiar') { - const altura = (y) => (k < 0.8 ? subir(y, 0, 0.2) : y + (fora - y) * faixaDe(k, 0.82, 1)); - 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.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); + // Cada mão vem do canto de baixo, por fora, num arco, girando até ficar inclinada para dentro. + // A da direita chega um pouco depois: duas mãos nunca se mexem igualzinho. + const cobrir = (olho, lado, atraso) => { + const ida = voltinha((k - atraso) / 0.24), volta = faixaDe(k, 0.8 + atraso * 0.5, 1); + const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta); // 0 = fora da tela, 1 = cobrindo + const alvoX = olho.cx - lado * olho.ry * 0.05, alvoY = olho.cy + olho.ry * 0.38; + const x = alvoX - lado * olho.ry * 1.6 * (1 - Math.min(1, v)); + const y = fora + (alvoY - fora) * v; + const respira = Math.sin(o.t * 3 + lado) * s * 0.03 * Math.min(1, v); + const ang = lado * (0.42 + 0.55 * (1 - Math.min(1, v))); // gira enquanto sobe, termina a ~24° + return { x, y: y + respira, ang }; + }; + const pe = cobrir(e, 1, 0), pd = cobrir(d, -1, 0.05); + if (maos.modo === 'espiar') { + // a mão da esquerda gira pelo punho para fora, como uma porta, e o olho aparece + const abre = faixaDe(k, 0.3, 0.44) - faixaDe(k, 0.64, 0.76); + pe.ang -= abre * 1.05; pe.x -= abre * e.ry * 0.35; pe.y += abre * e.ry * 0.2; + } + const treme = Math.sin(o.t * 22) * s * 0.012; // tímido: tremidinha + // dorso virado para quem olha: polegar para fora, perto da bochecha + mao(c, o, pe.x + treme, pe.y, s * 1.1, pe.ang, -1, 0, 0, false, true, true); + mao(c, o, pd.x - treme, pd.y, s * 1.1, pd.ang, 1, 0, 0, false, true, true); } 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.95, balanco, -1, 0, 1); + const ida = voltinha(k / 0.22), volta = faixaDe(k, 0.82, 1); + const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta); + const x = d.cx + d.ry * 2.2 + d.ry * 0.6 * (1 - Math.min(1, v)), y0 = d.cy + d.ry * 1.55; + const y = fora + (y0 - fora) * v; + const balanco = k > 0.22 && k < 0.82 ? Math.sin((k - 0.22) * Math.PI * 7) * 0.32 : 0; + mao(c, o, x, y, s * 0.95, -0.18 + balanco, -1, 0, 1, false, true); } else if (maos.modo === 'pensar') { + const ida = voltinha(k / 0.22), volta = faixaDe(k, 0.82, 1); + const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta); 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 = fora + (y0 - fora) * v + Math.sin(o.t * 9) * s * 0.03; mao(c, o, x, y, s * 0.85, -0.5, -1, 0.95, 0, true); } }