primocode 10.5.0 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/primocode.js +117 -24
- package/lib/act.js +141 -9
- package/lib/agents-md.js +140 -0
- package/lib/auditoria.js +122 -0
- package/lib/browser.js +48 -1
- package/lib/catalogo.js +29 -6
- package/lib/comandos-extra.js +2 -0
- package/lib/config.js +32 -4
- package/lib/entrada.js +33 -11
- package/lib/estudio/cenarios.js +91 -0
- package/lib/estudio/colecao.js +35 -1
- package/lib/estudio/diretor.js +1 -0
- package/lib/estudio/falantes.js +30 -6
- package/lib/estudio/hibrido.js +144 -8
- package/lib/estudio/index.js +196 -63
- package/lib/estudio/kit/mundo.jsx +1060 -0
- package/lib/estudio/kit/voxel.jsx +315 -70
- package/lib/estudio/kit.js +1 -1
- package/lib/estudio/kokoro.js +18 -4
- package/lib/estudio/margem.js +1 -1
- package/lib/estudio/movimentos.js +212 -0
- package/lib/estudio/mundo.js +328 -0
- package/lib/estudio/personagem.js +164 -11
- package/lib/estudio/plano.js +6 -0
- package/lib/estudio/producao.js +107 -17
- package/lib/estudio/projeto.js +77 -22
- package/lib/leitura.js +176 -0
- package/lib/local/cerebro.json +180 -3
- package/lib/local/entrada.js +61 -4
- package/lib/mcp.js +296 -12
- package/lib/memoria-conversa.js +32 -3
- package/lib/mencoes.js +5 -1
- package/lib/nome-curto.js +126 -0
- package/lib/nome-projeto.js +5 -2
- package/lib/outra-pasta.js +161 -0
- package/lib/perigo.js +34 -2
- package/lib/referencia.js +40 -11
- package/lib/repeticao.js +20 -1
- package/lib/tela.js +1 -1
- package/lib/tools.js +76 -6
- package/lib/travado.js +141 -0
- package/lib/ui.js +1 -0
- package/package.json +2 -2
|
@@ -30,6 +30,7 @@ import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPa
|
|
|
30
30
|
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
|
|
31
31
|
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
|
|
32
32
|
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
33
|
+
import { Mundo, Animal, Coisa, alturaDoBicho, useIcones } from './mundo.jsx';
|
|
33
34
|
|
|
34
35
|
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
35
36
|
const lerp = (a, b, k) => a + (b - a) * k;
|
|
@@ -261,11 +262,121 @@ function RostoPixel({ p, est }) {
|
|
|
261
262
|
);
|
|
262
263
|
}
|
|
263
264
|
|
|
264
|
-
|
|
265
|
+
|
|
266
|
+
/* ── A CABEÇA FACETADA (o padrão desde a 2ª referência, 11/10) ────────────
|
|
267
|
+
"olha como quero o 3D": o rosto da referência é LOW-POLY — uma esfera de
|
|
268
|
+
poucas faces com sombreado chapado, nariz de pirâmide, sobrancelha grossa,
|
|
269
|
+
o cabelo cacheado feito de dezenas de pedrinhas facetadas. O corpo segue
|
|
270
|
+
de blocos. `cabeca: 'cubo'` volta ao rosto de pixel da 10.5. */
|
|
271
|
+
const GEO_FACETA = new Map();
|
|
272
|
+
function faceta(det) {
|
|
273
|
+
if (!GEO_FACETA.has(det)) GEO_FACETA.set(det, new THREE.IcosahedronGeometry(1, det));
|
|
274
|
+
return GEO_FACETA.get(det);
|
|
275
|
+
}
|
|
276
|
+
const Faceta = ({ p = [0, 0, 0], s = [1, 1, 1], rot, c, det = 1, rough = 0.75, sombra = true }) => (
|
|
277
|
+
<mesh position={p} scale={s} rotation={rot} geometry={faceta(det)} castShadow={sombra} receiveShadow>
|
|
278
|
+
<meshStandardMaterial color={c} roughness={rough} flatShading />
|
|
279
|
+
</mesh>
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
function CabeloFacetado({ estilo, cor }) {
|
|
283
|
+
const pedras = useMemo(() => {
|
|
284
|
+
if (estilo === 'careca') return [];
|
|
285
|
+
const out = [];
|
|
286
|
+
const n = estilo === 'cacheado' ? 46 : estilo === 'longo' ? 34 : estilo === 'coque' ? 22 : 24;
|
|
287
|
+
for (let i = 0; i < n; i++) {
|
|
288
|
+
// Espalhadas na calota de cima e de trás da cabeça (nunca na testa baixa nem no rosto).
|
|
289
|
+
const u = hash(i * 3.7 + 1), v = hash(i * 9.1 + 4);
|
|
290
|
+
const az = u * Math.PI * 2;
|
|
291
|
+
const el = estilo === 'longo' ? -0.9 + v * 2.2 : 0.15 + v * 1.25;
|
|
292
|
+
const atras = Math.cos(az) < 0.35 || el > 0.75;
|
|
293
|
+
if (!atras) continue;
|
|
294
|
+
const r = estilo === 'cacheado' ? 0.105 + hash(i) * 0.05 : 0.09 + hash(i) * 0.03;
|
|
295
|
+
out.push({ p: [Math.sin(az) * Math.cos(el) * 0.33, 0.04 + Math.sin(el) * 0.38, -Math.cos(az) * Math.cos(el) * 0.34 + (Math.cos(az) > 0 ? 0.0 : -0.02)], r, k: i });
|
|
296
|
+
}
|
|
297
|
+
if (estilo === 'coque') out.push({ p: [0, 0.36, -0.3], r: 0.16, k: 99 });
|
|
298
|
+
return out;
|
|
299
|
+
}, [estilo]);
|
|
300
|
+
// A franja: a frente do cabelo, um pouco acima da sobrancelha.
|
|
301
|
+
return (
|
|
302
|
+
<group>
|
|
303
|
+
{estilo !== 'careca' && <Faceta p={[0, 0.2, -0.02]} s={[0.36, 0.26, 0.36]} c={cor} det={1} rough={0.9} />}
|
|
304
|
+
{pedras.map((x) => <Faceta key={x.k} p={x.p} s={[x.r, x.r * 0.9, x.r]} rot={[hash(x.k) * 3, hash(x.k + 1) * 3, 0]} c={escurecer(cor, 0.85 + hash(x.k + 2) * 0.3)} det={0} rough={0.95} />)}
|
|
305
|
+
</group>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function CabecaFacetada({ p, est }) {
|
|
310
|
+
const e = EMO[est.emocao] || EMO.neutro;
|
|
311
|
+
const sobr = e.sobr + est.enfase * 0.4;
|
|
312
|
+
const [abre, larg] = est.boca;
|
|
313
|
+
const pisca = clamp(est.pisca);
|
|
314
|
+
const escuro = '#17110f';
|
|
315
|
+
const fz = 0.335; // a frente do rosto
|
|
316
|
+
return (
|
|
317
|
+
<group>
|
|
318
|
+
{/* o crânio e o queixo: dois volumes facetados */}
|
|
319
|
+
<Faceta s={[0.34, 0.39, 0.35]} c={p.pele} det={1} rough={0.7} />
|
|
320
|
+
<Faceta p={[0, -0.17, 0.05]} s={[0.26, 0.2, 0.27]} c={p.pele} det={1} rough={0.7} />
|
|
321
|
+
{[-1, 1].map((k) => <Faceta key={'o' + k} p={[k * 0.335, -0.01, -0.02]} s={[0.05, 0.08, 0.06]} c={escurecer(p.pele, 0.9)} det={0} />)}
|
|
322
|
+
{/* os olhos: amêndoa escura que fecha na piscada, e a sobrancelha grossa */}
|
|
323
|
+
{[-1, 1].map((k) => (
|
|
324
|
+
<group key={k} position={[k * 0.125, 0.04, fz - 0.03]} rotation={[0, k * 0.28, 0]}>
|
|
325
|
+
<mesh scale={[0.045, 0.05 * (1 - pisca * 0.88), 0.02]}><sphereGeometry args={[1, 8, 6]} /><meshStandardMaterial color={escuro} roughness={0.4} /></mesh>
|
|
326
|
+
<mesh position={[0.012, 0.015, 0.016]} scale={[0.012, 0.012, 0.006]}><sphereGeometry args={[1, 6, 4]} /><meshStandardMaterial color="#ffffff" roughness={0.3} /></mesh>
|
|
327
|
+
<mesh position={[0, 0.085 + sobr * 0.025, 0.012]} rotation={[0, 0, k * (est.emocao === 'serio' ? 0.25 : 0.05) - k * sobr * 0.06]}>
|
|
328
|
+
<boxGeometry args={[0.14, 0.035, 0.04]} /><meshStandardMaterial color={p.corCabelo} roughness={0.9} flatShading />
|
|
329
|
+
</mesh>
|
|
330
|
+
</group>
|
|
331
|
+
))}
|
|
332
|
+
{/* o nariz: uma pirâmide de quatro faces */}
|
|
333
|
+
<mesh position={[0, -0.045, fz + 0.025]} rotation={[Math.PI / 2 - 0.25, Math.PI / 4, 0]} scale={[0.05, 0.11, 0.05]} castShadow>
|
|
334
|
+
<coneGeometry args={[1, 1, 4]} /><meshStandardMaterial color={escurecer(p.pele, 0.95)} roughness={0.7} flatShading />
|
|
335
|
+
</mesh>
|
|
336
|
+
{/* a boca: abre com a vogal e estica no "i" */}
|
|
337
|
+
<mesh position={[0, -0.17 - abre * 0.012, fz - 0.03]} scale={[0.05 + 0.04 * larg, 0.008 + 0.04 * abre, 0.02]}>
|
|
338
|
+
<sphereGeometry args={[1, 8, 6]} /><meshStandardMaterial color="#3a1416" roughness={0.6} />
|
|
339
|
+
</mesh>
|
|
340
|
+
{p.bigode && [-1, 1].map((k) => (
|
|
341
|
+
<mesh key={'b' + k} position={[k * 0.06, -0.115, fz + 0.005]} rotation={[0, 0, k * -0.35]} scale={[0.075, 0.03, 0.03]}>
|
|
342
|
+
<icosahedronGeometry args={[1, 0]} /><meshStandardMaterial color={p.corCabelo} roughness={0.9} flatShading />
|
|
343
|
+
</mesh>
|
|
344
|
+
))}
|
|
345
|
+
{p.barba && <Faceta p={[0, -0.2, 0.08]} s={[0.27, 0.17, 0.26]} c={p.corCabelo} det={1} rough={0.95} />}
|
|
346
|
+
{p.oculos && (
|
|
347
|
+
<group position={[0, 0.04, fz + 0.02]}>
|
|
348
|
+
{[-1, 1].map((k) => <mesh key={k} position={[k * 0.125, 0, 0]}><torusGeometry args={[0.065, 0.012, 6, 12]} /><meshStandardMaterial color="#111114" roughness={0.4} /></mesh>)}
|
|
349
|
+
<mesh><boxGeometry args={[0.06, 0.012, 0.012]} /><meshStandardMaterial color="#111114" /></mesh>
|
|
350
|
+
</group>
|
|
351
|
+
)}
|
|
352
|
+
<CabeloFacetado estilo={p.cabelo} cor={p.corCabelo} />
|
|
353
|
+
</group>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* A AÇÃO da fala mexe o braço por cima do gesto: acenar (a mão direita no
|
|
358
|
+
alto, abanando), apontar (o braço esticado para a frente), comemorar (os
|
|
359
|
+
dois para cima), pensar (a mão no queixo), dançar (os dois balançando). */
|
|
360
|
+
function poseDoBraco(acao, lado, t, k) {
|
|
361
|
+
if (!acao || k <= 0) return null;
|
|
362
|
+
const dir = lado > 0;
|
|
363
|
+
if (acao === 'acenar' && dir) return { x: -0.25, z: 2.55 + Math.sin(t * 9) * 0.25, c: -0.55 + Math.sin(t * 9) * 0.35 };
|
|
364
|
+
if (acao === 'apontar' && dir) return { x: -1.45, z: 0.12, c: -0.05 };
|
|
365
|
+
if (acao === 'comemorar') return { x: -0.2, z: lado * (2.7 + Math.sin(t * 8 + lado) * 0.15), c: -0.25 };
|
|
366
|
+
if (acao === 'pensar' && dir) return { x: -1.5, z: -0.35, c: -2.1 };
|
|
367
|
+
if (acao === 'dancar') return { x: -0.6 + Math.sin(t * 6 + lado) * 0.5, z: lado * (0.6 + Math.sin(t * 6) * 0.3), c: -1.1 };
|
|
368
|
+
if (acao === 'correr') return { x: Math.sin(t * 13 + (lado > 0 ? 0 : Math.PI)) * 0.9, z: lado * 0.1, c: -1.3 };
|
|
369
|
+
if (acao === 'andar') return { x: Math.sin(t * 7 + (lado > 0 ? 0 : Math.PI)) * 0.45, z: lado * 0.06, c: -0.25 };
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function BracoBloco({ lado, g, cor, pele, t, acao, k = 1 }) {
|
|
265
374
|
// Repouso: o braço caído, um tico para fora. Gesto: o antebraço sobe e a mão mexe.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
375
|
+
let ombroX = lerp(0.08, -0.85, g) + Math.sin(t * 6 + lado) * 0.06 * g;
|
|
376
|
+
let ombroZ = lado * lerp(0.06, 0.22, g);
|
|
377
|
+
let cotovelo = lerp(-0.12, -1.05, g) + Math.sin(t * 7.3 + lado * 2) * 0.14 * g;
|
|
378
|
+
const pose = poseDoBraco(acao, lado, t, k);
|
|
379
|
+
if (pose) { ombroX = lerp(ombroX, pose.x, k); ombroZ = lerp(ombroZ, pose.z, k); cotovelo = lerp(cotovelo, pose.c, k); }
|
|
269
380
|
return (
|
|
270
381
|
<group position={[lado * 0.47, 1.4, 0]} rotation={[ombroX, 0, ombroZ]}>
|
|
271
382
|
<Bloco s={[0.2, 0.36, 0.2]} p={[0, -0.17, 0]} c={cor} r={0.04} rough={0.8} />
|
|
@@ -281,54 +392,92 @@ function PessoaBloco({ p, est }) {
|
|
|
281
392
|
const resp = Math.sin(est.t * 1.7) * 0.008;
|
|
282
393
|
const [cy, cp, cr] = est.cabeca;
|
|
283
394
|
const calca = escurecer(p.roupa, 0.35);
|
|
395
|
+
const a = est.acao, ka = est.forcaAcao || 0;
|
|
396
|
+
// O corpo inteiro: pular e comemorar sobem, dançar balança, andar e correr marcham.
|
|
397
|
+
const pulo = (a === 'pular' || a === 'comemorar') ? Math.abs(Math.sin(est.t * 6.5)) * 0.32 * ka : 0;
|
|
398
|
+
const balanco = a === 'dancar' ? Math.sin(est.t * 6) * 0.14 * ka : 0;
|
|
399
|
+
const marcha = (a === 'andar' || a === 'correr') ? (a === 'correr' ? 13 : 7) : 0;
|
|
284
400
|
return (
|
|
285
|
-
<group>
|
|
401
|
+
<group position={[0, pulo, 0]} rotation={[0, 0, balanco]}>
|
|
286
402
|
{/* as pernas e o sapato */}
|
|
287
|
-
{[-1, 1].map((k) =>
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
|
|
403
|
+
{[-1, 1].map((k) => {
|
|
404
|
+
const passo = marcha ? Math.sin(est.t * marcha + (k > 0 ? 0 : Math.PI)) * (a === 'correr' ? 0.7 : 0.4) * ka : 0;
|
|
405
|
+
return (
|
|
406
|
+
<group key={k} position={[k * 0.15, 0.76, 0]} rotation={[passo, 0, 0]}>
|
|
407
|
+
<Bloco s={[0.25, 0.72, 0.27]} p={[0, -0.36, 0]} c={calca} r={0.03} rough={0.85} />
|
|
408
|
+
<Bloco s={[0.27, 0.1, 0.36]} p={[0, -0.71, 0.04]} c="#1a1a1f" r={0.03} />
|
|
409
|
+
</group>
|
|
410
|
+
);
|
|
411
|
+
})}
|
|
293
412
|
<group position={[0, resp, 0]}>
|
|
294
413
|
{/* o tronco, com a faixa branca da camisa da referência */}
|
|
295
414
|
<Bloco s={[0.76, 0.66, 0.4]} p={[0, 1.1, 0]} c={p.roupa} r={0.05} rough={0.8} />
|
|
296
415
|
<Bloco s={[0.06, 0.5, 0.02]} p={[0.17, 1.12, 0.205]} c="#dcd8d0" r={0.008} sombra={false} />
|
|
297
416
|
<Bloco s={[0.22, 0.08, 0.22]} p={[0, 1.46, 0]} c={escurecer(p.pele, 0.88)} r={0.02} />
|
|
298
417
|
</group>
|
|
299
|
-
<BracoBloco lado={-1} g={est.gesto[0]} cor={p.roupa} pele={p.pele} t={est.t} />
|
|
300
|
-
<BracoBloco lado={1} g={est.gesto[1]} cor={p.roupa} pele={p.pele} t={est.t} />
|
|
418
|
+
<BracoBloco lado={-1} g={est.gesto[0]} cor={p.roupa} pele={p.pele} t={est.t} acao={a} k={ka} />
|
|
419
|
+
<BracoBloco lado={1} g={est.gesto[1]} cor={p.roupa} pele={p.pele} t={est.t} acao={a} k={ka} />
|
|
301
420
|
<group position={[0, 1.8 + resp, 0]} rotation={[cp, cy, cr]}>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
421
|
+
{p.cabeca === 'cubo' ? (
|
|
422
|
+
<>
|
|
423
|
+
<Bloco s={[CAB, CAB, CAB]} c={p.pele} r={0.04} rough={0.6} />
|
|
424
|
+
<RostoPixel p={p} est={est} />
|
|
425
|
+
<Cabelo estilo={p.cabelo} cor={p.corCabelo} />
|
|
426
|
+
</>
|
|
427
|
+
) : <CabecaFacetada p={p} est={est} />}
|
|
305
428
|
</group>
|
|
306
429
|
</group>
|
|
307
430
|
);
|
|
308
431
|
}
|
|
309
432
|
|
|
310
433
|
/* ── O MASCOTE (quem é IA) ──────────────────────────────────────────────
|
|
311
|
-
O
|
|
312
|
-
|
|
313
|
-
|
|
434
|
+
O da 2ª referência (11/10): um CUBO laranja de quina viva, dois olhos
|
|
435
|
+
quadrados pretos, uma orelha de bloco de cada lado e uma fileira de
|
|
436
|
+
perninhas, em pé no chão. Não tem boca: ele fala com o CORPO — estica para
|
|
437
|
+
cima na vogal aberta, encolhe no "m" — e pisca fechando o olho em traço.
|
|
438
|
+
`bigode: true` põe o bigode da referência. */
|
|
314
439
|
function Mascote({ p, est }) {
|
|
315
|
-
const cor = p.cor || '#
|
|
440
|
+
const cor = p.cor || '#d9532b';
|
|
316
441
|
const [abre] = est.boca;
|
|
317
|
-
const
|
|
318
|
-
const
|
|
442
|
+
const pulo = (est.acao === 'pular' || est.acao === 'comemorar') ? Math.abs(Math.sin(est.t * 6)) * 0.25 * (est.forcaAcao || 0) : 0;
|
|
443
|
+
const respira = Math.sin(est.t * 1.9 + 0.7) * 0.012;
|
|
444
|
+
const estica = 1 + abre * 0.07 + respira, achata = 1 - abre * 0.035;
|
|
319
445
|
const pisca = clamp(est.pisca);
|
|
320
446
|
const [cy, cp, cr] = est.cabeca;
|
|
321
|
-
const
|
|
447
|
+
const W = 1.0, H = 0.92, D = 0.78, perna = 0.46;
|
|
448
|
+
const pernas = [-0.38, -0.19, 0, 0.19, 0.38];
|
|
322
449
|
return (
|
|
323
|
-
<group position={[0,
|
|
324
|
-
|
|
325
|
-
<
|
|
326
|
-
|
|
327
|
-
<Bloco
|
|
328
|
-
|
|
329
|
-
|
|
450
|
+
<group position={[0, pulo, 0]} rotation={[0, cy * 0.8, cr * 0.5]}>
|
|
451
|
+
{pernas.map((x, i) => (
|
|
452
|
+
<group key={i}>
|
|
453
|
+
<Bloco s={[0.13, perna, 0.13]} p={[x, perna / 2 + Math.max(0, Math.sin(est.t * 9 + i)) * (est.falando ? 0.015 : 0), D / 2 - 0.12]} c={escurecer(cor, 0.9)} r={0.015} rough={0.6} />
|
|
454
|
+
{i % 2 === 0 && <Bloco s={[0.13, perna, 0.13]} p={[x, perna / 2, -D / 2 + 0.12]} c={escurecer(cor, 0.8)} r={0.015} rough={0.6} />}
|
|
455
|
+
</group>
|
|
456
|
+
))}
|
|
457
|
+
<group position={[0, perna, 0]} rotation={[cp * 0.5, 0, 0]}>
|
|
458
|
+
<group scale={[achata, estica, achata]}>
|
|
459
|
+
<Bloco s={[W, H, D]} p={[0, H / 2, 0]} c={cor} r={0.025} rough={0.55} />
|
|
460
|
+
{[-1, 1].map((k) => (
|
|
461
|
+
<Bloco key={k} s={[0.16, 0.34, 0.3]} p={[k * (W / 2 + 0.08), H * 0.5 + (est.gesto[k > 0 ? 1 : 0] * 0.08), 0]} c={escurecer(cor, 0.93)} r={0.015} rough={0.55} />
|
|
462
|
+
))}
|
|
463
|
+
{[-1, 1].map((k) => (
|
|
464
|
+
<Chapa key={'o' + k} s={[0.13, pisca > 0.5 ? 0.025 : 0.155, 0.012]} p={[k * 0.23, H * 0.66, D / 2 + 0.006]} c="#0d0a0b" />
|
|
465
|
+
))}
|
|
466
|
+
{/* o bigode da referência: a barra grossa no meio e as pontas que sobem em curva */}
|
|
467
|
+
{p.bigode && (
|
|
468
|
+
<group position={[0, H * 0.43, D / 2 + 0.012]}>
|
|
469
|
+
{/* grosso no meio (dois gomos), afinando e subindo nas pontas */}
|
|
470
|
+
{[-1, 1].map((k) => (
|
|
471
|
+
<group key={k}>
|
|
472
|
+
<Chapa s={[0.2, 0.14, 0.024]} p={[k * 0.09, -0.005, 0]} rot={[0, 0, k * 0.12]} c="#0d0a0b" />
|
|
473
|
+
<Chapa s={[0.14, 0.1, 0.024]} p={[k * 0.235, 0.012, 0]} rot={[0, 0, k * 0.38]} c="#0d0a0b" />
|
|
474
|
+
<Chapa s={[0.08, 0.07, 0.024]} p={[k * 0.32, 0.07, 0]} rot={[0, 0, k * 0.95]} c="#0d0a0b" />
|
|
475
|
+
</group>
|
|
476
|
+
))}
|
|
477
|
+
</group>
|
|
478
|
+
)}
|
|
479
|
+
</group>
|
|
330
480
|
</group>
|
|
331
|
-
{pe(-0.27, 0.13, 1)}{pe(-0.1, 0.13, 2)}{pe(0.1, 0.13, 3)}{pe(0.27, 0.13, 4)}
|
|
332
481
|
</group>
|
|
333
482
|
);
|
|
334
483
|
}
|
|
@@ -426,12 +575,37 @@ function ChaoDeLadrilhos({ c }) {
|
|
|
426
575
|
}
|
|
427
576
|
|
|
428
577
|
/* ── A CÂMERA ──────────────────────────────────────────────────────────── */
|
|
578
|
+
/* O LUGAR de cada um: um sozinho no meio; dois frente a frente; três e
|
|
579
|
+
quatro num arco aberto para a câmera, todos virados para o centro. Bicho
|
|
580
|
+
pequeno fica um passo à frente, para não sumir atrás de quem é alto. */
|
|
581
|
+
const ARCO = { 1: [[0, 0]], 2: [[-0.78, 0], [0.78, 0]], 3: [[-1.35, 0.1], [0, -0.35], [1.35, 0.1]], 4: [[-1.9, 0.25], [-0.65, -0.25], [0.65, -0.25], [1.9, 0.25]] };
|
|
429
582
|
function lugarDe(el, i) {
|
|
430
|
-
const
|
|
431
|
-
|
|
583
|
+
const n = Math.min(4, el.length) || 1;
|
|
584
|
+
const [x, z] = (ARCO[n] || ARCO[1])[Math.min(i, n - 1)];
|
|
585
|
+
const baixo = el[i] && (el[i].tipo === 'animal' || el[i].tipo === 'coisa') && alturaDaCabeca(el[i]) < 1.2;
|
|
586
|
+
return [x, 0, z + (baixo && n > 1 ? 0.35 : 0)];
|
|
587
|
+
}
|
|
588
|
+
function giroDe(el, i) {
|
|
589
|
+
const n = el.length;
|
|
590
|
+
if (n < 2) return 0;
|
|
591
|
+
const [x] = lugarDe(el, i);
|
|
592
|
+
return clamp(-x * 0.5, -0.45, 0.45);
|
|
593
|
+
}
|
|
594
|
+
function alturaDaCabeca(p) {
|
|
595
|
+
if (!p) return 1.8;
|
|
596
|
+
if (p.tipo === 'robo') return 0.46 + 0.62; // o meio dos olhos do cubo
|
|
597
|
+
if (p.tipo === 'animal') return alturaDoBicho(p.especie);
|
|
598
|
+
if (p.tipo === 'coisa') return 1.1;
|
|
599
|
+
return 1.8;
|
|
600
|
+
}
|
|
601
|
+
/* Os figurantes ficam nas pontas do arco, um pouco atrás: estão na cena, mas não roubam o plano. */
|
|
602
|
+
function lugarDoFigurante(el, fig, j) {
|
|
603
|
+
const n = Math.max(1, el.length);
|
|
604
|
+
const borda = n === 1 ? 1.15 : n === 2 ? 1.75 : 2.5;
|
|
605
|
+
const lado = j % 2 ? 1 : -1;
|
|
606
|
+
const baixo = fig.tipo === 'animal' || fig.tipo === 'coisa';
|
|
607
|
+
return [lado * (borda + Math.floor(j / 2) * 0.9), 0, baixo ? 0.45 : -0.5];
|
|
432
608
|
}
|
|
433
|
-
function giroDe(el, i) { return el.length > 1 ? (el[i].lado < 0 ? 0.42 : -0.42) : 0; }
|
|
434
|
-
function alturaDaCabeca(p) { return p && p.tipo === 'robo' ? 1.55 : 1.8; }
|
|
435
609
|
|
|
436
610
|
function cameraDe(pl, t, part, aspecto) {
|
|
437
611
|
const vert = aspecto < 1;
|
|
@@ -443,14 +617,22 @@ function cameraDe(pl, t, part, aspecto) {
|
|
|
443
617
|
const k = clamp((t - pl.de) / Math.max(0.5, pl.ate - pl.de));
|
|
444
618
|
const fovK = vert ? 1.45 : 1;
|
|
445
619
|
let pos, alvo, fov;
|
|
620
|
+
// Bicho e coisa falam DE FRENTE: por cima do ombro, o focinho ficava de perfil.
|
|
621
|
+
if (pl.tipo === 'ombro' && el[quem] && (el[quem].tipo === 'animal' || el[quem].tipo === 'coisa')) pl = { ...pl, tipo: 'medio' };
|
|
446
622
|
if (pl.tipo === 'aberto' || pl.tipo === 'largo') {
|
|
447
623
|
// O plano aberto de cima, como o da referência: o set inteiro e a parede de telas.
|
|
448
624
|
fov = (pl.tipo === 'largo' ? 38 : 34) * fovK;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
625
|
+
// Mais gente no set, a câmera recua; elenco baixo (bichos), ela desce.
|
|
626
|
+
const largura = el.length > 2 ? 1 + (el.length - 2) * (vert ? 0.42 : 0.28) : 1;
|
|
627
|
+
const dist = (pl.tipo === 'largo' ? (vert ? 8.2 : 6.6) : (vert ? 6.6 : 5.2)) * largura;
|
|
628
|
+
const alturaMedia = el.length ? el.reduce((s, p) => s + alturaDaCabeca(p), 0) / el.length : 1.8;
|
|
629
|
+
pos = [pl.lado * (el.length > 2 ? 0.2 : 0.5), (pl.tipo === 'largo' ? 3.1 : 2.5) * (0.55 + 0.45 * alturaMedia / 1.8), dist];
|
|
630
|
+
alvo = [0, Math.max(0.6, alturaMedia - 0.35), -0.6];
|
|
452
631
|
} else if (pl.tipo === 'ombro' && dois) {
|
|
453
|
-
|
|
632
|
+
// Por cima do ombro de quem está MAIS PERTO de quem fala (com 3 ou 4 no set).
|
|
633
|
+
let o = quem === 0 ? 1 : 0;
|
|
634
|
+
for (let j = 0; j < el.length; j++) if (j !== quem && Math.abs(lugarDe(el, j)[0] - lugarDe(el, quem)[0]) < Math.abs(lugarDe(el, o)[0] - lugarDe(el, quem)[0])) o = j;
|
|
635
|
+
const h = cabeca(o), s = cabeca(quem);
|
|
454
636
|
const d = [h[0] - s[0], h[2] - s[2]], n = Math.hypot(d[0], d[1]) || 1;
|
|
455
637
|
pos = [h[0] + (d[0] / n) * 0.9, h[1] + 0.25, h[2] + 1.6];
|
|
456
638
|
alvo = [s[0], s[1] - 0.1, s[2]]; fov = 32 * fovK;
|
|
@@ -466,6 +648,8 @@ function cameraDe(pl, t, part, aspecto) {
|
|
|
466
648
|
pos = [h[0] + dir[0] * dist, h[1] + baixo, h[2] + dir[2] * dist];
|
|
467
649
|
alvo = [h[0], h[1] + (pl.tipo === 'medio' ? -0.35 : -0.02), h[2]];
|
|
468
650
|
}
|
|
651
|
+
// No vertical a legenda está no alto: a câmera mira acima, e o rosto desce no quadro.
|
|
652
|
+
if (vert) alvo = [alvo[0], alvo[1] + (pl.tipo === 'aberto' || pl.tipo === 'largo' ? 0.45 : pl.tipo === 'medio' ? 0.3 : pl.tipo === 'ombro' ? 0.22 : 0.17), alvo[2]];
|
|
469
653
|
const m = suave(k);
|
|
470
654
|
if (pl.mov === 'entra' || pl.mov === 'sai') {
|
|
471
655
|
const s = pl.mov === 'entra' ? m : 1 - m;
|
|
@@ -490,18 +674,39 @@ function cameraDe(pl, t, part, aspecto) {
|
|
|
490
674
|
/* ── POR CIMA: a legenda da referência ─────────────────────────────────── */
|
|
491
675
|
const FONTE = "'Inter','SF Pro Display','Helvetica Neue',Arial,sans-serif";
|
|
492
676
|
|
|
677
|
+
// Palavra que não fecha frase: o pedaço da legenda não termina nela ("uma fogueira no").
|
|
678
|
+
const FRACA = /^(no|na|nos|nas|de|do|da|dos|das|o|a|os|as|e|que|um|uma|com|em|pra|pro|por|para|ao|se|mais|muito|meu|minha|seu|sua)$/i;
|
|
493
679
|
function pedacos(palavras) {
|
|
494
680
|
const out = [];
|
|
495
681
|
let cur = [];
|
|
496
682
|
for (const w of palavras) {
|
|
497
683
|
cur.push(w);
|
|
498
684
|
const letras = cur.reduce((n, x) => n + String(x.t).length + 1, 0);
|
|
499
|
-
if (letras > 30 || /[.!?;:]$/.test(w.t) || (cur.length >= 4 && /,$/.test(w.t))) {
|
|
685
|
+
if (letras > 30 || /[.!?;:]$/.test(w.t) || (cur.length >= 4 && /,$/.test(w.t))) {
|
|
686
|
+
// Terminou numa palavra fraca: ela desce para o próximo pedaço.
|
|
687
|
+
if (cur.length > 2 && FRACA.test(String(w.t).replace(/[^\p{L}]/gu, '')) && !/[.!?;:,]$/.test(w.t)) { out.push(cur.slice(0, -1)); cur = [w]; continue; }
|
|
688
|
+
out.push(cur); cur = [];
|
|
689
|
+
}
|
|
500
690
|
}
|
|
501
691
|
if (cur.length) out.push(cur);
|
|
502
692
|
return out;
|
|
503
693
|
}
|
|
504
694
|
|
|
695
|
+
/* O LAYOUT DO QUE VAI POR CIMA (11/10, "nada fora do lugar nem sobrepondo"):
|
|
696
|
+
- a legenda mora no ALTO no vertical (25% da altura, como na 2ª
|
|
697
|
+
referência) e embaixo no horizontal (80%); a câmera do vertical mira
|
|
698
|
+
acima do rosto (cameraDe), então o rosto fica do meio para baixo e a
|
|
699
|
+
legenda nunca passa por cima dele;
|
|
700
|
+
- cabe em no máximo DUAS linhas na largura útil (84%): a fonte encolhe para
|
|
701
|
+
o pedaço mais longo, em vez de quebrar em três ou vazar da tela;
|
|
702
|
+
- o cartão de destaque fica no alto (12% / 8%), numa linha só, e também
|
|
703
|
+
encolhe para caber — os dois nunca dividem a mesma faixa. */
|
|
704
|
+
const LARGURA_LETRA = 0.54; // a largura média de uma letra da Inter, em "em"
|
|
705
|
+
function tamanhoQueCabe(texto, base, largura, linhas) {
|
|
706
|
+
const porLinha = Math.ceil(String(texto).length / linhas) + 2;
|
|
707
|
+
return Math.max(Math.round(base * 0.6), Math.min(base, Math.floor(largura / (porLinha * LARGURA_LETRA))));
|
|
708
|
+
}
|
|
709
|
+
|
|
505
710
|
function Legenda({ part, t, W, H }) {
|
|
506
711
|
const f = part.falas.find((x) => t >= x.de - 0.1 && t <= x.ate + 0.35);
|
|
507
712
|
if (!f) return null;
|
|
@@ -509,12 +714,14 @@ function Legenda({ part, t, W, H }) {
|
|
|
509
714
|
const g = grupos.find((gr) => t <= gr[gr.length - 1].ate + 0.2) || grupos[grupos.length - 1];
|
|
510
715
|
const entra = suave((t - (g[0].de - 0.08)) / 0.18);
|
|
511
716
|
const sai = 1 - suave((t - (f.ate + 0.15)) / 0.2);
|
|
512
|
-
const
|
|
717
|
+
const vertical = H > W;
|
|
718
|
+
const util = W * 0.84;
|
|
513
719
|
const texto = g.map((w) => w.t).join(' ');
|
|
720
|
+
const tam = tamanhoQueCabe(texto, Math.round(vertical ? W * 0.05 : H * 0.05), util, 2);
|
|
514
721
|
return (
|
|
515
|
-
<div style={{ position: 'absolute', left: 0, right: 0, top: H * (
|
|
516
|
-
<div style={{ fontFamily: FONTE, fontWeight: 600, fontSize: tam, lineHeight: 1.
|
|
517
|
-
opacity: entra * sai, transform: `translateY(${(1 - entra) * tam * 0.15}px)`,
|
|
722
|
+
<div style={{ position: 'absolute', left: 0, right: 0, top: H * (vertical ? 0.25 : 0.8), display: 'flex', justifyContent: 'center' }}>
|
|
723
|
+
<div style={{ maxWidth: util, fontFamily: FONTE, fontWeight: 600, fontSize: tam, lineHeight: 1.18, color: '#ffffff', textAlign: 'center', letterSpacing: '-0.005em',
|
|
724
|
+
overflowWrap: 'break-word', textWrap: 'balance', opacity: entra * sai, transform: `translateY(${(1 - entra) * tam * 0.15}px)`,
|
|
518
725
|
textShadow: '0 2px 12px rgba(0,0,0,0.55), 0 1px 2px rgba(0,0,0,0.6)' }}>{texto}</div>
|
|
519
726
|
</div>
|
|
520
727
|
);
|
|
@@ -526,10 +733,11 @@ function Destaque({ part, t, W, H, acento }) {
|
|
|
526
733
|
const k = t - (f.de + 0.3);
|
|
527
734
|
const fim = Math.min(f.ate + 0.3, f.de + 3) - (f.de + 0.3);
|
|
528
735
|
const s = suave(k / 0.3) * (1 - suave((k - fim + 0.3) / 0.3));
|
|
529
|
-
const
|
|
736
|
+
const util = W * 0.86;
|
|
737
|
+
const tam = tamanhoQueCabe(f.destaque, Math.round(Math.min(W, H) * 0.045), util - Math.min(W, H) * 0.07, 1);
|
|
530
738
|
return (
|
|
531
739
|
<div style={{ position: 'absolute', left: 0, right: 0, top: H * (H > W ? 0.12 : 0.08), display: 'flex', justifyContent: 'center' }}>
|
|
532
|
-
<div style={{ opacity: s, transform: `translateY(${(1 - s) * -12}px)`, background: 'rgba(12,14,32,0.72)', border: `2px solid ${acento}`, borderRadius: tam * 0.5,
|
|
740
|
+
<div style={{ maxWidth: util, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', opacity: s, transform: `translateY(${(1 - s) * -12}px)`, background: 'rgba(12,14,32,0.72)', border: `2px solid ${acento}`, borderRadius: tam * 0.5,
|
|
533
741
|
padding: `${tam * 0.3}px ${tam * 0.7}px`, fontFamily: FONTE, fontWeight: 700, fontSize: tam, color: '#fff', boxShadow: `0 0 ${tam * 0.8}px ${acento}66` }}>{f.destaque}</div>
|
|
534
742
|
</div>
|
|
535
743
|
);
|
|
@@ -553,23 +761,50 @@ export function VoxelCena({ el, t }) {
|
|
|
553
761
|
for (const f of falas) if (f.quem === i && f.de <= t + 0.2) e = t <= f.ate + 2.5 ? f.emocao : 'neutro';
|
|
554
762
|
return e;
|
|
555
763
|
};
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
764
|
+
// A ação da fala vale da voz até 0,6 s depois, entrando e saindo suave.
|
|
765
|
+
const acaoDe = (i) => {
|
|
766
|
+
for (const f of falas) {
|
|
767
|
+
if (f.quem !== i || !f.acao || f.acao === 'falar') continue;
|
|
768
|
+
if (t >= f.de - 0.3 && t <= f.ate + 0.6) return [f.acao, Math.min(suave((t - f.de + 0.3) / 0.4), 1 - suave((t - f.ate - 0.1) / 0.5))];
|
|
769
|
+
}
|
|
770
|
+
return [null, 0];
|
|
771
|
+
};
|
|
772
|
+
const quemFala = () => { const f = falas.find((x) => t >= x.de && t <= x.ate); return f ? f.quem : -1; };
|
|
773
|
+
const estado = (i, figurante = false) => {
|
|
774
|
+
const fala = !figurante && falandoAgora(i);
|
|
775
|
+
const qf = quemFala();
|
|
776
|
+
const outro = qf >= 0 && qf !== i;
|
|
777
|
+
const enfase = figurante ? [0, 0] : gesto(falas, i, t);
|
|
560
778
|
const nod = Math.sin(t * (fala ? 5.2 : 1.4) + i) * (fala ? 0.04 : 0.015) + (outro ? Math.max(0, Math.sin(t * 2.1 + i)) * 0.04 : 0);
|
|
561
|
-
const emo = emocaoDe(i);
|
|
779
|
+
const emo = figurante ? 'feliz' : emocaoDe(i);
|
|
562
780
|
const e = EMO[emo] || EMO.neutro;
|
|
563
781
|
const micro = (hash(Math.floor(t * 1.5) + i * 7) - 0.5) * 0.6;
|
|
564
782
|
// Quem ouve vira um pouco para quem fala; quem fala olha para a câmera de vez em quando.
|
|
565
|
-
const
|
|
783
|
+
const dx = outro && !figurante ? lugarDe(elenco, qf)[0] - lugarDe(elenco, i)[0] : 0;
|
|
784
|
+
const paraQuemFala = clamp(dx * 0.35, -0.35, 0.35);
|
|
785
|
+
const [acao, forcaAcao] = figurante ? [null, 0] : acaoDe(i);
|
|
566
786
|
return {
|
|
567
|
-
t, falando: fala, emocao: emo, boca: boca(falas, i, t), pisca: piscada(i, t),
|
|
568
|
-
gesto: enfase, enfase: Math.max(enfase[0], enfase[1]),
|
|
569
|
-
olhar: [micro + (outro ? (
|
|
570
|
-
cabeca: [Math.sin(t * 0.7 + i * 2) * 0.07 +
|
|
787
|
+
t, falando: fala, emocao: emo, boca: figurante ? [0, 0.55] : boca(falas, i, t), pisca: piscada(i + (figurante ? 5 : 0), t),
|
|
788
|
+
gesto: enfase, enfase: Math.max(enfase[0], enfase[1]), acao, forcaAcao,
|
|
789
|
+
olhar: [micro + (outro ? Math.sign(dx) * 0.8 : 0), 0.1],
|
|
790
|
+
cabeca: [Math.sin(t * 0.7 + i * 2) * 0.07 + paraQuemFala, nod, (e.inclina || 0) * (i % 2 ? -1 : 1) + Math.sin(t * 0.5 + i) * 0.02],
|
|
571
791
|
};
|
|
572
792
|
};
|
|
793
|
+
const desenhar = (p, est) => (p.tipo === 'robo' ? <Mascote p={p} est={est} />
|
|
794
|
+
: p.tipo === 'animal' ? <Animal p={p} est={est} />
|
|
795
|
+
: p.tipo === 'coisa' ? <Coisa p={p} est={est} icones={icones} />
|
|
796
|
+
: <PessoaBloco p={p} est={est} />);
|
|
797
|
+
/* O MUNDO da hora: o da fala que está (ou acabou de estar) no ar. A troca
|
|
798
|
+
acontece no começo da fala — junto com o corte de câmera. */
|
|
799
|
+
const mundos = part.mundos || [];
|
|
800
|
+
let iMundo = 0;
|
|
801
|
+
for (const f of falas) if (f.de - 0.1 <= t && f.mundo != null) iMundo = f.mundo;
|
|
802
|
+
const mundo = mundos[iMundo];
|
|
803
|
+
const ehTelas = !mundo || mundo.telas;
|
|
804
|
+
const figurantes = part.figurantes || [];
|
|
805
|
+
// Todo PNG que vira bloco (peças fora do catálogo e coisas que falam), lido antes do quadro.
|
|
806
|
+
const srcs = useMemo(() => [...new Set([...mundos.flatMap((m) => m.props.map((p) => p.src)), ...elenco.map((e) => e.src), ...figurantes.map((e) => e.src)].filter(Boolean))].sort(), [part]);
|
|
807
|
+
const icones = useIcones(srcs);
|
|
573
808
|
const fadeIn = clamp(t / 0.5), fadeOut = clamp((part.duracao - t) / 0.6);
|
|
574
809
|
|
|
575
810
|
return (
|
|
@@ -579,24 +814,34 @@ export function VoxelCena({ el, t }) {
|
|
|
579
814
|
gl={{ antialias: true, preserveDrawingBuffer: true, toneMapping: THREE.ACESFilmicToneMapping, toneMappingExposure: 1.0 }}>
|
|
580
815
|
<Camera pos={cam.pos} alvo={cam.alvo} fov={cam.fov} />
|
|
581
816
|
<Ambiente />
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
817
|
+
{ehTelas ? (
|
|
818
|
+
<>
|
|
819
|
+
<color attach="background" args={[c.parede]} />
|
|
820
|
+
<fog attach="fog" args={[c.parede, 9, 20]} />
|
|
821
|
+
<hemisphereLight args={[c.recorte, escurecer(c.chao, 0.4), 0.55]} />
|
|
822
|
+
{/* a luz principal, quente, de cima e da frente: o que dá volume aos blocos */}
|
|
823
|
+
<spotLight position={[2.2, 5.2, 4.4]} angle={0.5} penumbra={0.8} intensity={120} distance={22} decay={2} color={c.luz} castShadow
|
|
824
|
+
shadow-mapSize-width={2048} shadow-mapSize-height={2048} shadow-bias={-0.0003} shadow-radius={4} />
|
|
825
|
+
{/* o recorte roxo por trás, que separa o boneco do fundo */}
|
|
826
|
+
<spotLight position={[-2.5, 3.6, -2.4]} angle={0.7} penumbra={1} intensity={60} distance={14} decay={2} color={c.recorte} />
|
|
827
|
+
<pointLight position={[0, 2, -2.6]} intensity={16} distance={8} color="#6f7dff" />
|
|
828
|
+
<directionalLight position={[-3, 2, 4]} intensity={0.35} color="#c9d4ff" />
|
|
829
|
+
<ChaoDeLadrilhos c={c} />
|
|
830
|
+
<ParedeDeTelas c={c} paineis={part.paineis} t={t} />
|
|
831
|
+
</>
|
|
832
|
+
) : <Mundo key={iMundo} m={mundo} t={t} icones={icones} paineis={part.paineis} />}
|
|
594
833
|
{elenco.map((p, i) => (
|
|
595
834
|
<group key={i} position={lugarDe(elenco, i)} rotation={[0, giroDe(elenco, i), 0]}>
|
|
596
|
-
{
|
|
835
|
+
{desenhar(p, estado(i))}
|
|
836
|
+
</group>
|
|
837
|
+
))}
|
|
838
|
+
{figurantes.map((p, j) => (
|
|
839
|
+
<group key={'f' + j} position={lugarDoFigurante(elenco, p, j)} rotation={[0, (j % 2 ? -1 : 1) * 0.5, 0]}>
|
|
840
|
+
{desenhar(p, estado(10 + j, true))}
|
|
597
841
|
</group>
|
|
598
842
|
))}
|
|
599
|
-
|
|
843
|
+
{/* Fora do set de telas o céu é claro: o brilho cai, senão o céu inteiro estoura. */}
|
|
844
|
+
<Lente W={W} H={H} foco={cam.foco} abertura={cam.abertura} desfoqueMax={cam.desfoqueMax} brilho={ehTelas ? 0.5 : mundo.cobertura ? 0.5 : mundo.estrelas || mundo.hora === 'noite' ? 0.45 : 0.18} />
|
|
600
845
|
</ThreeCanvas>
|
|
601
846
|
<div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(ellipse at center, rgba(0,0,0,0) 60%, rgba(0,0,0,0.45) 100%)' }} />
|
|
602
847
|
<Destaque part={part} t={t} W={W} H={H} acento={c.acento} />
|
package/lib/estudio/kit.js
CHANGED
|
@@ -67,7 +67,7 @@ const PECAS = {
|
|
|
67
67
|
animacao: { precisa: ['src'] },
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'voxel.jsx', 'animacao.jsx', 'index.jsx'];
|
|
70
|
+
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'mundo.jsx', 'voxel.jsx', 'animacao.jsx', 'index.jsx'];
|
|
71
71
|
|
|
72
72
|
/** Os arquivos do kit, para o projeto Remotion: [{nome: 'kit/base.jsx', conteudo}]. */
|
|
73
73
|
function arquivos() {
|
package/lib/estudio/kokoro.js
CHANGED
|
@@ -87,11 +87,25 @@ async function narrar(lista, mp3, { voz, uv, espeak = acharEspeak(), executar =
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/** A ordem das vozes: pedida → Kokoro e depois o Edge; senão Gemini →
|
|
91
|
-
|
|
90
|
+
/** A ordem das vozes: pedida → Kokoro e depois o Edge; senão Gemini → Edge → Kokoro.
|
|
91
|
+
O Edge (vozes neurais da Microsoft, ou a ElevenLabs quando há chave) vem
|
|
92
|
+
ANTES do Kokoro desde 11/10: com a cota do Gemini no fim, o Kokoro em
|
|
93
|
+
português era a "voz robótica" que a pessoa ouvia. `motorVoz: 'edge'` no
|
|
94
|
+
roteiro pula as outras. */
|
|
92
95
|
function ordem(voz, env = process.env, { soEdge = false } = {}) {
|
|
93
96
|
if (soEdge) return ['edge'];
|
|
94
|
-
return querKokoro(voz, env) ? ['kokoro', 'edge'] : ['gemini', '
|
|
97
|
+
return querKokoro(voz, env) ? ['kokoro', 'edge'] : ['gemini', 'edge', 'kokoro'];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* A voz do Gemini no Edge: o narrar.py só conhece antonio, francisca e
|
|
101
|
+
thalita — o nome do Gemini ("Kore") caía na primeira, e toda fala de mulher
|
|
102
|
+
saía na voz do Antonio. Mantém o GÊNERO de quem fala. */
|
|
103
|
+
const GEMINI_FEMININAS = new Set(['kore', 'aoede', 'leda', 'zephyr', 'sulafat', 'achird', 'despina', 'erinome', 'laomedeia', 'pulcherrima', 'vindemiatrix', 'callirrhoe', 'autonoe', 'gacrux']);
|
|
104
|
+
function vozNoEdge(voz, reserva = 'thalita') {
|
|
105
|
+
const v = String(voz || '').trim().toLowerCase();
|
|
106
|
+
if (['antonio', 'francisca', 'thalita'].includes(v)) return v;
|
|
107
|
+
if (!v || querKokoro(v, {})) return reserva;
|
|
108
|
+
return GEMINI_FEMININAS.has(v) ? 'francisca' : 'antonio';
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
/** Roda a corrente: a primeira voz que der certo ganha; a falha de uma nunca derruba. */
|
|
@@ -108,4 +122,4 @@ async function corrente(passos, provedores) {
|
|
|
108
122
|
return ultimo;
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
module.exports = { querKokoro, pronuncia, vozKokoro, acharEspeak, comando, narrar, ordem, corrente, DEPS, SCRIPT };
|
|
125
|
+
module.exports = { vozNoEdge, querKokoro, pronuncia, vozKokoro, acharEspeak, comando, narrar, ordem, corrente, DEPS, SCRIPT };
|
package/lib/estudio/margem.js
CHANGED
|
@@ -44,7 +44,7 @@ function areaSegura(W, H, m = MARGEM) {
|
|
|
44
44
|
/* As peças do kit que são o QUADRO inteiro (textura, padrão, o gerativo que
|
|
45
45
|
tem a própria conta) ou enfeite de fundo. */
|
|
46
46
|
const PECAS_FUNDO = new Set(['textura', 'padrao', 'particulas', 'papel', 'grade', 'orbe', 'cromo', 'esfera', 'estrela',
|
|
47
|
-
'gerativo', 'fluido', 'personagem', 'site']);
|
|
47
|
+
'gerativo', 'fluido', 'personagem', 'voxel', 'site']);
|
|
48
48
|
|
|
49
49
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
50
50
|
|