1 Ativos, formatos de troca e orçamento de cena

A peça mais bem acabada desta oficina chegou pronta de fora. A primeira coisa que ela fez foi errar o próprio tamanho por um fator de cem.

O que muda de método aqui. Até agora toda forma da oficina nasceu de código que dava para abrir e corrigir. A partir deste ponto entra conteúdo que ninguém deste lado autorou, e entram objetos repetidos às centenas. E a frase “está rodando bem” deixa de servir para qualquer coisa.

Peça o arquivo à cena e ele entra. O carregador devolve quatro malhas e dois materiais, chamados FerroFundido e AcoUsinado. Vem junto uma animação de nome AbrirFechar, com três segundos de duração (quarenta e oito triângulos ao todo, o que é modesto de propósito). Nada falha. O console fica limpo. O quadro sai no ritmo de antes, e a morsa aparece.

Ela aparece deitada. Aparece com dezoito metros no maior lado, quando a peça que ela representa tem dezoito centímetros. E aparece pairando trinta centímetros acima do tampo, pendurada num gancho que ninguém modelou.

O veredito imediato é que o arquivo veio quebrado. Ele não veio. Passe-o por qualquer validador da especificação e ele sai aprovado, com a mesma cara de arquivo bem escrito que teria se estivesse perfeito. A especificação fixa o metro como unidade e o eixo Y como vertical; quem exportou escreveu centímetro e Z. Nenhum campo do cabeçalho tem onde registrar essa troca, porque o cabeçalho pressupõe que ela não aconteceu.

Existe uma pergunta que separa quem resolve isso em três minutos de quem passa a tarde arrastando a peça com o mouse. Quanto mede a morsa fora do computador? A resposta não está no arquivo, não tem como estar no arquivo, e é conhecida por uma pessoa só.

1.1 O arquivo está correto, e a peça está errada

Nenhum dos três desvios produz uma linha de erro.

Some-se o segundo desencontro ao primeiro. O vertical da cena é Y, o vertical de quem autorou é Z. As duas convenções existem, as duas são defensáveis, e cada família de ferramentas escolheu a sua há décadas. O arquivo carrega as coordenadas e não diz qual eixo significava “para cima” na cabeça de quem as escreveu. O terceiro é a origem. Dentro do arquivo, ela está trinta centímetros abaixo da base da peça (o número sai redondo porque veio de um plano de apoio, e não de uma medida da morsa). Isso acontece quando alguém modela sobre um plano que depois some, e é comum a ponto de não merecer nota de rodapé.

Olhe outra vez para a tela. Os três desvios juntos produzem exatamente o que ela mostra: uma morsa deitada, gigante e flutuando. E produzem isso sem uma linha de erro, sem aviso amarelo, sem exceção capturada. Para qualquer verificação automática que exista no projeto, a importação funcionou.

Antes de seguir, responda de cabeça. Se o carregamento foi bem-sucedido nos três casos, o que exatamente vai acusar o problema? Guarde a sua resposta: ela decide se o próximo ativo vai custar três minutos ou uma tarde.

Compare este caso com o do arquivo que falha de verdade. Um arquivo truncado no meio do download não carrega. A rotina devolve exceção com mensagem. Um arquivo com extensão errada não é reconhecido. O carregador diz isso em voz alta. Os dois doem imediatamente, e doem no lugar certo. O arquivo com convenção divergente atravessa a cadeia de verificação inteira intacto e só se manifesta como imagem.

Quem exporta em centímetro raramente está errando por descuido. O programa de modelagem dele trabalha em centímetro porque a peça cabe numa bancada, e um mundo medido em metros, com objetos de dois centésimos, fica desconfortável de manipular. A escolha faz todo sentido lá. Ela só não sobrevive à viagem.

flowchart LR
    A["programa de autoria<br/>trabalha em centimetro<br/>vertical em Z"]
    B["arquivo em formato<br/>de troca aberto"]
    C["cena em execucao<br/>metro, vertical em Y"]
    A -->|"geometria"| B
    A -->|"materiais"| B
    A -->|"animacoes"| B
    B --> C
    D["a unidade escolhida"]
    E["qual eixo era o vertical"]
    F["quanto a peca mede<br/>fora do computador"]
    A -.->|"nao tem campo"| D
    A -.->|"nao tem campo"| E
    A -.->|"ninguem sabe la"| F
    D -.-> G["fica para tras"]
    E -.-> G
    F -.-> G
Figura 1: O que o formato de troca leva de um lado ao outro, e a informação que fica para trás.

1.1.1 O contrato público e o buraco dentro dele

NotaDefinição — Formato de troca aberto

Um formato de troca aberto é um arquivo com especificação pública que transporta, de um programa de autoria até um ambiente em execução, a geometria, os materiais e as animações de um objeto.

A especificação pode fixar convenções — a unidade em que os números são lidos e qual eixo é o vertical — e é isso que ela faz. O que ela não pode fazer é obrigar quem exporta a obedecê-las.

Daí decorre a única coisa que o formato não transporta: o tamanho da peça no mundo. Um arquivo escrito noutra unidade continua formalmente válido, nenhum campo do cabeçalho denuncia a troca, e o número de metros que a peça mede fora do computador é conhecido por uma pessoa, não pelo arquivo.

Perceba o tamanho do estrago no menor exemplo possível. Um cubo de um metro de aresta, exportado em centímetro, guarda cem em cada direção — e guarda cem porque cem é a medida certa na unidade de quem exportou. Quem lê aplica a especificação e monta um cubo de cem metros. Qual dos dois lados você mandaria corrigir? Os dois fizeram a coisa certa, e o resultado é um cubo do tamanho de um prédio de trinta andares. Veja bem o que a definição não exige. Ela não exige que o arquivo esteja mal formado, não exige que o exportador tenha se enganado, e não exige sequer que exista uma versão correta em algum lugar. O arquivo da morsa é o melhor arquivo possível dentro do que o formato permite escrever. A informação que falta não cabe nele.

Isso muda o vocabulário com que se fala do problema. A mudança tem consequência prática. Um arquivo com defeito se devolve para a origem. Um arquivo com convenção diferente se converte na chegada. A conversão é trabalho de quem monta a cena, faz-se uma vez por ativo, e sobrevive a qualquer troca de fornecedor.

1.1.2 Quatro escolhas escritas uma vez

NotaDefinição — Convenção da cena

A convenção da cena é o conjunto de escolhas que todo objeto da cena obedece, escrito uma vez e em um lugar só: a unidade de medida, qual eixo é o vertical, onde fica a origem de cada peça e qual sentido é a frente.

Ela se declara antes, em vez de se descobrir no que chega. Uma cena sem convenção escrita não tem contra o que acusar divergência: cada objeto importado passa a trazer a sua, cada ajuste é feito no olho, e não sobra ninguém capaz de dizer qual dos dois lados está errado.

O que a convenção converte é o trabalho de reconciliar. Com ela, a diferença entre o que chegou e o que a cena espera é uma conta; sem ela, é uma negociação, e ela se repete em cada objeto novo.

Na oficina isso são quatro linhas, guardadas num lugar em que qualquer pessoa tropeça nelas. Elas são a primeira coisa que se escreve, antes de o primeiro ativo chegar.

a convenção declarada, em src/bancada/content/ativos.ts
export const CONVENCAO_DA_CENA = {
  unidade: 'metro',
  eixoVertical: 'Y',
  origemDaPeca: 'no ponto em que ela toca a superfície de apoio',
  frente: '+Z',
} as const;

