# 3D de verdade

Cena com geometria, câmera e luz calculadas a cada quadro, feita em Three.js dentro do projeto HyperFrames
(`modelo-projeto`). As peças têm lateral, espessura e interior; a luz reage quando giram; a sombra cai no chão.
Não é a mesma coisa que camadas planas inclinadas ou texto atrás da pessoa: isso é 2,5D e é chamado assim.

O projeto já traz tudo o que o 3D precisa, sem baixar nada na hora do render:

- `assets/three/`: o three.js r181 (licença MIT em `LICENSE`, origem e conferência em `ORIGEM.txt`), com o
  leitor de modelo `.glb`/`.gltf` (`GLTFLoader.js`) e a luz de estúdio (`RoomEnvironment.js`).
- `compositions/cenas/3d/motor-three.js`: o motor comum (palco, luz, sombra, sólidos, texto em volume, modelo,
  vídeo como textura, rótulos, enquadramento e conferências).
- `compositions/cenas/3d/k3d_*.html`: o kit, quatro cenas prontas para adaptar (tabela abaixo).

## Como falar com a pessoa

- Português simples. Nada de código nem nome de biblioteca na conversa; "3D de verdade" e "o teste de 5
  segundos" bastam.
- Antes de montar, conte o plano da cena em 3 ou 4 linhas (modelo abaixo) e espere o ok se ela pediu para
  aprovar. Depois do teste de 5 s, mostre o arquivo e diga o que conferiu.
- Qualquer troca de técnica (3D virou desenho em 2D, por exemplo) é dita com todas as letras e com o nome certo.

## Quando usar e quando não usar

Pergunte primeiro: que relação some numa vista plana? Use 3D quando a resposta for uma destas:

- **volume**: o objeto tem corpo e isso importa (espessura, lateral, o que tem dentro);
- **ponto de vista que muda**: girar mostra algo que de frente não aparece;
- **peças que se separam e voltam**: camadas de um produto, partes de um método que se encaixam;
- **interior**: o que fica por dentro de uma caixa, de um aparelho, de um lugar;
- **caminho**: percurso que contorna obstáculos, circulação, ordem de lugares;
- **disposição**: onde as coisas ficam umas em relação às outras, o que tampa o quê.

Não use quando:

- a ideia é plana (lista, número, comparação lado a lado, passo a passo): 2D explica melhor
  (`references/animacao.md`);
- o motivo é só "parecer caro" ou "ficar moderno";
- não há tempo de fala para mostrar a mudança com calma (menos de uns 3 s para a cena);
- a relação depende de medida que ninguém conferiu e a pessoa quer escala: sem medida conferida, a cena é
  esquemática e não escreve tamanho nem escala.

Honestidade:

- Vídeo ou imagem numa tela 3D é um plano chapado. O aparelho tem volume; a pessoa na tela não ganha volume
  quando gira. Diga isso na entrega.
- Texto atrás da pessoa e camadas inclinadas são 2,5D. Não chame de 3D.
- Modelo `.glb`/`.gltf` só da pessoa ou liberado para uso. Nada de modelo comprado, gerado fora ou baixado sem
  autorização.

## O plano da cena (antes de escrever qualquer coisa)

Escreva e mostre, curto:

```
Cena 3D de 12,5 a 16,9 s ("Primeiro, ela ouviu tudo... palavra por palavra")
Objetos: 4 placas empilhadas (Vídeo bruto, Áudio da fala, Cada palavra, Os cortes), esquemático, sem escala.
Começa: parado, de 3/4 um pouco de cima, peças encaixadas.
Revela: em "ouviu" as placas se afastam e a câmera gira uns 20 graus; os rótulos aparecem presos a cada uma;
em "palavra" (a segunda) elas voltam ao encaixe. Termina parado nesse ângulo.
Render: HyperFrames neste Mac, com placa de vídeo; teste de 5 s antes do resto.
```

