From 982bf56cd0430e6c44cebc5384d0999d1a66f51d Mon Sep 17 00:00:00 2001 From: julio Date: Tue, 6 Oct 2026 03:18:17 -0300 Subject: [PATCH] feat(rosto): sobrancelha perto do olho, boca mais alta e ajustes de posicao A sobrancelha passa a ser posicionada a partir do topo real do olho de cada estilo e formato (antes usava a altura do olho classico, alto demais nos rostos novos). Nos rostos novos a boca sobe um pouco. Novos ajustes 'Subir a boca' e 'Distancia das sobrancelhas' (tela Rosto e Ajustes), validados no servidor. Co-Authored-By: Claude Opus 5.5 --- android/app/assets/carinha.html | 20 ++++++++++++++++---- android/app/assets/rosto-motor.js | 6 ++++++ carinha.html | 20 ++++++++++++++++---- controle_pc.py | 5 +++-- rosto-motor.js | 6 ++++++ web/src/lib/ajustes.svelte.ts | 2 ++ web/src/telas/Rosto.svelte | 19 +++++++++++++++++++ 7 files changed, 68 insertions(+), 10 deletions(-) diff --git a/android/app/assets/carinha.html b/android/app/assets/carinha.html index 8f2a4de..13b7dd4 100644 --- a/android/app/assets/carinha.html +++ b/android/app/assets/carinha.html @@ -92,7 +92,7 @@ addEventListener("resize", ajustar); const PRETO = [6, 8, 14]; const CIANO = [92, 214, 255]; -const EXTRAS = { boca: true, sobrancelhas: false, iris: false }; +const EXTRAS = { boca: true, sobrancelhas: false, iris: false, subirBoca: 0, distSobrancelha: 16 }; const CIANO_E = [150, 232, 255]; const VERM = [255, 78, 78]; const ROSA = [255, 112, 176]; @@ -150,8 +150,18 @@ function caminhoLinha(x0, y0, x1, y1) { return "M " + x0 + "," + y0 + " L " + x1 + "," + y1; } /* formas da boca (o humor escolhe qual) */ +function deslocamentoBoca() { + const auto = ROSTO && ROSTO.motor ? 22 : 0; // rostos novos têm olhos mais baixos + return -(auto + (EXTRAS.subirBoca || 0)) * ESC; +} +/* Topo do olho atual (para a sobrancelha ficar perto dele, em qualquer estilo/formato). */ +function metadeOlho(esc) { + const m = ROSTO && ROSTO.motor && window.MotorRosto && window.MotorRosto.metadeAltura + ? window.MotorRosto.metadeAltura(ROSTO.motor, FORMATO) : 1; + return m * RY * esc; +} function caminhoBoca(tipo) { - const cx = BOCA[0], cy = BOCA[1], l = BOCA_L, meia = l / 2; + const cx = BOCA[0], cy = BOCA[1] + deslocamentoBoca(), l = BOCA_L, meia = l / 2; if (tipo === "aberta") { // surpresa: um "O" const rx = l * 0.17, ry = l * 0.22; return caminhoElipse(cx, cy, rx, ry); @@ -429,7 +439,7 @@ function desenhar() { const c = i === 0 ? E1 : E2; const lado = i === 0 ? 1 : -1; const largura = RX * esc * 0.85; - const y = c[1] - RY * esc - 20 * ESC; + const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC; const inclinacao = r.forma === 'bravo' ? 18 * lado * ESC : r.forma === 'triste' ? -14 * lado * ESC : 0; const elevacao = r.esc > 1.1 ? -15 * ESC : 0; brow.setAttribute('d', 'M ' + (c[0]-largura) + ',' + (y-inclinacao+elevacao) + ' Q ' + c[0] + ',' + (y-10*ESC+elevacao) + ' ' + (c[0]+largura) + ',' + (y+inclinacao+elevacao)); @@ -447,7 +457,7 @@ function tracosDoRosto(esc) { } if (EXTRAS.sobrancelhas && !r.dormindo) { [E1, E2].forEach((c, i) => { - const lado = i === 0 ? 1 : -1, largura = RX * esc * 0.85, y = c[1] - RY * esc * 1.25 - 20 * ESC; + const lado = i === 0 ? 1 : -1, largura = RX * esc * 0.85, y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC; const inc = r.forma === 'bravo' ? 18 * lado * ESC : r.forma === 'triste' ? -14 * lado * ESC : 0; const ele = r.esc > 1.1 ? -15 * ESC : 0; tracos.push({ d: 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele), largura: 9 * ESC }); @@ -916,6 +926,8 @@ if (window.eilik) window.eilik.camera = CAM; // so depois do const: sem TDZ let configRecebida = null; function configurarApp(c) { EXTRAS.boca = c.boca !== false; EXTRAS.sobrancelhas = c.sobrancelhas === true; EXTRAS.iris = c.iris === true; + if (typeof c.subirBoca === 'number') EXTRAS.subirBoca = c.subirBoca; + if (typeof c.distSobrancelha === 'number') EXTRAS.distSobrancelha = c.distSobrancelha; AUTO = c.auto; AUDIO.ligado = c.audio && par.get('espelho') !== '1' && par.get('audio') !== '0'; AUDIO.volume = c.volume; COMPORTAMENTO.ouvir = c.ouvir; COMPORTAMENTO.reagir = c.reagir; diff --git a/android/app/assets/rosto-motor.js b/android/app/assets/rosto-motor.js index 5137506..b0ddb43 100644 --- a/android/app/assets/rosto-motor.js +++ b/android/app/assets/rosto-motor.js @@ -492,6 +492,12 @@ window.MotorRosto = { desenhar, retrato, esconder, + /** Metade da altura do olho (em frações de RY) para o estilo/formato: posiciona as sobrancelhas. */ + metadeAltura: (estilo, formato) => { + const base = ESTILOS[estilo]; if (!base) return 1; + if (base.lente) return base.w * 0.5 * 1.12; + return ((!base.lente && FORMATOS[formato]) || base).h / 2; + }, maos: (modo) => { if (DUR_MAOS[modo]) { maos.modo = modo; maos.t0 = performance.now() / 1000; } }, faixa: definirFaixa, estilos: Object.keys(ESTILOS), formatos: Object.keys(FORMATOS) }; })(); diff --git a/carinha.html b/carinha.html index 8f2a4de..13b7dd4 100644 --- a/carinha.html +++ b/carinha.html @@ -92,7 +92,7 @@ addEventListener("resize", ajustar); const PRETO = [6, 8, 14]; const CIANO = [92, 214, 255]; -const EXTRAS = { boca: true, sobrancelhas: false, iris: false }; +const EXTRAS = { boca: true, sobrancelhas: false, iris: false, subirBoca: 0, distSobrancelha: 16 }; const CIANO_E = [150, 232, 255]; const VERM = [255, 78, 78]; const ROSA = [255, 112, 176]; @@ -150,8 +150,18 @@ function caminhoLinha(x0, y0, x1, y1) { return "M " + x0 + "," + y0 + " L " + x1 + "," + y1; } /* formas da boca (o humor escolhe qual) */ +function deslocamentoBoca() { + const auto = ROSTO && ROSTO.motor ? 22 : 0; // rostos novos têm olhos mais baixos + return -(auto + (EXTRAS.subirBoca || 0)) * ESC; +} +/* Topo do olho atual (para a sobrancelha ficar perto dele, em qualquer estilo/formato). */ +function metadeOlho(esc) { + const m = ROSTO && ROSTO.motor && window.MotorRosto && window.MotorRosto.metadeAltura + ? window.MotorRosto.metadeAltura(ROSTO.motor, FORMATO) : 1; + return m * RY * esc; +} function caminhoBoca(tipo) { - const cx = BOCA[0], cy = BOCA[1], l = BOCA_L, meia = l / 2; + const cx = BOCA[0], cy = BOCA[1] + deslocamentoBoca(), l = BOCA_L, meia = l / 2; if (tipo === "aberta") { // surpresa: um "O" const rx = l * 0.17, ry = l * 0.22; return caminhoElipse(cx, cy, rx, ry); @@ -429,7 +439,7 @@ function desenhar() { const c = i === 0 ? E1 : E2; const lado = i === 0 ? 1 : -1; const largura = RX * esc * 0.85; - const y = c[1] - RY * esc - 20 * ESC; + const y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC; const inclinacao = r.forma === 'bravo' ? 18 * lado * ESC : r.forma === 'triste' ? -14 * lado * ESC : 0; const elevacao = r.esc > 1.1 ? -15 * ESC : 0; brow.setAttribute('d', 'M ' + (c[0]-largura) + ',' + (y-inclinacao+elevacao) + ' Q ' + c[0] + ',' + (y-10*ESC+elevacao) + ' ' + (c[0]+largura) + ',' + (y+inclinacao+elevacao)); @@ -447,7 +457,7 @@ function tracosDoRosto(esc) { } if (EXTRAS.sobrancelhas && !r.dormindo) { [E1, E2].forEach((c, i) => { - const lado = i === 0 ? 1 : -1, largura = RX * esc * 0.85, y = c[1] - RY * esc * 1.25 - 20 * ESC; + const lado = i === 0 ? 1 : -1, largura = RX * esc * 0.85, y = c[1] - metadeOlho(esc) - (EXTRAS.distSobrancelha + 4) * ESC; const inc = r.forma === 'bravo' ? 18 * lado * ESC : r.forma === 'triste' ? -14 * lado * ESC : 0; const ele = r.esc > 1.1 ? -15 * ESC : 0; tracos.push({ d: 'M ' + (c[0] - largura) + ',' + (y - inc + ele) + ' Q ' + c[0] + ',' + (y - 10 * ESC + ele) + ' ' + (c[0] + largura) + ',' + (y + inc + ele), largura: 9 * ESC }); @@ -916,6 +926,8 @@ if (window.eilik) window.eilik.camera = CAM; // so depois do const: sem TDZ let configRecebida = null; function configurarApp(c) { EXTRAS.boca = c.boca !== false; EXTRAS.sobrancelhas = c.sobrancelhas === true; EXTRAS.iris = c.iris === true; + if (typeof c.subirBoca === 'number') EXTRAS.subirBoca = c.subirBoca; + if (typeof c.distSobrancelha === 'number') EXTRAS.distSobrancelha = c.distSobrancelha; AUTO = c.auto; AUDIO.ligado = c.audio && par.get('espelho') !== '1' && par.get('audio') !== '0'; AUDIO.volume = c.volume; COMPORTAMENTO.ouvir = c.ouvir; COMPORTAMENTO.reagir = c.reagir; diff --git a/controle_pc.py b/controle_pc.py index fc877c2..82e687b 100644 --- a/controle_pc.py +++ b/controle_pc.py @@ -20,7 +20,7 @@ PADRAO = dict(preview=False, auto=True, audio=True, volume=0.35, somaltolim=0.55, sompausa=20, somcooldown=5, movlim=0.05, movforte=0.12, movcooldown=6, movolhar=40, economia=True, brilho=0.7, brilhoRepouso=0.08, repousoTela=30, - boca=True, sobrancelhas=False, iris=False, videoRTC=True, + boca=True, sobrancelhas=False, iris=False, subirBoca=0, distSobrancelha=16, videoRTC=True, sensorMic=True, sensorCamera=True, micSono=True, cameraSono=False, cameraLado='user', videoResolucao='auto', videoFPS=0, rastrearRosto=True, rastrearMaos=True, seguirRastreamento=True, mostrarRastreamento=True) @@ -30,7 +30,8 @@ ESCOLHAS = dict(cameraLado=('user', 'environment'), LIMITES = dict(volume=(0, 1), somlim=(0, 1), somganho=(0, 1), somaltolim=(0, 1), sompausa=(1, 3600), somcooldown=(0, 120), movlim=(0, 1), movforte=(0, 1), movcooldown=(0, 120), movolhar=(0, 100), - brilho=(0.01, 1), brilhoRepouso=(0.01, 1), repousoTela=(5, 3600)) + brilho=(0.01, 1), brilhoRepouso=(0.01, 1), repousoTela=(5, 3600), + subirBoca=(-20, 60), distSobrancelha=(0, 60)) def validar(dados): diff --git a/rosto-motor.js b/rosto-motor.js index 5137506..b0ddb43 100644 --- a/rosto-motor.js +++ b/rosto-motor.js @@ -492,6 +492,12 @@ window.MotorRosto = { desenhar, retrato, esconder, + /** Metade da altura do olho (em frações de RY) para o estilo/formato: posiciona as sobrancelhas. */ + metadeAltura: (estilo, formato) => { + const base = ESTILOS[estilo]; if (!base) return 1; + if (base.lente) return base.w * 0.5 * 1.12; + return ((!base.lente && FORMATOS[formato]) || base).h / 2; + }, maos: (modo) => { if (DUR_MAOS[modo]) { maos.modo = modo; maos.t0 = performance.now() / 1000; } }, faixa: definirFaixa, estilos: Object.keys(ESTILOS), formatos: Object.keys(FORMATOS) }; })(); diff --git a/web/src/lib/ajustes.svelte.ts b/web/src/lib/ajustes.svelte.ts index 00c58bc..b695729 100644 --- a/web/src/lib/ajustes.svelte.ts +++ b/web/src/lib/ajustes.svelte.ts @@ -47,6 +47,8 @@ export const GRUPOS: { titulo: string; descricao: string; campos: Campo[] }[] = { chave: 'boca', rotulo: 'Mostrar boca', tipo: 'toggle' }, { chave: 'sobrancelhas', rotulo: 'Mostrar sobrancelhas', tipo: 'toggle' }, { chave: 'iris', rotulo: 'Mostrar íris', ajuda: 'Pupila que acompanha o olhar (Viva, Bloco e Matriz)', tipo: 'toggle' }, + { chave: 'subirBoca', rotulo: 'Subir a boca', tipo: 'slider', min: -20, max: 60, passo: 2 }, + { chave: 'distSobrancelha', rotulo: 'Distância das sobrancelhas', tipo: 'slider', min: 0, max: 60, passo: 2 }, { chave: 'audio', rotulo: 'Sons no celular', ajuda: 'Bipes do próprio robô', tipo: 'toggle' }, { chave: 'volume', rotulo: 'Volume dos sons', tipo: 'slider', min: 0, max: 1, passo: 0.05 }, { chave: 'brilho', rotulo: 'Brilho normal', tipo: 'slider', min: 0.01, max: 1, passo: 0.01 }, diff --git a/web/src/telas/Rosto.svelte b/web/src/telas/Rosto.svelte index 03a1bdb..8e2714e 100644 --- a/web/src/telas/Rosto.svelte +++ b/web/src/telas/Rosto.svelte @@ -4,6 +4,7 @@ cor, CORES, FORMATOS_INFO, formato, gesto, GESTOS_INFO, hex, nomeRosto, rgb, rosto, ROSTOS_INFO, } from '../lib/comandos.svelte'; import RostoPrevia from '../lib/componentes/RostoPrevia.svelte'; + import Slider from '../lib/componentes/Slider.svelte'; import Toggle from '../lib/componentes/Toggle.svelte'; import { salvarApp } from '../lib/ajustes.svelte'; import { estado } from '../lib/estado.svelte'; @@ -144,6 +145,24 @@ onchange={(v) => salvarApp({ iris: v })} /> +
+ salvarApp({ subirBoca: v })} + /> + salvarApp({ distSobrancelha: v })} + /> +