fix(rosto): parado mais humano; tonto com espirais e bocejo com a mão
Parado ele vivia escondendo o rosto: a IA local (Qwen 0.6B) reagia a cada som ou "sozinho" a cada 20-60 s e repetia 'esconder' apesar do "varie". - IA local: cada evento só oferece gestos que combinam (barulho: susto ou olhar; movimento: olhar e acenar), os dois últimos ficam fora do enum, no máximo a cada 45 s, não interrompe atividade e, com o modo vida ligado, não reage a "sozinho". - Sem ninguém olhando (sem movimento/PIR há 3 min) nada de gesto "para alguém" (acenar, tímido, esconder...): ele vive a vida dele e começa atividades com mais frequência. - Gestos soltos no celular: quase sempre sutis (olhar de lado, piscar devagar), gesto grande raro e nunca repetido em seguida. - Olhar em repouso com fixações e sacadas em vez do vaivém contínuo. - Tonto: espirais girando no lugar dos olhos (todos os rostos, inclusive Matriz) e boca de "ó". Bocejo: mão na frente da boca com tapinhas e Zzz subindo. As mãos agora aparecem também quando o gesto vem sozinho. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -279,6 +279,13 @@ function olho(indice, cx, cy, rx, ry, ab, forma, cor, lado, lx, ly) {
|
||||
if(p.desenho!==desenho){if(p.figurinha)p.figurinha.remove();p.figurinha=window.carinhaConstruirSVG(desenho);p.desenho=desenho;if(p.figurinha)document.getElementById('o'+(indice+1)).append(p.figurinha);}
|
||||
if(p.figurinha){p.figurinha.style.display='';p.figurinha.setAttribute('x',cx-rx*.65);p.figurinha.setAttribute('y',cy-ry*.65);p.figurinha.setAttribute('width',rx*1.3);p.figurinha.setAttribute('height',ry*1.3);}
|
||||
}
|
||||
if (espiralAtiva()) {
|
||||
p.traco.setAttribute("d", caminhoEspiral(cx, cy, Math.min(rx, ry) * 0.95, lado));
|
||||
p.traco.setAttribute("stroke-width", rx * 0.17);
|
||||
p.traco.setAttribute("stroke", rgb(cor));
|
||||
p.traco.style.display = "";
|
||||
return;
|
||||
}
|
||||
if (icone) {
|
||||
forma='aberto';p.icone.textContent=window.carinhaOlhosIcone||'';
|
||||
p.icone.setAttribute('x',cx);p.icone.setAttribute('y',cy);
|
||||
@@ -372,12 +379,12 @@ function sequencias() {
|
||||
const pula = (n, alto, baixo, d) => Array.from({ length: n }, (_, i) => [{ dy: i % 2 ? baixo : alto }, d]);
|
||||
const rir = [[{ forma: "feliz", boca: "aberta", esc: 1.08, dy: -4 }, 0.12], ...pula(8, -6, 2, 0.1),
|
||||
[{ forma: "feliz", boca: "sorriso", dy: 0 }, 0.25], [{ forma: "aberto", boca: "neutra", esc: 1.0 }, 0.3]];
|
||||
const bocejar = [[{ ab: 0.5, boca: "aberta", dy: -4, esc: 1.05 }, 0.5], [{ ab: 0.3, dy: -5 }, 1.0],
|
||||
const bocejar = [[{ ab: 0.5, boca: "aberta", dy: -4, esc: 1.05 }, 0.5], [{ ab: 0.25, dy: -5 }, 1.0],
|
||||
[{ ab: 0.0, boca: "neutra", dy: 2, esc: 1.0 }, 0.25], [{ ab: 1.0, dy: 0 }, 0.35]];
|
||||
const confuso = [[{ ab2: 0.55, dx: -8, esc: 0.98 }, 0.25], [{ dx: 8 }, 0.45], [{ dx: -6 }, 0.4], [{}, 0.4],
|
||||
[{ ab2: 1.0, dx: 0, esc: 1.0 }, 0.3]];
|
||||
const roda = Array.from({ length: 16 }, (_, i) => [{ dx: 14 * Math.cos(i * Math.PI / 4), dy: 9 * Math.sin(i * Math.PI / 4) }, 0.1]);
|
||||
const tonto = [[{ esc: 0.94, boca: "aberta" }, 0.15], ...roda, [{ dx: 0, dy: 0, esc: 1.0, boca: "neutra" }, 0.35]];
|
||||
const balanca = Array.from({ length: 8 }, (_, i) => [{ dx: i % 2 ? -5 : 5, dy: i % 2 ? 2 : -2 }, 0.2]);
|
||||
const tonto = [[{ esc: 1.05, boca: "aberta" }, 0.15], ...balanca, [{ dx: 0, dy: 0, esc: 1.0, boca: "neutra" }, 0.35]];
|
||||
const timido = [[{ forma: "feliz", boca: "sorriso", dx: -14, dy: 9, esc: 0.9, cor: ROSA }, 0.35], [{}, 1.2],
|
||||
[{ dx: 0, dy: 0, esc: 1.0, forma: "aberto", cor: CIANO }, 0.4]];
|
||||
const comemorar = [[{ forma: "feliz", boca: "sorriso", cor: AMARELO, dy: -10, esc: 1.15 }, 0.15], [{ dy: 2, cor: ROSA }, 0.15],
|
||||
@@ -458,9 +465,24 @@ function atualizarFeicao(s) {
|
||||
for (const c of ["curva", "lado", "abre", "dx"]) a[c] += (b[c] - a[c]) * k;
|
||||
for (let i = 0; i < 2; i++) { a.sob[i] += (b.sob[i] - a.sob[i]) * k; a.inc[i] += (b.inc[i] - a.inc[i]) * k; }
|
||||
}
|
||||
/* Gestos soltos mais humanos: quase sempre coisas sutis (olhar de lado, piscar devagar, olhar para
|
||||
baixo); de vez em quando um gesto maior do humor, nunca o mesmo duas vezes seguidas. Gestos
|
||||
"para alguém" (acenar, tímido, piscadinha...) só quando tem gente por perto. */
|
||||
const SUTIS = ["olhar_esq", "olhar_dir", "olhar_baixo", "piscar2", "olhar_esq", "olhar_dir"];
|
||||
const PARA_ALGUEM = ["acenar", "coracao", "piscadinha", "timido", "esconder", "espiar", "surpreso"];
|
||||
const RECENTES = [];
|
||||
function alguemOlhando() {
|
||||
const agora = performance.now() / 1000;
|
||||
return (MOV.ativo && agora - MOV.ultimoMov < 30) || (TRACK.alvo && agora - TRACK.visto < 30);
|
||||
}
|
||||
function gestoSolto() {
|
||||
const lista = LIVRES_HUMOR[HUMOR] || LIVRES;
|
||||
return lista[Math.floor(Math.random() * lista.length)];
|
||||
const grande = Math.random() < ({ feliz: 0.3, entediado: 0.25, bravo: 0.3 }[HUMOR] || 0.18);
|
||||
let lista = grande ? (LIVRES_HUMOR[HUMOR] || LIVRES) : SUTIS;
|
||||
if (!alguemOlhando()) lista = lista.filter((g) => !PARA_ALGUEM.includes(g));
|
||||
const opcoes = lista.filter((g) => !RECENTES.includes(g));
|
||||
const g = (opcoes.length ? opcoes : SUTIS)[Math.floor(Math.random() * (opcoes.length || SUTIS.length))];
|
||||
RECENTES.push(g); if (RECENTES.length > 2) RECENTES.shift();
|
||||
return g;
|
||||
}
|
||||
|
||||
function proximo() {
|
||||
@@ -476,7 +498,22 @@ function proximo() {
|
||||
r.kf_t = 0;
|
||||
}
|
||||
|
||||
const ESPIRAL = { ate: 0 }; // tonto: espiral no lugar dos olhos até este instante
|
||||
const espiralAtiva = () => performance.now() / 1000 < ESPIRAL.ate;
|
||||
function caminhoEspiral(cx, cy, R, lado) {
|
||||
const voltas = 2.75, giro = performance.now() / 1000 * 7 * lado;
|
||||
let d = "M " + cx + "," + cy;
|
||||
for (let th = 0.22; th <= voltas * Math.PI * 2; th += 0.22) {
|
||||
const rr = R * th / (voltas * Math.PI * 2), a = th * lado + giro;
|
||||
d += " L " + (cx + Math.cos(a) * rr).toFixed(1) + "," + (cy + Math.sin(a) * rr).toFixed(1);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function animar(nome) {
|
||||
if (nome === "tonto") ESPIRAL.ate = performance.now() / 1000 + 2.1;
|
||||
// mãos (esconder, espiar, acenar, pensar, bocejar) valem também quando o gesto vem sozinho
|
||||
if (SEQS[nome] && window.MotorRosto && window.MotorRosto.maos) window.MotorRosto.maos(nome);
|
||||
if (SEQS[nome]) {
|
||||
r.fila = SEQS[nome].map(([a, d]) => [Object.assign({}, a), d]);
|
||||
proximo();
|
||||
@@ -502,6 +539,7 @@ function passo(dt) { // avanca a fila; true se ha gesto em
|
||||
|
||||
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
||||
let ATIV = null;
|
||||
const OLHAR = { x: 0, y: 0, ax: 0, ay: 0, prox: 0 }; // ponto que os olhos estão fitando no repouso
|
||||
function atividade(nome, segundos) {
|
||||
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
||||
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
||||
@@ -523,9 +561,18 @@ function repouso(t) { // pose calma: olha devagar, "respira
|
||||
r.boca = "neutra";
|
||||
r.cor = CIANO.slice();
|
||||
r.ab = 1.0; r.ab2 = null; r.esc = 1.0;
|
||||
// o vaivem de sempre + a direcao de quem se mexeu na frente (se houver)
|
||||
r.dx = 8.0 * Math.sin(t * 0.55) + 3.0 * Math.sin(t * 1.7) + MOV.olharX;
|
||||
r.dy = 3.5 * Math.sin(t * 0.9) + 1.5 * Math.sin(t * 2.3) + MOV.olharY;
|
||||
// olhar de gente: fixa num ponto por 1 a 4 s e salta rápido para outro (quase sempre perto do
|
||||
// centro, às vezes para o lado), mais a direção de quem se mexeu na frente (se houver)
|
||||
if (t >= OLHAR.prox || t < OLHAR.prox - 10) {
|
||||
const longe = Math.random() < 0.25;
|
||||
OLHAR.ax = (Math.random() * 2 - 1) * (longe ? 18 : 7);
|
||||
OLHAR.ay = (Math.random() * 2 - 1) * (longe ? 8 : 3);
|
||||
OLHAR.prox = t + 1.2 + Math.random() * 3;
|
||||
}
|
||||
OLHAR.x += (OLHAR.ax - OLHAR.x) * 0.3;
|
||||
OLHAR.y += (OLHAR.ay - OLHAR.y) * 0.3;
|
||||
r.dx = OLHAR.x + 1.0 * Math.sin(t * 0.9) + MOV.olharX;
|
||||
r.dy = OLHAR.y + 0.8 * Math.sin(t * 1.3) + MOV.olharY;
|
||||
if (ATIV) { // fazendo algo: olha para a atividade
|
||||
const v = ATIV.v;
|
||||
r.dx = r.dx * (1 - v) * 0.3 + ATIV.x * v;
|
||||
@@ -555,7 +602,7 @@ function desenhar() {
|
||||
OLHOS.forEach((g) => { g.style.display = comMotor ? "none" : ""; });
|
||||
if (comMotor) {
|
||||
window.MotorRosto.desenhar({
|
||||
estilo: ROSTO.motor, formato: FORMATO, t: tAgora, cor: r.cor, dormindo: r.dormindo,
|
||||
estilo: ROSTO.motor, formato: FORMATO, t: tAgora, cor: r.cor, dormindo: r.dormindo, espiral: espiralAtiva(),
|
||||
icone: !r.dormindo && !window.carinhaOlhosDesenho ? window.carinhaOlhosIcone || null : null,
|
||||
carga: window.carinhaBateria || null,
|
||||
iris: EXTRAS.iris,
|
||||
@@ -660,7 +707,6 @@ function trata(ordem) {
|
||||
} else if (SEQS[ordem]) {
|
||||
if (window.MotorRosto) {
|
||||
if (ordem === "musica") window.MotorRosto.faixa("equalizador", { segundos: 2.3 });
|
||||
else window.MotorRosto.maos(ordem);
|
||||
}
|
||||
animar(ordem);
|
||||
} else if (ordem === "calma") {
|
||||
|
||||
@@ -107,7 +107,20 @@
|
||||
}
|
||||
|
||||
// Olho plano (vivo, bloco, matriz): corpo + pálpebras recortadas (transparentes).
|
||||
// Tonto: uma espiral girando no lugar de cada olho (cada um gira para um lado, como no emoji 😵💫).
|
||||
function espiral(c, o, cx, cy, R, lado) {
|
||||
const voltas = 2.75, giro = (o.t || 0) * 7 * lado;
|
||||
c.save(); c.strokeStyle = css(o.cor); c.lineWidth = Math.max(2, R * 0.17); c.lineCap = 'round';
|
||||
c.beginPath();
|
||||
for (let th = 0; th <= voltas * Math.PI * 2; th += 0.22) {
|
||||
const rr = R * th / (voltas * Math.PI * 2), a = th * lado + giro;
|
||||
if (th) c.lineTo(cx + Math.cos(a) * rr, cy + Math.sin(a) * rr); else c.moveTo(cx, cy);
|
||||
}
|
||||
c.stroke(); c.restore();
|
||||
}
|
||||
|
||||
function olhoPlano(c, o, p, est, e) {
|
||||
if (o.espiral) { espiral(c, o, p.cx, p.cy, Math.min(est.w, est.h) * p.ry * 0.5, p.lado || 1); return; }
|
||||
const respira = 1 + 0.012 * Math.sin(o.t * 1.4);
|
||||
const temConteudo = !!(o.icone || o.desenho);
|
||||
if (temConteudo) e = { top: 0, slant: 0, bot: 0, coracao: 0 };
|
||||
@@ -162,6 +175,7 @@
|
||||
|
||||
// Olho lente: esclera, íris, pupila que dilata e pálpebras de metal.
|
||||
function olhoLente(c, o, p, est, e) {
|
||||
if (o.espiral) { espiral(c, o, p.cx, p.cy, est.w * p.ry * 0.5, p.lado || 1); return; }
|
||||
const R = est.w * p.ry * 0.5 * Math.max(1, p.ab) * (1 + 0.01 * Math.sin(o.t * 1.4));
|
||||
const fecha = Math.min(1, Math.max(o.icone || o.desenho ? 0 : e.top, 1 - Math.min(1, p.ab)));
|
||||
c.save();
|
||||
@@ -289,7 +303,7 @@
|
||||
// ------------------------------------------------------------------ mãos
|
||||
// Mãozinhas na cor dos olhos que sobem da borda da tela. Duração casada com as sequências
|
||||
// de rosto de mesmo nome no carinha.html.
|
||||
const DUR_MAOS = { esconder: 2.6, espiar: 3.2, acenar: 2.4, pensar: 2.8 };
|
||||
const DUR_MAOS = { esconder: 2.6, espiar: 3.2, acenar: 2.4, pensar: 2.8, bocejar: 2.1 };
|
||||
const maos = { modo: null, t0: null };
|
||||
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
|
||||
@@ -387,6 +401,16 @@
|
||||
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 === 'bocejar') {
|
||||
// mão na frente da boca, com tapinhas, e os Zzz subindo do olho
|
||||
const ida = voltinha(k / 0.25), volta = faixaDe(k, 0.78, 1);
|
||||
const v = Math.max(0, Math.min(1.08, ida)) * (1 - volta);
|
||||
const bx = (e.cx + d.cx) / 2 + e.ry * 0.12, by = e.cy + e.ry * 1.2;
|
||||
const tapa = k > 0.3 && k < 0.75 ? Math.abs(Math.sin((k - 0.3) * Math.PI * 6)) * s * 0.12 : 0;
|
||||
mao(c, o, bx + e.ry * 0.5 * (1 - Math.min(1, v)), fora + (by + s * 0.55 - fora) * v + tapa, s * 0.9, -0.22, -1, 0.15, 0, false, true, true);
|
||||
c.save(); c.globalAlpha *= Math.min(1, v);
|
||||
desenharZzz(c, Object.assign({}, o, { dormindo: true }), olhos, 0.8);
|
||||
c.restore();
|
||||
} 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);
|
||||
|
||||
Reference in New Issue
Block a user