As quatro perguntas que o plano responde: quais objetos (sólidos simples ou o modelo da pessoa), de onde a
câmera começa, o que se quer revelar e em que frase, e como vai ser renderizado de fato.

## O kit

Cada cena é uma sub-composição do HyperFrames com os PARÂMETROS no topo do `<script>`, comentados. Tempo é
palavra da fala (`{palavra: "ouviu", depois: 0, padrao: 0.9}`) ou segundos da cena. Cores `null` usam as do
estilo do projeto (`papelaria`, `noite` ou `laranja`). Todas têm `fundo` (`"papel"`, `"#rrggbb"` ou `null` =
transparente), `area` (onde o 3D fica, em px; `null` = zona segura), `recorte` (com `area`, o 3D só aparece
dentro dela, numa janela de cantos redondos), `sombras` (false deixa o render mais leve; a sombra de contato
fica), `entrada` e `saida` (segundos do surgir e do sumir).

| cena | o que faz | parâmetros principais |
|---|---|---|
| `k3d_explodida` | peças empilhadas se separam na palavra, com um giro curto da câmera, ganham rótulos presos a cada uma e voltam ao encaixe | `pecas` (nome, altura, forma `placa`/`caixa`/`cilindro`/`modelo` com `arquivo`, cor), `largura`, `profundidade`, `encolhe`, `afastamento`, `separa`, `duracaoSepara`, `junta` (null = ficam abertas), `duracaoJunta`, `cameraInicio`, `cameraFim`, `rotulos` (`dir`, `esq` ou null) |
| `k3d_caminho` | chão com blocos e um caminho que contorna os blocos; a câmera abre parada no primeiro ponto, anda com o caminho (que vai sendo desenhado), chega em cada parada na palavra e termina parada numa visão geral | `pontos` (x, z; com `nome` e `chega` = parada rotulada; sem nome = curva), `obstaculos` (x, z, largura, profundidade, altura), `base` (maquete embaixo), `seguir` (azim, elev, janela), `acompanha`, `final` (azim, elev, duracao; null = fica no último ponto) |
| `k3d_volume` | uma palavra (ou uma caixa, ou o modelo da pessoa) em volume, parada, que gira um pouco na palavra para mostrar espessura e laterais, com luz e sombra; termina parada | `objeto` (`texto`, `caixa`, `modelo`), `texto` (`\n` quebra a linha), `modelo`, `cor`, `corLados`, `espessura`, `gira`, `duracaoGiro`, `anguloInicio`, `anguloFim`, `elevacao`, `rotulo` |
| `k3d_tela` | um notebook ou celular de verdade em 3D com o vídeo (ou uma imagem) da pessoa na tela; abre de frente, gira na palavra e mostra o corpo do aparelho. A tela é plano chapado | `aparelho` (`notebook`, `celular`), `encaixe` (`conter`, `cobrir`), `foco`, `corCorpo`, `gira`, `duracaoGiro`, `cameraInicio`, `cameraFim`, `rotulo`; e o `<video id="k3dt-midia">` (ou `<img>` com o mesmo id) |

O que o motor faz sozinho (não precisa escrever): luz de estúdio com sombra suave, chão com grade que some nas
bordas (só com fundo), sombra de contato embaixo das peças, enquadramento medido (projeta os cantos da peça e
acha a distância em que ela cabe na área), rótulos presos por projeção (seguem a peça, desviam um do outro,
ficam dentro da área e somem se a peça sair da área), texto em volume desenhado na fonte do projeto (acento
serve), e duas conferências que acusam erro no `check`: a câmera entrando num objeto e o caminho passando por
dentro de um bloco.