Repare no que essas linhas não fazem. Quatro campos, nenhuma lógica, nenhum comportamento, uma constante que o programa quase não lê. O valor dela está em existir escrita quando alguém precisa decidir se a peça que chegou está certa. Imagine a mesma oficina sem elas, e acompanhe a discussão. Chega o primeiro ativo, alguém acha que ficou grande e divide por cem. Chega o segundo, alguém acha que ficou pequeno e multiplica por dez. Chega o terceiro. Agora a discussão é sobre qual dos dois anteriores serve de referência. Ninguém está errado, porque não existe nada contra o que estar errado. A cena vira uma colcha de escalas que só quem a costurou consegue interpretar.

A origem da peça é o campo que as pessoas menos escrevem. Ele merece um parágrafo. Aqui ela fica no ponto em que a peça toca a superfície de apoio. Essa escolha faz com que assentar um objeto sobre o tampo seja atribuir uma altura só, sem correção por peça. Fosse a origem no centro geométrico, cada objeto exigiria meia altura de compensação — e meia altura de um objeto importado é justamente o número que ninguém sabe de cor.

1.2 Metade se mede, metade alguém escreve

Metade dos parâmetros sai de uma divisão. A outra metade só existe se alguém a escrever.

NotaDefinição — Ajuste de importação

O ajuste de importação é a transformação aplicada ao que chegou para trazê-lo à convenção da cena, e ele tem duas metades de naturezas diferentes.

A metade medida sai da caixa envolvente do objeto que chegou, por divisão e por subtração: o fator de escala e o assentamento da base. Ela não tem como estar errada por distração, e trocar o ativo por outro a recalcula sozinha.

A metade declarada é o que o arquivo não carrega e não tem onde carregar: qual eixo o autor usou como vertical e quanto a peça mede no mundo. Ela é escrita por quem conhece a peça.

A fronteira entre as duas é o que separa importar de arrastar. Um fator de escala digitado é a metade medida transformada em declaração, e funciona até chegar um ativo em outra unidade — quando falha, falha em silêncio, e o conserto é feito num terceiro lugar.

A caixa envolvente é um instrumento honesto e limitado. Quanto ela sabe, exatamente? Ela percorre os vértices. Guarda o menor e o maior valor em cada direção, e devolve três larguras. No caso da morsa, devolve 18,00, 12,00 e 17,00. Ela não tem opinião nenhuma sobre o significado desses três números.

Pergunte a ela se dezoito são centímetros e ela não responde. Pergunte se a peça está deitada e ela também não responde, porque uma peça deitada tem caixa envolvente tão legítima quanto uma peça em pé. Tudo o que ela sabe é onde a geometria começa e onde termina. O que ela permite, então, é uma divisão. Declare que o maior lado real da peça mede 0,18 metro. A caixa mede 18,00 no maior lado, com a peça já em pé, e o fator sai da razão entre os dois: 0,0100. Esse número não foi escolhido por ninguém. Ele foi produzido. E produzido de um jeito que se refaz sozinho quando o ativo muda.

O mesmo vale para o assentamento. Depois de girar e escalar, a caixa envolvente diz onde está a face inferior da peça em relação à origem dela. A diferença entre esse valor e zero é o deslocamento a corrigir. Na morsa ele dá 0,30 metro. Nenhum dos dois números foi digitado.

1.2.1 O número que só uma pessoa sabe

Do outro lado ficam duas informações que a caixa envolvente jamais produzirá, por mais vértices que percorra. Nenhuma das duas se deduz da geometria que chegou.

o que quem importa precisa declarar, em src/bancada/content/ativos.ts
/**
 * O que quem importa precisa declarar, porque o arquivo não responde.
 *
 * `maiorLadoRealM` é a medida do objeto no mundo, em metros, obtida por quem
 * conhece a peça. Dela sai o fator de escala, por divisão contra o que foi
 * medido no arquivo — e é por isso que o fator não é digitado: digitar 0.01
 * funciona até o dia em que chega um ativo em milímetro.
 */