Funções do motor para uma cena nova (dentro de `construir(K, P)`): `P.placa`, `P.caixa`, `P.cilindro`,
`P.material`, `P.textoEmVolume`, `P.carregarModelo`, `P.texturaDeMidia`, `P.luzes`, `P.chao`,
`P.sombraDeContato`, `P.rotulos`, `P.orbita`, `P.distanciaQueCabe`, `P.solido` (registra o objeto para a
conferência da câmera); e no tempo `K.momento`, `K.palavra`, `K.trecho`, `K.mistura`, `K.misturaPose`,
`K.suave`, `K.freia`. A cena devolve `atualizar(t)`, que põe tudo no lugar para aquele `t`.

## Como ligar no projeto

1. Copie a cena do kit para `compositions/cenas/<nome>.html` (por exemplo `s05_camadas.html`). Troque, no
   arquivo inteiro, o id da cena (`k3d_explodida` vira `s05_camadas`: no `<template id>`, no
   `data-composition-id` da raiz, no `ID` e em `window.__timelines[...]`) e o prefixo dos ids (`k3de-` vira
   `s05-`). Pode também usar a cena direto em `compositions/cenas/3d/`, se ela aparecer uma vez só no vídeo.
2. Mude os PARÂMETROS. Não mexa no bloco "ligação com o HyperFrames".
3. No `projeto.json`:

```json
"cenas_proprias": [
  {"arquivo": "compositions/cenas/s05_camadas.html", "de": 12.5, "ate": 16.9, "esconde_legenda": true}
]
```

4. Formato 16:9: troque `data-width="1080" data-height="1920"` da raiz e do `<canvas>` para 1920 e 1080.
5. `k3d_tela` com vídeo: no `<video id="...-midia">`, ponha em `data-media-start` o `de` da cena (o segundo do
   corte em que ela começa, para a boca bater com a voz) e em `data-duration` a duração da cena. Com imagem,
   troque a tag por `<img id="...-midia" src="assets/foto.jpg" alt=""/>`.

Vídeo com a pessoa falando: antes e depois do 3D a tela é toda dela. Se ela precisa continuar à vista enquanto a
cena roda, ela desce para a moldura e o 3D fica com o espaço de cima (ele explicando as camadas do Chip Editor
lá embaixo, e as camadas se abrindo em cima, por exemplo). No projeto são duas coisas no mesmo trecho: um bloco
`moldura` no trecho e a cena, com `recorte: true` e `area`
apontando para o retângulo livre acima da moldura (`[140, 230, 940, 950]` no 9:16). O `fundo` decide o que se vê
atrás dos objetos: `null` deixa aparecer o papel da moldura, `"papel"` deixa a janela lisa. Nenhum rótulo sai da
`area` nem encosta na pessoa.

A moldura leva uns 0,7 s para fechar, e enquanto fecha a pessoa ainda ocupa a parte de cima. Comece a cena
quando a moldura termina de fechar (a cena recebe `moldura.fechada_em`), não no mesmo segundo do bloco: se a cena
entrar junto, os objetos passam por cima do rosto nesse meio segundo. Com fim parado (`segura_fim`), deixe o
`ate` da cena no fim do corte: a cena continua até o fim do vídeo.

## Regras do movimento

1. Abre parado: a vista de partida fica um instante sem mexer antes da primeira mudança.
2. Mexe só o que revela a relação: um giro curto (15 a 40 graus), um avanço, ou as peças se afastando. Uma
   mudança em destaque por vez.
3. Preso à fala: cada movimento começa na palavra que ele explica (`separa`, `junta`, `gira`, `chega`).
4. Termina parado no ângulo que mais explica, por pelo menos uns 0,3 s antes de voltar para a pessoa.
5. A câmera nunca atravessa um objeto (o motor confere em todos os quadros e dá erro no `check`).
6. Rótulos curtos (1 a 3 palavras), legíveis, presos ao que descrevem, sem medida inventada.
7. Sombra de contato e oclusão separam os planos: não desligue a sombra sem motivo.
8. Tudo em função do tempo: nada de `Math.random`, `Date.now`, contador de quadros, física ou qualquer coisa
   que dependa do quadro anterior. Animação que vem dentro do modelo é posta no tempo (`noTempo(t)`).

## O portão: teste de 5 s

Antes do resto do vídeo, um trecho de uns 5 s com o ângulo mais difícil, o momento em que uma peça esconde a
outra e a transformação principal, renderizado como vídeo do mesmo jeito do final. Imagem parada e prévia do
estúdio não provam nada. Na pasta do projeto:

```bash
python3 build.py amostra 12.2 17.2
python3 build.py hf lint
python3 build.py hf check
python3 build.py hf render --quality draft --output renders/teste-3d.mp4
```

(`12.2 17.2`: segundos do corte, cobrindo a transformação principal; o `amostra` pode começar no meio de uma
cena, ela continua do ponto certo.) Depois tire quadros do arquivo para olhar parado, por exemplo antes, no
meio e depois do giro:

```bash
for t in 0.8 1.5 2.6 4.4; do ffmpeg -v error -y -ss $t -i renders/teste-3d.mp4 -frames:v 1 renders/q_$t.png; done
```

Opcional, para provar que o render se repete: renderize de novo com outro nome e compare só a área do 3D (a
moldura e o vídeo do corte podem variar 1 ou 2 pixels entre dois renders, então o arquivo inteiro não sai byte a
byte igual quando há moldura): tire o mesmo quadro dos dois e recorte a `area` da cena antes de comparar.

## O que conferir no arquivo

- Ao girar, aparecem as laterais e a espessura; a luz muda nas faces conforme o ângulo.
- Sombra no chão e sombra de contato embaixo das peças; a peça de cima escurece a de baixo.
- Uma peça passa na frente da outra do jeito certo (a de trás some atrás da da frente).
- Rótulos legíveis no tamanho do celular, presos ao que descrevem enquanto tudo se mexe, sem cobrir a peça,
  a pessoa ou a legenda.
- Peças que se separam voltam exatamente ao encaixe, sem trocar de lugar entre si.
- Abre parado, mexe na palavra certa, termina parado; nada pula nem pisca.
- A câmera não entra em nada e nada é cortado pela borda sem querer.
- Na tela 3D, o vídeo mexe junto com a voz (se não, `data-media-start`).
- O `check` sem erro. O aviso "Runtime did not become render-ready within 3000ms" com o Mac ocupado não é erro:
  o render espera a cena montar.

## Se o teste falhar: em que camada está o problema

| o que aparece | camada | o que fazer |
|---|---|---|
| caixa vermelha "Cena 3D ... não montou: o WebGL não abriu" e `console_error` no `check` | WebGL / placa de vídeo | O navegador do render não abriu o WebGL. Veja no log do render a linha `GPU:` ou `browserGpuMode probe` (`hardware` é a placa; `software` é o SwiftShader). Tente `python3 build.py hf render ... --browser-gpu`. Se nem assim abrir, use a alternativa declarada (abaixo). |
| render muito lento, log diz `software gpu` | placa de vídeo | Sem placa, o 3D roda em software: funcionou igual, mas levou uns 5 min 40 s para 5 s de cena (com placa, 6 a 36 s). Force `--browser-gpu`; se a máquina não tiver placa, simplifique (menos peças, `sombras: false`, cena mais curta) ou avise quanto vai demorar. |
| `[cena 3D ...] não carreguei o motor 3D` | pacotes / carregamento | Falta `compositions/cenas/3d/motor-three.js` ou `assets/three/` no projeto (copie de `<SKILL>/modelo-projeto`). A cena precisa estar dentro de `compositions/`. |
| `erro ao montar os objetos: ...` | sólidos / modelo | Parâmetro errado, modelo `.glb` quebrado ou fora de `assets/`, texto vazio. A mensagem diz qual. |
| `a câmera entra em "..." em 2.40 s` | câmera | Afaste a câmera (`elev` maior, `dist`/`janela` maior) ou mude o caminho. |
| `o caminho passa por dentro do bloco N` | sólidos | Ponha um ponto de curva (sem nome) antes do bloco. |
| peça pula ou volta diferente ao arrastar a agulha para trás | tempo | Algo depende do quadro anterior. Tudo tem de sair de `t`. |
| dois renders dão quadros diferentes | tempo / desenho | Procure `Math.random`, `Date.now`, `performance.now` na decisão do quadro, filtro `blur()` de canvas ou sombra desfocada de CSS (`box-shadow` com desfoque) em cima do 3D: os dois variam um tico de um render para outro. O motor borra por redução e ampliação e usa sombra dura nos rótulos. |
| vídeo parado ou fora de sincronia na tela 3D | carregamento | `data-media-start` e `data-duration` do `<video>` da cena. |
| lento, mas com placa | lentidão | Menos peças, `tamanhoPx` menor no texto, sem vídeo na textura, cena mais curta. |