export interface AjusteDeImportacao {
  readonly maiorLadoRealM: number;

O campo se chama maiorLadoRealM, com a terminação marcando a unidade, e o nome dele carrega a decisão inteira. Ele pede uma medida do mundo, em metros, e não um fator de conversão. A diferença entre as duas coisas parece de estilo e é de arquitetura. Suponha que o campo pedisse o fator. O que mudaria hoje? Quem importa a morsa escreveria 0.01, porque o arquivo veio em centímetro, e o resultado seria idêntico ao de agora. A cena ficaria correta, o laudo ficaria correto, e ninguém notaria diferença por meses. O defeito só nasce no dia em que chegar um ativo escrito em milímetro.

Nesse dia, o 0.01 continua sendo um número perfeitamente válido. Ele apenas descreve o arquivo anterior. A peça nova entra dez vezes maior do que devia, alguém a vê enorme, e a correção mais provável é dividir a escala daquele objeto por dez, noutro lugar do código. A partir daí existem dois fatores de conversão espalhados, sem parentesco visível entre eles, e o próximo ativo herda a confusão.

Repare que o campo declarado é um único número por ativo, e que ele é o mesmo número que alguém consultaria num catálogo de fornecedor. Isso o torna conferível por quem não escreveu o código. Um fator de conversão não tem essa propriedade: ele descreve o arquivo em vez da peça, e conferi-lo exige abrir o arquivo com uma ferramenta que saiba lê-lo.

1.2.2 Girar, escalar, assentar

Os três ajustes acontecem numa ordem fixa, e a ordem é consequência da geometria da caixa envolvente. O giro vem primeiro. É ele que decide o que a caixa vai medir depois. A peça chega com o vertical em Z e precisa terminar com ele em Y, o que se faz com noventa graus negativos em torno de X. Enquanto esse giro não acontece, a caixa descreve o objeto deitado.

A escala vem em seguida, porque o fator depende de qual dimensão é o maior lado. Numa peça deitada, o maior lado da caixa é medido numa direção; na mesma peça em pé, noutra. Na morsa, a caixa passa a ter 17,00 de altura depois do giro, o assentamento passa a olhar para outra face, e o número que ele mede muda junto.

flowchart TD
    A["o que chegou<br/>18,00 x 12,00 x 17,00<br/>unidades do arquivo"]
    B["girar<br/>-90 graus em X"]
    C["medir a caixa envolvente<br/>com a peca ja em pe"]
    D["escalar<br/>fator medido: 0,0100"]
    E["assentar<br/>residuo de 0,30 m na base"]
    F["18,0 x 17,0 x 12,0 cm<br/>na convencao da cena"]
    A --> B --> C --> D --> E --> F
    E -. "trocar esta ordem<br/>assenta pela face<br/>que vira lateral" .-> B
Figura 2: Os três ajustes em sequência, com o valor que cada um produz neste ativo.

Noventa graus negativos é o giro que leva o vertical de Z para Y, e metade das tentativas começa pelo sinal oposto. Uma rotação em torno de X move os eixos Y e Z e deixa X onde está (é a convenção de mão direita, a mesma que a biblioteca usa em toda parte). Com o sinal negativo, o que apontava para o Z positivo passa a apontar para o Y positivo. Com o sinal positivo, o mesmo eixo termina apontando para o Y negativo, e a peça fica de cabeça para baixo.

A morsa invertida tem um sintoma disfarçado. Olhe o que acontece com um objeto aproximadamente simétrico em torno do vertical: ele fica parecido consigo mesmo invertido, visto do ângulo em que a cena nasceu. A base larga vira topo largo. O mordente aponta para o tampo em vez do teto, e a silhueta continua reconhecível como uma morsa. Trocar o sinal e olhar é a conferência inteira, e ela leva alguns segundos.

Inverter os dois primeiros passos produz um resultado específico, fácil de reproduzir sem perceber. Assentar antes de girar significa medir a face inferior da peça deitada, que é uma das laterais da peça em pé. O deslocamento calculado a partir dela move o objeto pela dimensão errada, e o giro posterior carrega esse deslocamento junto, numa direção que não era a pretendida.

O resultado visível é um objeto flutuando acima da superfície de apoio ou enterrado nela. A altura do erro é a diferença entre duas dimensões da peça. Na morsa daria alguns centímetros: pequena demais para chamar atenção, grande demais para não atrapalhar quando alguém tentar apanhar a peça.

Qual das três ordens admite troca

Escalar antes ou depois de girar dá no mesmo, porque a escala é uniforme nas três direções e o fator sai igual nos dois casos. Quem manda é a ordem entre assentamento e giro, a única das três que não admite troca. E o pior dela é o que ela faz com o registro da importação: todos os números saem corretos, porque cada um deles descreve fielmente uma operação que de fato aconteceu.

1.3 Sete campos que outra pessoa consegue ler

Toda importação devolve um registro do que aconteceu. Aí está a peça que separa importar de arrastar. Ele nasce dentro da mesma rotina que aplica a transformação, e os valores registrados são os que a rotina de fato aplicou. Não os que alguém pretendia aplicar. Essa distinção parece pequena e é o que dá valor à folha inteira.

o laudo da importação, em src/bancada/content/ativos.ts
export interface DiagnosticoDeImportacao {
  readonly arquivo: string;
  readonly comoChegou: DimensoesM;
  readonly comoFicou: DimensoesM;
  readonly fatorDeEscala: number;
  readonly giroEmXGraus: number;
  readonly deslocamentoCorrigidoM: Vector3;
  readonly divergencias: readonly string[];
}

Os dois primeiros campos são o par que sustenta a conferência. O campo comoChegou guarda as dimensões no sistema do arquivo, e na morsa registra 18,00 por 12,00 por 17,00 unidades. O campo comoFicou guarda as dimensões na convenção da cena, e registra 18,0 por 17,0 por 12,0 centímetros. Ler as duas linhas em sequência é ver a viagem inteira num relance. Os três campos seguintes registram o que foi aplicado: o fator de 0,0100, o giro de noventa graus negativos em X e o deslocamento corrigido. O sétimo é a lista de divergências. É ele que transforma o registro em instrumento.

flowchart TD
    L["o laudo da importacao"]
    L --> D["cinco campos que DESCREVEM"]
    L --> A["dois campos que permitem JULGAR"]
    D --> D1["arquivo"]
    D --> D2["como chegou<br/>18,00 x 12,00 x 17,00 unidades"]
    D --> D3["como ficou<br/>18,0 x 17,0 x 12,0 cm"]
    D --> D4["fator aplicado: 0,0100"]
    D --> D5["giro aplicado: -90 graus em X"]
    A --> A1["deslocamento corrigido: 0,30 m"]
    A --> A2["lista de divergencias<br/>tres linhas nesta peca"]
    A2 --> N["quando nada diverge,<br/>a lista diz isso<br/>em vez de sair vazia"]
Figura 3: Cinco campos descrevem a viagem; os dois últimos permitem julgá-la.

Note a diferença entre esses dois grupos. Os cinco primeiros campos descrevem, e valem tanto para um ativo perfeito quanto para um ativo torto. A lista acusa. Ela só tem conteúdo quando alguma coisa saiu do que a convenção esperava. Um relatório que apenas descrevesse obrigaria quem lê a fazer a comparação de cabeça, toda vez, para cada ativo.

No caso da morsa a lista sai com três linhas. A primeira diz que o arquivo mede 18.00 para uma peça de 0.18 m, o que dá 100 unidades por metro. A segunda diz que a peça foi girada −90 graus em X. A terceira diz que a peça vinha deslocada 0.30 m da própria base. Repare na frase sobre as cem unidades por metro. Ela não afirma que o arquivo está em centímetro; afirma o que foi medido. Quem lê conclui o centímetro sozinho, e conclui com a evidência na mão. Chegue amanhã um arquivo com mil unidades por metro, e a mesma linha continua dizendo a verdade sem manutenção nenhuma.

1.3.1 Por que 0,998 não entra na lista

Os limiares são largos de propósito. Um fator entre 0,5 e 2,0 passa sem comentário; um deslocamento residual acima de um centímetro é acusado. Pouco rigor? É exatamente o contrário.

Pense no que aconteceria com limiares apertados. Exportadores produzem ruído numérico, e uma peça que deveria medir 0,180 sai medindo 0,17964, com o fator resultante em 0,998. Uma lista que reclamasse disso teria uma linha em toda importação, todas irrelevantes. E a linha que de fato importa? A troca de unidade erra por cem vezes. Ela apareceria no meio das outras, com o mesmo destaque e a mesma pressa de ser ignorada.

O que a lista existe para pegar erra por ordens de grandeza. Centímetro contra metro é cem. Milímetro contra metro é mil. Nenhum desses desvios chega perto da faixa entre 0,5 e 2, e nenhum ruído de exportação chega a sair dela. Os limiares largos separam as duas populações com folga em vez de tentar cortá-las no fio.

Quando nada diverge acima da tolerância, a lista diz isso com todas as letras em vez de sair vazia. Uma lista vazia é ambígua: pode significar que nada divergiu, ou que ninguém examinou. Uma linha afirmando que a peça chegou dentro da convenção fecha a ambiguidade e custa uma linha de código.

1.3.2 O que mais veio dentro do arquivo

A morsa traz uma animação, AbrirFechar, com três segundos de duração. Ela move o mordente móvel. É conteúdo autorado fora do projeto tanto quanto a geometria. Quem faz essa animação andar? Existem dois candidatos, os dois disponíveis e os dois plausíveis. O primeiro é um relógio próprio, que a biblioteca oferece pronto e que conta o tempo real desde que a peça entrou. O segundo é o intervalo que o laço já calcula para mover o resto da cena — o mesmo que gira o eixo do mecanismo e atualiza tudo o mais.

A morsa usa o segundo, e a razão aparece quando dois computadores são comparados. Com relógio próprio, a peça andaria à velocidade dela e a cena andaria à do laço; numa máquina que desenha mais rápido, a diferença entre as duas mudaria. A morsa passaria a viver num tempo ligeiramente diferente do da oficina.

Esse defeito tem um perfil desconfortável. Ele não aparece durante o desenvolvimento, porque o desenvolvimento acontece numa máquina só. Ele aparece quando o ambiente é aberto em dois aparelhos lado a lado, o que costuma acontecer no dia da demonstração. E não deixa rastro no laudo, porque o laudo examina geometria.

Quando o ativo simplesmente não chega

Um ativo externo depende de rede, de caminho e de servidor, e qualquer um dos três pode falhar. A oficina trata isso como falha visível: a cena continua de pé, sem a morsa, e o painel escreve que o ativo não pôde ser trazido. O silêncio seria pior justamente porque a cena continua funcionando — ninguém nota a ausência de uma peça entre outras vinte.

1.4 Duas técnicas, duas colunas

Instanciar e simplificar aparecem juntos em toda conversa sobre desempenho, e atacam problemas que não se tocam.

NotaDefinição — Chamada de desenho

Uma chamada de desenho é uma conversa entre o programa e a placa de vídeo para desenhar um objeto: a placa recebe qual forma usar, com qual material e em que posição, e só então desenha.

O custo dessa conversa é fixo por objeto e independe do tamanho dele. Um objeto minúsculo custa a mesma preparação que um objeto grande, e é por isso que uma cena de mil peças pequenas pode engasgar sem que a contagem de triângulos assuste ninguém.

Chamada de desenho e triângulo são unidades de contas diferentes. As duas se pagam no mesmo envelope, e nenhuma das duas se lê na outra.

O menor exemplo concreto cabe numa comparação de duas cenas. A primeira tem um objeto com dez mil triângulos. A segunda tem mil objetos com dez triângulos cada, o que dá dez mil triângulos também. As duas cenas são idênticas na coluna dos triângulos. Na coluna das chamadas, um fator de mil as separa. É a segunda que engasga. A definição não exige que a chamada seja cara em termos absolutos. Ela é barata; é a repetição dela que pesa. Também não exige que o custo esteja na placa, porque boa parte dele fica do lado do programa, preparando estado, verificando material e enviando matrizes.

flowchart TD
    Q["o quadro estourou"]
    Q --> CH["coluna das<br/>chamadas de desenho"]
    Q --> TR["coluna dos<br/>triangulos"]
    CH --> I["instanciar"]
    TR --> N["nivel de detalhe"]
    I --> I1["206 chamadas passam a 1"]
    I --> I2["os 4.944 triangulos<br/>continuam 4.944"]
    N --> N1["224 triangulos passam a 82"]
    N --> N2["a chamada por lote<br/>continua uma"]
Figura 4: Cada técnica move uma coluna do relatório e deixa a outra intacta.

Olha só o caso mais comum: alguém aplica as duas técnicas com esforço genuíno e no alvo errado. Onde ele errou? Essa pessoa olha para a borda de parafusos, vê muitos objetos e conclui que a cena tem geometria demais. Simplifica o parafuso, que já tinha vinte e quatro triângulos, e o transforma num parafuso de dezoito. Depois olha para a morsa importada, vê uma peça complexa, e a instancia: uma peça, um lote de uma cópia.

Os dois trabalhos foram feitos com competência. O primeiro cortou seis triângulos por cabeça em duzentas e seis cabeças, o que é respeitável e some no ruído. O segundo trocou uma chamada de desenho por uma chamada de desenho. E as duzentas e seis conversas com a placa, que eram o problema real, continuam exatamente onde estavam. O diagnóstico correto exige olhar para qual coluna do relatório está grande, e não para qual objeto parece complicado. Complexidade aparente e custo não andam juntos. Uma engrenagem de catorze dentes parece cara e é uma chamada só; duzentos e seis parafusos cabem na unha, parecem baratos, e são duzentas e seis conversas.

1.4.1 Repetição tratada como repetição

O tampo da bancada mede 1,4 por 0,8 metro, e os parafusos ficam a cada dois centímetros ao longo do perímetro. Desse arranjo saem duzentas e seis cabeças. O número não aparece em lugar nenhum do código. O que aparece é a rotina que dispõe posições ao longo da borda de um retângulo, recebendo as dimensões do tampo e o espaçamento desejado. Essa distinção decide o que acontece quando alguém muda a bancada. Com o número digitado, trocar o tampo por um maior deixaria os parafusos agrupados num pedaço da borda, e alguém precisaria recontar. Com a conta, o tampo maior traz mais parafusos sozinho.

Cada cabeça é uma peça sextavada de vinte e quatro triângulos. É a cabeça e nada mais. Na prática, o corpo do parafuso está dentro da madeira, e modelar o que ninguém vê é orçamento gasto sem contrapartida. Vinte e quatro vezes duzentas e seis dá 4.944 triângulos na borda inteira. Fixe esse número antes de a técnica entrar, porque ele é o que não vai mudar.

NotaDefinição — Instanciação

Instanciar é desenhar muitas cópias de uma mesma forma numa chamada de desenho só: a placa recebe a forma uma vez e, em seguida, a lista das posições de todas as cópias.

O aviso de que aquelas posições não mudam mais é o que autoriza a placa a guardar o lote e não voltar a tocá-lo. A matriz de cada cópia é escrita uma vez, na montagem, e não a cada quadro.

O que a instanciação move é a coluna das chamadas. A contagem de triângulos não muda: eles continuam sendo os mesmos, um por um, e a placa os desenha igual. Instanciar não é sobre triângulo, e esperar que o número de triângulos caia é o modo mais comum de concluir que a técnica não funcionou.

a montagem do lote, em src/bancada/content/repeticao.ts
/**
 * Monta um lote instanciado a partir de uma malha e de uma lista de posições.
 *
 * A matriz de cada cópia é escrita uma vez, na montagem, e não a cada quadro. O
 * aviso de que a matriz não muda mais é o que autoriza a biblioteca a mandar o
 * lote inteiro para a placa e não voltar a tocá-lo — e é ele que transforma
 * duzentas e vinte chamadas em uma.
 */
export function montarLoteInstanciado(

A rotina recebe uma malha, um material e uma lista de posições, e devolve um objeto só. Cada posição vira uma matriz, e cada matriz é escrita na tabela do lote, uma vez, na montagem da cena. O detalhe que sustenta o ganho está no aviso que acompanha a tabela. Ao dizer que aquelas matrizes não vão mudar, o programa autoriza a biblioteca a enviar o lote e não voltar a tocá-lo em quadro nenhum.

Isso põe um limite claro no alcance da técnica. Um lote de objetos que se movem sozinhos a cada quadro perde a maior parte da vantagem, porque a tabela volta a ser escrita e enviada. Os parafusos da borda são candidatos ideais justamente por serem entediantes: nascem parafusados e ficam onde nasceram até alguém desmontar a bancada.

Com o lote montado, o relatório muda numa linha e conserva a outra. As duzentas e seis chamadas passam a uma. A contagem de triângulos continua em 4.944. A expectativa contrária é comum, e produz uma conclusão errada. Alguém aplica a instanciação, olha o contador de triângulos, vê o mesmo número de antes e conclui que a técnica não serviu. Ela serviu; apenas moveu a coluna que estava fora do campo de visão de quem olhava.

1.4.2 A mesma malha, duas decisões opostas

NotaDefinição — Nível de detalhe

Um nível de detalhe é uma segunda versão da mesma malha, com menos triângulos, exibida no lugar da original quando o objeto está longe o bastante para que a diferença não chegue ao olho.

O critério é a distância, e não o objeto. A mesma peça pode receber nível de detalhe num lugar da cena e não receber em outro, e simplificar o que fica sempre perto do olho é trabalho jogado fora.

A simplificação tem um pré-requisito e uma conferência, e os dois são frequentemente omitidos. O pré-requisito é unir os vértices coincidentes: uma malha que duplica vértice em cada face, para ter aresta viva, é lida pelo algoritmo como um monte de triângulos soltos, sem aresta alguma para colapsar. A conferência é do resultado: a ferramenta pode devolver malha vazia ou com coordenada inválida, e uma peça que some da cena é defeito pior do que um nível distante mais pesado que o ideal. Quando o resultado não passa, a malha original fica no lugar e a observação vai para o relatório.

Depois de simplificar, as normais são recalculadas: os triângulos que sobraram têm outra inclinação, e manter a normal antiga produz iluminação que não corresponde à forma — um defeito que aparece como mancha, e não como buraco.

A prateleira ao fundo da oficina guarda vinte e quatro peças de reposição. Elas são a mesma malha da engrenagem que está sobre a bancada, e essa identidade é o argumento inteiro. A engrenagem da bancada fica a meio metro do olho e não recebe nível de detalhe nenhum. As vinte e quatro da prateleira ficam ao fundo, e recebem. Nada mudou no objeto entre uma frase e a outra. Mudou a distância. Com ela mudou a quantidade de pixels que cada triângulo ocupa na tela. A meio metro, os flancos dos catorze dentes se distinguem um do outro; ao fundo, a peça inteira cabe num punhado de pixels.

A ferramenta corta a malha de 224 para 82 triângulos, e de 338 para 169 vértices, o que dá 63% de redução. Nas vinte e quatro peças da prateleira, isso são 5.376 triângulos que viram 1.968. A troca entre os dois lotes acontece a 2,2 metros da câmera ao centro da prateleira, e ela vale para o lote inteiro, não para cada peça.

Há um pré-requisito que a oficina paga aqui e que foi decidido lá atrás, por outro motivo. A malha do projeto duplica vértice em toda face, para que os cantos das peças fiquem vivos em vez de macios.

o acumulador de vértices, em src/bancada/content/malha.ts
  vertice(
  x: number,
  y: number,
  z: number,
  nx: number,
  ny: number,
  nz: number,
  u: number,
  v: number,
): number {
  this.posicoes.push(x, y, z);
  this.normais.push(nx, ny, nz);
  this.uvs.push(u, v);
  return this.posicoes.length / 3 - 1;
}

Cada chamada dessa função registra um vértice com posição, normal e coordenada de textura próprias. Duas faces que se encontram numa aresta viva chamam a função duas vezes para o mesmo ponto do espaço, com normais diferentes. Do ponto de vista da geometria da peça, é um ponto; do ponto de vista da lista, são dois.

Um algoritmo de simplificação trabalha colapsando arestas: escolhe uma aresta, funde os dois vértices dela num só, e remove os triângulos que degeneraram. Para descobrir quais arestas existem, ele olha quais triângulos compartilham vértices. Numa malha com vértices duplicados por face, nenhum triângulo compartilha vértice com nenhum outro. O que o algoritmo enxerga, então, é um monte de triângulos soltos flutuando lado a lado. Não há aresta para colapsar, porque no modelo dele nada se toca. Ele devolve a malha quase intacta, sem erro e sem mensagem, e quem confere pelo contador conclui que a peça já estava simplificada ao máximo.

O conserto é unir os vértices coincidentes antes de simplificar, o que devolve ao algoritmo uma superfície costurada. Isso custa a aresta viva, porque o ponto unificado passa a ter uma normal só para as duas faces. No nível distante, essa perda é exatamente o que se quer pagar: a três metros, canto duro e canto macio são o mesmo punhado de pixels.

Duas conferências que quase ninguém faz. Depois de simplificar, a normal antiga descreve uma superfície que deixou de existir. A iluminação passa a errar por pouco em cada ponto. O sintoma é mancha, e mancha manda quem procura direto para o material e para a luz, onde não há nada errado. E a ferramenta pode devolver malha vazia ou com coordenada não finita: aí a peça some da cena, e some junto com ela a linha do relatório que a mencionaria.

Existe ainda um modo de anular as duas técnicas ao mesmo tempo, e ele vem de uma escolha razoável. As bibliotecas oferecem um mecanismo pronto de nível de detalhe que alterna a malha objeto por objeto, conforme cada um se afasta. Aplicado a um lote instanciado, ele desmonta o lote: cada peça passa a decidir sozinha qual malha usar, e cada decisão custa uma chamada de desenho. No fim das contas, o ganho de uma técnica paga a perda da outra. O relatório mostra os mesmos números de antes, e quem lê conclui que nenhuma das duas serviu. Por isso, aqui, cada nível é um lote inteiro, e a troca liga um e desliga o outro.

1.5 Duas contas com o mesmo nome, e o envelope que as julga

Dois números de triângulos na mesma folha. Por que eles quase nunca batem?

NotaDefinição — Censo da cena e leitura do quadro

O censo é estático: percorre a árvore da cena e soma o que existe, esteja ou não à vista. Responde quanto esta cena pesa.

A leitura é dinâmica: conta o que a placa desenhou no quadro que acabou de sair. Responde quanto custou o que estava no campo de visão.

A diferença entre os dois mede o descarte do que ficou fora do enquadramento, e nunca um erro de contagem. Um censo alto com leitura baixa descreve uma cena grande vista por um buraco de fechadura, e vira problema no instante em que alguém gira a cabeça — o que, dentro de um visor, acontece o tempo todo.

O censo conta o lote instanciado como uma chamada e como o produto de triângulos por instância. Somar tudo num número só esconderia exatamente a decisão que se está tomando.

Acontece que o censo percorre a árvore a partir da raiz e soma. Ele não sabe onde está a câmera, e não perguntaria mesmo que soubesse. A resposta dele é a mesma com a câmera na bancada, na prateleira ou apontada para a parede vazia atrás. Repara na consequência: esse número não muda enquanto ninguém acrescentar conteúdo. A leitura vem do laço, imediatamente depois de a placa terminar o quadro. Ela descreve um instante, e o instante seguinte pode ser outro. Com a câmera junto à bancada, a prateleira do fundo fica fora do enquadramento: ela continua inteira no censo e sai da leitura.

a forma do censo, em src/bancada/content/medicao.ts
export interface Censo {
  readonly malhas: number;
  readonly lotesInstanciados: number;
  readonly instancias: number;
  readonly triangulos: number;
  readonly materiaisDistintos: number;
  /** Piso de chamadas de desenho: uma por malha, uma por lote — não uma por instância. */
  readonly chamadasPrevistas: number;
}

O censo devolve seis números, e a escolha deles carrega a decisão do módulo. Malhas soltas e lotes instanciados são contados em campos separados, e o número de instâncias tem campo próprio. As chamadas previstas somam uma por malha e uma por lote, nunca uma por instância. Somar tudo num número só seria mais simples de ler e apagaria a informação inteira. Um total que contasse cada parafuso como uma chamada mostraria a cena antes da instanciação; um total que ignorasse os parafusos mostraria uma cena que não existe. A separação é o que permite ver, na mesma folha, a borda com 4.944 triângulos e a chamada única que os desenha.

flowchart TD
    C["censo da cena<br/>estatico, percorre a arvore"]
    L["leitura do quadro<br/>dinamica, le o quadro que saiu"]
    C --> P1["quanto esta cena pesa"]
    L --> P2["quanto custou o que estava<br/>no campo de visao"]
    P1 --> DIF["a diferenca entre os dois"]
    P2 --> DIF
    DIF --> S["descarte por enquadramento<br/>nunca erro de contagem"]
    S --> J["camera parada na janela:<br/>numero fixo e confortavel"]
    S --> V["cabeca girando no visor:<br/>o que estava fora entra"]
Figura 5: Duas contas sobre a mesma cena, e o que a diferença entre elas significa.

Na janela do computador, com a câmera apontada para a bancada, a prateleira fica fora do enquadramento a maior parte do tempo. O conteúdo dela é pago em memória e não é pago por quadro. Alguém que meça só nessa posição colhe números confortáveis, conclui que a cena tem folga generosa, e a conclusão vale exatamente para aquele ângulo.

Dentro de um visor, a mesma cena se comporta de outro jeito. A cabeça gira o tempo todo, sem que ninguém decida girá-la, e cada giro traz conteúdo novo para o enquadramento. A transição entre olhar para a bancada e olhar para o fundo leva uma fração de segundo. O custo por quadro muda dentro dessa fração. É também o instante em que o corpo está em movimento, e mais sensível ao desencontro.

A defesa é medir em mais de uma posição de câmera e registrar as duas leituras. Mede-se junto à bancada, mede-se junto à prateleira, e comparam-se as duas folhas contra o mesmo censo. Duas leituras idênticas sobre uma cena espalhada indicam que o descarte por enquadramento não está acontecendo. O lugar de procurar é a hierarquia: um objeto pendurado no galho errado arrasta os filhos dele para dentro do campo de visão.

1.5.1 O teto declarado antes da despesa

Cada quadro é um envelope de onze milissegundos, e o que não couber nele não fica para depois: some.

Quando essa frase foi escrita, ela descrevia uma restrição abstrata. A oficina tinha um tampo, um suporte, cinco peças e um painel, tudo em caixas e cilindros, com quatrocentos e quarenta triângulos ao todo. O teto era um número sem adversário. Agora ele tem três adversários concretos. Uma peça importada com quatro malhas e animação própria, uma borda com 4.944 triângulos numa chamada, e uma prateleira com 5.376 triângulos que caem para 1.968 quando a câmera se afasta. Cada um deles é uma decisão que alguém tomou. Cada decisão gastou parte do envelope.

A ordem em que essas duas coisas aconteceram é o que faz diferença. O teto veio primeiro, e a despesa veio depois. Um teto declarado depois da cena montada tem outro nome: ele é um laudo do que já foi gasto. Informa que a cena está pesada e não oferece critério algum para decidir o que cortar, porque a essa altura cortar significa remodelar peças que já estão prontas.

os dois tetos, em src/bancada/core/orcamento.ts
/** Teto do desktop do laboratório, em milissegundos: sessenta imagens por segundo. */
export const TETO_DESKTOP_MS: number = 16.7;

/** Teto do visor, em milissegundos: noventa imagens por segundo. */
export const TETO_VISOR_MS: number = 11.1;

O código declara dois números, e nenhum dos dois é escolha de última hora. Onze vírgula um milissegundos para o visor, dezesseis vírgula sete para o computador de mesa. O teto adotado para esta cena é o do visor. A mesma cena sobe nos dois aparelhos pela mesma URL, e um conteúdo orçado pelo aparelho mais folgado estoura no outro.

A escolha custa alguma coisa, e os dois lados dela precisam estar escritos. Orçar pelo visor significa que a versão em janela fica menos bonita do que a máquina aguentaria. Orçar pelo computador de mesa significa que a versão no visor estoura, e no visor o estouro produz desconforto físico em vez de uma animação menos lisa. Eu prefiro pagar em aparência. A decisão fica escrita ao lado do teto adotado.

O instrumento tem um limite de alcance, e reconhecê-lo evita procurar dentro dele uma resposta que ele não guarda. Ele mede tempo: diz quantos milissegundos o quadro levou e qual foi o pior da janela observada. Ele não diz onde esses milissegundos foram gastos. Um quadro de vinte milissegundos pode ter gasto quinze desenhando, quinze percorrendo a árvore da cena ou quinze esperando o navegador terminar outra coisa. O número é o mesmo nos três casos.

Localizar o gasto é trabalho das ferramentas de perfilamento do navegador, que registram a pilha de chamadas e mostram quanto tempo cada função consumiu. Elas respondem onde, e respondem bem, mas rodam na janela do computador, fora da sessão imersiva, com o depurador aberto. O painel dentro da cena responde se o quadro está estourando e quando, inclusive de dentro do visor, com a cabeça em movimento e a cena no estado real de uso.

1.6 Nenhum número viaja sozinho

Duas pessoas competentes, olhando medições verdadeiras do mesmo ambiente, chegam a decisões opostas quando os aparelhos diferem. Esse é o preço de um número que circula sozinho.

NotaDefinição — Máquina de referência

A máquina de referência é o aparelho contra o qual a cena é orçada, e ela é a mais modesta do parque previsto — não a de quem escreveu o ambiente.

Um número de desempenho sem a máquina ao lado não permite a ninguém repetir a conta. “Roda bem” é uma frase sem sujeito; a mesma cena, no mesmo estado, produz números diferentes em aparelhos diferentes, e a decisão que se toma a partir de cada um é oposta.

Medir na máquina mais forte disponível produz um teto que as outras não alcançam, e o resultado prático é o ambiente estourar justamente nos computadores em que ele vai ser apresentado.

Quando o aparelho não informa o que se pediu — o nome da placa, por exemplo, que o navegador frequentemente recusa dizer —, o relatório declara que não sabe. Preencher o campo com um nome plausível é pior do que deixá-lo vazio: quem lê passa a acreditar que a medição é reproduzível.

a máquina declarada, em src/bancada/content/medicao.ts
export const MAQUINA_DE_REFERENCIA = {
  descricao: 'desktop x64 do laboratório, com vídeo integrado e sem placa dedicada',
  navegador: 'Chrome com WebGL 2 habilitado',
  observacao:
    'é o limite duro do parque: nenhum número deste ambiente pode depender de placa dedicada',
} as const;

Três campos, e o terceiro é uma frase em português que nenhum programa lê. Ela existe para quem for acrescentar conteúdo à cena depois: o parque previsto tem vídeo integrado e não tem placa dedicada, e nenhum número deste ambiente pode depender de uma. A constante fica ao lado do código que mede, e a proximidade é o ponto. Um relatório que imprime números e uma anotação que descreve a máquina, guardados em lugares diferentes, se separam na primeira vez que alguém copia os números para outro lugar.

flowchart TD
    A["1. a maquina de referencia<br/>a mais modesta do parque"]
    B["2. o navegador previsto"]
    C["3. o que o aparelho disse da placa<br/>ou que ela nao foi identificada"]
    D["4. o teto declarado por quadro"]
    E["5. o censo estatico"]
    F["6. a leitura do quadro<br/>ou o aviso de que ela entra depois"]
    A --> B --> C --> D --> E --> F
    A -.-> G["o contexto chega antes do numero"]
    F -.-> G
Figura 6: A ordem das linhas da folha: o contexto chega antes do número.

Você mediria no computador mais rápido da sala? Pense no que isso significa. Os números saem confortáveis, a folga contra o teto parece generosa, e a decisão razoável a partir deles é acrescentar conteúdo. A decisão razoável a partir dos números da máquina fraca é o contrário. Nenhuma das duas pessoas tem como perceber isso, se o número circular sozinho.

O instrumento guarda uma janela de cento e vinte quadros, em espaço fixo que se reescreve por cima do mais antigo (pouco mais de um segundo, num aparelho confortável). Essa janela é curta, e a brevidade dela é a decisão. Acumular a sessão inteira produziria uma média que melhora sozinha com o tempo, sem que nada na cena tivesse melhorado. Um engasgo de trinta milissegundos, diluído em dez mil quadros confortáveis, desaparece na terceira casa decimal. Numa janela de cento e vinte, o mesmo engasgo ainda pesa.

A leitura devolve o intervalo médio, o pior intervalo da janela e a contagem de quadros que passaram do teto. Esta última é calculada e fica disponível a quem escreve código, embora não seja impressa na folha. O pior intervalo é o número que descreve a experiência de quem usa o aparelho, porque o corpo registra o engasgo e não registra a média. Dentro de um visor, um quadro perdido é uma imagem repetida enquanto a cabeça continuou girando.

O nome da placa fecha o assunto com o caso mais desconfortável. Ele é pedido ao navegador por uma extensão opcional, e navegadores modernos frequentemente a bloqueiam por privacidade. Quando o pedido não é atendido, a oficina escreve que a placa não foi identificada, com o motivo entre parênteses. Um campo preenchido com um nome plausível transformaria a folha inteira numa medição aparentemente reproduzível que ninguém consegue repetir.

1.7 O quinto estado da Bancada

A mesma cena do estado anterior, agora com uma peça que ninguém deste lado desenhou encostada no tampo.

O estado anterior da Bancada — a oficina de montagem servida em três regimes pela mesma URL — mostrava cinco peças vestidas. Cada uma com dente, faceta e superfície declarada. Toda aquela forma tinha nascido em código, e a convenção da cena estava garantida por construção: os vértices foram escritos já em metro, com o vertical em Y. Agora a cena sobe primeiro e o ativo chega depois. Essa ordem aparece na tela. Por um instante a oficina existe sem a morsa, e então ela aparece sobre o tampo, e ninguém escondeu esse instante atrás de uma tela de carregamento.

a composição do marco, em src/demos/05_demo.ts
// ---------------------------------------------------------------------------
// Marco 05 — o ativo que veio de fora, e o que ele custa.
//
// Até aqui toda geometria nasceu em código, com a convenção da cena garantida por
// construção. Este módulo traz um arquivo que alguém autorou noutro programa: ele
// chega em outra unidade, com outro vertical, com a origem noutro canto — e nada
// disso está declarado no arquivo. Quem importa é que declara, e o laudo registra
// o que foi corrigido.
//
// A cena já está de pé quando o ativo chega. Essa ordem não é conveniência de
// código: é como o ambiente se comporta de verdade, e escondê-la atrás de uma
// tela de carregamento ensinaria o contrário.
//
// Ponto de entrada do MARCO 05, imutável (`specs/marcos-executaveis.md`).
// ---------------------------------------------------------------------------

import { montarPalco, type Palco } from '../bancada/core/palco';
import { montarCena, type CenaDaBancada } from '../bancada/core/cena';
import { Relogio, type Amostra } from '../bancada/core/relogio';
import { Orcamento, TETO_DESKTOP_MS } from '../bancada/core/orcamento';
import { montarLaco, type Laco } from '../bancada/core/laco';
import { vestirCena, type Conteudo } from '../bancada/content/pecas';
import { importarAtivo, linhasDoAtivo, type AtivoImportado } from '../bancada/content/ativos';
import { censoDaCena, identificarPlaca, linhasDaMedicao } from '../bancada/content/medicao';

const TETO_DO_QUADRO_MS: number = TETO_DESKTOP_MS;

// A morsa tem dezoito centímetros de boca. Esse número não está no arquivo e não
// tem como estar: é o que se sabe da peça, e é dele que sai a escala por divisão
// — digitar 0.01 funciona até chegar um ativo em milímetro.
const MAIOR_LADO_DA_MORSA_M: number = 0.18;

function criarNo<T extends HTMLElement>(tag: string, pai: HTMLElement, id?: string): T {
  const no: T = document.createElement(tag) as T;
  if (id !== undefined) {
    no.id = id;
  }
  pai.appendChild(no);
  return no;
}

const raiz: HTMLElement = document.getElementById('bancada') ?? document.body;

const titulo: HTMLHeadingElement = criarNo<HTMLHeadingElement>('h1', raiz);
titulo.textContent = 'Bancada — o ativo externo, ajustado à convenção da cena';

const canvas: HTMLCanvasElement = criarNo<HTMLCanvasElement>('canvas', raiz, 'cena');
const botaoMedir: HTMLButtonElement = criarNo<HTMLButtonElement>('button', raiz, 'medir');
botaoMedir.type = 'button';
botaoMedir.textContent = 'Medir agora';

const raizAtivo: HTMLElement = criarNo<HTMLElement>('div', raiz, 'ativo');
const raizCusto: HTMLElement = criarNo<HTMLElement>('div', raiz, 'custo');

const palco: Palco = montarPalco(canvas);
const cena: CenaDaBancada = montarCena();
const relogio: Relogio = new Relogio();
const orcamento: Orcamento = new Orcamento(TETO_DO_QUADRO_MS);
const laco: Laco = montarLaco(palco, cena.sala, relogio, orcamento);

const conteudo: Conteudo = vestirCena(cena);
void conteudo;

function escrever(destino: HTMLElement, linhas: readonly string[]): void {
  destino.replaceChildren();
  const bloco: HTMLPreElement = document.createElement('pre');
  bloco.textContent = linhas.join('\n');
  destino.appendChild(bloco);
}

escrever(raizAtivo, ['O ativo externo ainda não foi trazido.']);

// A medição é sob demanda porque medir é perguntar ao aparelho, e a resposta só
// vale depois de alguns quadros terem sido desenhados de verdade.
botaoMedir.addEventListener('click', () => {
  escrever(
    raizCusto,
    linhasDaMedicao(censoDaCena(cena.sala), orcamento.ler(), identificarPlaca(palco.renderer)),
  );
});

let ativo: AtivoImportado | undefined = undefined;

void importarAtivo('/ativos/morsa.gltf', {
  maiorLadoRealM: MAIOR_LADO_DA_MORSA_M,
  giroEmXGraus: -90,
  assentarNaBase: true,
  centrarNoPlano: true,
})
  .then((importado: AtivoImportado) => {
    ativo = importado;
    cena.bancada.add(importado.no);
    escrever(raizAtivo, linhasDoAtivo(importado));
  })
  .catch((erro: unknown) => {
    // Ativo que não chega é falha de conteúdo e precisa aparecer como falha. A
    // cena continua de pé sem ele, e é por continuar que o silêncio seria
    // perigoso: ninguém notaria a morsa faltando.
    escrever(raizAtivo, [
      'O ativo externo não pôde ser trazido.',
      erro instanceof Error ? erro.message : String(erro),
    ]);
  });

laco.aoPasso((amostra: Amostra) => {
  cena.eixo.rotation.y += amostra.delta * 0.6;
  ativo?.avancar(amostra.delta);
});

laco.iniciar();

O marco declara uma constante antes de qualquer outra coisa. O maior lado da morsa mede 0,18 metro. Esse número está no código porque não está no arquivo, e o comentário ao lado dele diz exatamente isso. A importação recebe esse valor, o giro de −90 graus em X, e os dois pedidos de assentar na base e centrar no plano.

Repare no bloco de tratamento de falha. Por que ele existe, se um ativo que não chega parece problema evidente? Porque a cena continua de pé sem o ativo. Uma oficina completa, com tampo, peças e prateleira, não parece incompleta por não ter uma morsa. O painel escreve a falha em vez de deixar o buraco invisível.

1.7.1 O laudo das três divergências

Assim que o ativo entra, a página escreve o laudo. Ele começa pelo par que interessa. A morsa chegou medindo 18,00 por 12,00 por 17,00 unidades do arquivo, e ficou com 18,0 por 17,0 por 12,0 centímetros na convenção da cena. Em seguida vem o fator aplicado, 0,0100, com a observação de que ele foi medido e não digitado.

Depois disso vem o conteúdo do arquivo: quatro malhas, quarenta e oito triângulos e dois materiais, FerroFundido e AcoUsinado. E a animação AbrirFechar, com três segundos de duração. Ela é avançada pelo mesmo intervalo com que o laço gira o eixo do mecanismo, e não por um relógio próprio.

Fecham a folha três linhas de divergência. Uma por desvio que o arquivo trouxe.

A primeira diz que o arquivo mede 18.00 para uma peça de 0.18 m, o que dá 100 unidades por metro. Ela relata a medida, e a conclusão sobre a unidade fica com quem lê. A segunda diz que a peça foi girada −90 graus em X, o desencontro entre o vertical do autor e o vertical desta cena. A terceira diz que a peça vinha deslocada 0.30 m da própria base.

Quarenta e oito triângulos são pouco para uma peça que parece complexa, e o ativo foi escolhido assim. O assunto deste estado é o caminho entre o arquivo e a cena. Um ativo pesado deslocaria a atenção para a contagem, que é assunto da folha da medição.

1.7.2 A borda e a prateleira, cada uma na sua coluna

Na oficina completa, o tampo de 1,4 por 0,8 metro recebe parafusos a cada dois centímetros de perímetro. A rotina que dispõe as posições devolve 206, e a montagem transforma essas 206 posições num lote instanciado preso ao próprio tampo. O relatório da repetição mostra o efeito numa linha e a ausência de efeito na outra. As 206 chamadas de desenho passam a uma. Os 4.944 triângulos das 206 cabeças sextavadas continuam 4.944, porque cada cabeça continua sendo desenhada inteira.

A prateleira do fundo é o outro lado. Ela guarda 24 peças de reposição. São a mesma malha da engrenagem que está sobre a bancada. Essa identidade é o argumento: o que muda de uma para a outra é a distância até o olho. A prateleira fica a 0,4 metro de altura e 2,8 metros para trás da origem da sala.

A ferramenta corta a malha de 224 para 82 triângulos, e de 338 para 169 vértices. Nas 24 peças isso são 5.376 triângulos que viram 1.968. A troca acontece a 2,2 metros da câmera ao centro da prateleira, e ela vale para o lote inteiro. Os dois níveis são dois lotes, e a troca liga um e desliga o outro.

A oficina tem um botão que aproxima a câmera da prateleira e outro que a traz de volta. Ele é o instrumento de conferência. Com a folha da medição aberta, a contagem de triângulos sobe ao chegar perto e desce ao afastar. Sem esse botão, a troca de nível seria uma afirmação sobre código; com ele, é um número que muda na tela.

1.7.3 A folha da medição

O botão de medir escreve censo, leitura e máquina na mesma página. A ordem das linhas é parte do argumento. A folha abre pela máquina, e não pelos números. Primeiro a descrição do aparelho de referência — o desktop x64 do laboratório, com vídeo integrado e sem placa dedicada. Depois o navegador previsto, e depois o que este aparelho relatou sobre a placa. Quando o navegador recusa dizer o nome do adaptador, essa linha diz que a placa não foi identificada, com o motivo entre parênteses.

Só então vem o teto declarado por quadro. Em seguida o censo estático: triângulos totais, malhas soltas, lotes instanciados, instâncias, chamadas previstas e materiais distintos. Quem lê a folha na sequência em que ela foi escrita recebe o contexto antes do número, o que torna difícil copiar o número sozinho.

A leitura dinâmica entra por último, e ela avisa quando ainda não tem o que dizer. Antes do primeiro quadro medido, a folha escreve que a leitura entra depois. Imprimir zeros ali pareceria medição, e seria a única linha mentirosa de uma página inteira de fatos.

Com quadros medidos, a leitura traz o número de quadros da janela, as chamadas e os triângulos efetivamente desenhados. Traz também o intervalo médio, com a folga contra o teto, e o pior intervalo observado. Fecha a folha a linha que nomeia a diferença entre censo e medição: os triângulos que ficaram fora do campo de visão neste quadro, e que voltam assim que a câmera girar.

Medir com a câmera junto à bancada e medir com a câmera junto à prateleira devolve duas folhas com o mesmo censo e leituras diferentes. E se as duas leituras saírem iguais? Então alguma coisa não está sendo descartada por enquadramento, e o lugar de procurar é a hierarquia da cena.

1.8 A linha de base que julga o resto

O eixo de modelagem fecha aqui, e o que sobrevive dele para todos os capítulos seguintes é a medição.

A oficina tem agora conteúdo de três procedências. As três se conferem de jeitos diferentes. Geometria autorada dentro do projeto, com cada vértice decidido em código. Uma peça trazida de fora, corrigida na chegada e registrada num laudo. E repetição tratada como repetição, com uma coluna do relatório movida e a outra deixada onde estava.

flowchart LR
    A["geometria autorada<br/>no proprio projeto"] --> A1["confere-se abrindo<br/>a rotina que a escreveu"]
    B["peca trazida de fora"] --> B1["confere-se pelo laudo:<br/>como chegou, como ficou,<br/>o que foi corrigido"]
    C["repeticao tratada<br/>como repeticao"] --> C1["confere-se pelas duas colunas:<br/>uma se move, a outra fica"]
    A1 --> D["folha com censo, leitura<br/>e maquina lado a lado"]
    B1 --> D
    C1 --> D
Figura 7: Três procedências de conteúdo, e a conferência que cada uma exige.
ImportanteO saldo, em três afirmações
  • Existe uma convenção escrita — unidade, vertical, origem e frente —, e todo objeto da cena a obedece.
  • Cada ativo trazido de fora tem laudo, com o que chegou, o que ficou e o que foi corrigido, e os números do laudo saíram de medição em vez de digitação.
  • Existe uma folha com censo, leitura e máquina lado a lado, e a máquina é a mais modesta do parque previsto.

Quantas das três você conseguiria mostrar numa captura de tela? Nenhuma. A cena de quem fez as três e a cena de quem arrastou tudo com o mouse produzem a mesma imagem. A diferença aparece no primeiro ativo novo, no primeiro aparelho diferente e no primeiro recurso acrescentado. Daqui em diante, cada assunto acrescenta trabalho por quadro. A câmera que se move, a seleção que testa um raio contra a cena, o agarre, a sessão imersiva que desenha a mesma cena duas vezes, o registro contra uma superfície física. Nenhum deles é gratuito. O mais caro desenha a cena inteira duas vezes.

Sem um número medido agora, a primeira queda de desempenho será atribuída ao último recurso acrescentado. Essa atribuição é quase sempre errada, e é convincente: o recurso novo é a coisa que mudou, e a coincidência temporal parece prova. Com a linha de base registrada, a comparação é entre duas folhas, e a folha antiga não tem opinião sobre quem é o culpado.

O hábito que sobrevive deste ponto em diante custa menos do que parece. Medir antes e depois de cada acréscimo, com a mesma posição de câmera nos dois momentos, produz um par de folhas que atribui o custo ao acréscimo com evidência. O trabalho é apertar um botão duas vezes. O que ele evita é a discussão que não termina, em que cada pessoa tem uma teoria sobre qual recurso pesou.

A oficina, até aqui, foi vista de um ângulo só, e essa imobilidade escondeu duas coisas. A diferença entre censo e leitura, com a câmera parada, é um número fixo; ela vira uma grandeza que muda a cada segundo assim que alguém puder olhar em volta. E a troca de nível de detalhe nunca dispara sozinha, porque a distância até a prateleira é constante. Ela foi testada por um botão que aproxima a câmera à força. A demonstração é honesta e artificial.

O passo seguinte não acrescenta conteúdo à cena. Ele acrescenta um jeito de olhar para ela. A câmera deixa de ser fixa e passa a orbitar sob o cursor. A oficina vira algo que outra pessoa consegue abrir e percorrer sem instrução verbal. É a primeira vez que a cena sai da tela de quem a construiu.

Tarefa 1: Trazer ativos externos e ajustá-los ao projeto

Parte do conteúdo da cena chega de fora, num formato de troca aberto que carrega geometria, materiais e animação. Trazer é só o começo: escala, orientação, unidade de medida e posição na hierarquia são decisões a tomar aqui, e um modelo importado sem ajuste costuma aparecer deitado, gigante ou a dez metros da origem.

O que fica pronto é o caminho completo entre o arquivo de origem e o objeto integrado à cena, com cada ajuste registrado. É esse caminho que distingue um ambiente construído de uma cena de demonstração aberta e publicada como está.

Tarefa 2: Tratar repetição como repetição

Uma cena com quatrocentos parafusos pode custar quatrocentas chamadas de desenho ou uma só. Objetos repetidos passam a ser desenhados como repetição, e os que existem para serem vistos de longe ganham versão simplificada.

A tarefa fecha quando a cena completa do domínio escolhido está montada com esses dois recursos aplicados onde eles compensam, e com o critério de onde compensa escrito. Simplificar o objeto que fica sempre perto do olho é trabalho jogado fora.

Tarefa 3: Medir o custo em número

Impressão sobre desempenho não sustenta decisão alguma. Triângulos e chamadas de desenho passam a ser contados, e o número passa a caber num teto declarado antes da medição.

O que fica pronto é a medição registrada junto da máquina em que foi obtida, tomando a máquina mais modesta prevista como referência. Um número sem a máquina ao lado não permite a ninguém repetir a conta.