Simplifique sem perder a relação que precisa ser explicada: menos peças, superfícies mais simples (placa no
lugar de modelo), malha mais leve, sombra só de contato. O que não pode sair é o que a fala afirma (as peças se
separando, o caminho contornando, o volume aparecendo no giro).

## A alternativa declarada

Só quando este computador não roda o 3D de jeito nenhum. Conte o que travou, em uma linha, e ofereça uma
alternativa sempre rotulada:

- **vista explodida em 2D** (as peças em desenho plano, afastadas, com os rótulos: `references/animacao.md`);
- **desenho em corte** (o objeto cortado mostrando o interior, plano);
- **camadas planas inclinadas** (2,5D: dá profundidade, não dá volume).

Frase de exemplo: "O 3D não abriu neste computador (o navegador do render ficou sem placa de vídeo). Posso
fazer uma vista explodida em 2D com os mesmos rótulos; não é 3D. Quer assim ou prefere tentar em outra
máquina?" Se a pessoa pediu 3D com todas as letras, a troca é decisão dela. Nunca troque em silêncio e nunca
contrate render pago para contornar.

## Números medidos (Mac M4 Pro, render draft, 5 s de cena)

- Montagem de cada cena: 0,1 a 0,3 s (até 1,6 s com o Mac ocupado).
- Render com placa de vídeo: 6 a 9 s com o Mac livre (volume, explodida, caminho, celular com imagem); a tela
  com vídeo, 13 a 33 s. Com o Mac ocupado por outros renders: 19 a 36 s (o mesmo trecho sem 3D, 12 a 31 s).
- Render em software (`--no-browser-gpu`): 5 min 43 s, mesma imagem.
- Dois renders do mesmo trecho: a área do 3D sai idêntica pixel a pixel nas quatro cenas (a cena sozinha, sem
  moldura, sai byte a byte igual).

## Como funciona por dentro (para mexer no motor)

- O relógio da cena é a timeline GSAP dela (`relogio.t` vai de 0 à duração). O desenho acontece no
  `onUpdate` da timeline (estúdio tocando) e no evento `hf-seek` do HyperFrames (render e agulha). Os dois
  desenham o mesmo quadro para o mesmo `t`.
- O motor é carregado com `import()` a partir da pasta do projeto; o `window.__hf.buildReady[id]` segura o
  render até a cena montar.
- `preserveDrawingBuffer` ligado e `pixelRatio` 1: a captura pega o quadro desenhado, no tamanho do vídeo.
- Vídeo como textura: no render o HyperFrames põe um `<img class="__render_frame__">` ao lado do `<video>` com
  o quadro certo e manda desenhar de novo; a textura lê dali. O canvas da textura fica na CPU
  (`willReadFrequently`) e o fundo borrado é feito por redução e ampliação, nunca por `filter: blur()`. Os
  rótulos usam sombra dura (sem desfoque) pelo mesmo motivo.
- `window.__cena3d["<id>"]` no console do estúdio: `pronto`, `erro`, `avisos`, `montagemMs`, `placa` (o
  renderizador do WebGL) e `t` (o último tempo desenhado).
