%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
A["o arquivo da morsa<br/>18 × 12 × 17 unidades"] -->|"url e o que quem<br/>importa declarou"| B["'importarAtivo()'<br/>gira, escala, assenta<br/>'linhasDoAtivo()'<br/>escreve o laudo"]
B --> F["'montarAtivosECusto()'<br/>monta a folha"]
F --> K["a folha na tela<br/>fator 0,0100<br/>três divergências<br/>206 chamadas contra 1<br/>224 e 110 triângulos<br/>13.484 no censo<br/>8.108 na leitura"]
D["'disporNaBorda()'<br/>'custoDaRepeticao()'<br/>'linhasDaRepeticao()'"] --> F
G["'simplificar()'<br/>'montarPrateleira()'<br/>'linhas()'"] --> F
I["clique em Medir agora<br/>'censoDaCena()', 'ler()'<br/>'linhasDaMedicao()'"] --> F
1 Ativos, formatos de troca e orçamento de cena — Infográfico do código
O código deste módulo traz para a cena uma peça feita fora do projeto, desenha centenas de cópias repetidas num pedido só à placa de vídeo e conta o que a cena custa. Este infográfico acompanha uma execução só: da abertura da página do módulo, a que esse código monta no navegador, com a morsa chegando, até um clique em Medir agora.
1.1 O caminho
O arquivo da morsa entra à esquerda e vira a primeira das quatro partes da folha (o texto que o programa escreve na tela, abaixo do desenho); as outras três se juntam a essa primeira em montarAtivosECusto(), e a folha sai à direita com os números desta execução.
1.2 Os arquivos
Cada arquivo do módulo, o que ele declara e o que ele usa de módulos anteriores.
| Arquivo | O que declara | O que usa de antes |
|---|---|---|
ativos.ts |
CONVENCAO_DA_CENA, os tipos AjusteDeImportacao, DimensoesM, DiagnosticoDeImportacao e AtivoImportado, e as funções importarAtivo(), apontarDivergencias() e linhasDoAtivo() |
contarTriangulos(), do módulo de modelagem; o carregador GLTFLoader, da biblioteca |
repeticao.ts |
ESPACAMENTO_DOS_PARAFUSOS (0,02 m), DISTANCIA_DE_TROCA (2,2 m), os tipos CustoDaRepeticao, NivelSimplificado e Prateleira, e as funções disporNaBorda(), montarLoteInstanciado(), custoDaRepeticao(), simplificar(), montarPrateleira() e linhasDaRepeticao() |
contarTriangulos() e contarVertices(), do módulo de modelagem; mergeVertices() e SimplifyModifier, da biblioteca |
medicao.ts |
MAQUINA_DE_REFERENCIA, o tipo Censo e as funções censoDaCena(), identificarPlaca() e linhasDaMedicao() |
contarTriangulos(), do módulo de modelagem; o tipo LeituraDoOrcamento, do módulo do grafo de cena |
custo.ts |
montarAtivosECusto() |
paragrafo() e subtitulo(), do módulo de dispositivos |
morsa.gltf |
o ativo: quatro malhas, cada uma a forma de uma parte guardada em listas de números, os materiais FerroFundido e AcoUsinado (material é a descrição da superfície que a biblioteca desenha: cor, brilho metálico e aspereza), a animação AbrirFechar e, no campo de texto livre asset.generator, a frase “unidade: centimetro; eixo vertical: Z”, que nenhum programa é obrigado a ler |
— |
05_demo.ts |
a página do módulo: as constantes TETO_DO_QUADRO_MS (16,7 ms, o mesmo valor de TETO_DESKTOP_MS), MAIOR_LADO_DA_MORSA_M (0,18), POSICAO_DA_MORSA, PECAS_DE_REPOSICAO (24), REMOCAO_NO_NIVEL_DISTANTE (0,5), ALVO_DA_BANCADA, ALVO_DA_PRATELEIRA e DISTANCIA_DA_PRATELEIRA_M (1,2 m), os dois botões e a ordem em que as funções rodam |
montarPalco(), montarCena(), montarLaco(), Orcamento e TETO_DESKTOP_MS, do módulo do grafo de cena; vestirCena(), extrudarPerfil() e perfilRegular(), do módulo de modelagem |
1.3 Os dados na memória
Os tipos que o módulo define, campo a campo, com o valor que cada campo guarda nesta execução.
1.3.1 A importação
A convenção da cena é o conjunto de escolhas que todo objeto da cena obedece, escrito uma vez e num lugar só:
ativos.ts
O ajuste de importação tem duas metades. O tipo AjusteDeImportacao guarda só a declarada, o que quem importa escreve porque o arquivo não responde; a outra metade, quanto escalar e quanto deslocar para assentar, o programa mede:
ativos.ts
/**
* O que quem importa precisa declarar, porque o arquivo não responde.
*
* `maiorLadoRealM` é a medida da peça no mundo, em metros, informada por quem
* a conhece. O fator de escala sai dela por divisão contra o maior lado medido
* no arquivo, e por isso não é digitado: um 0.01 escrito à mão serve ao arquivo
* em centímetro e erra, sem aviso, no primeiro ativo em milímetro.
*/
export interface AjusteDeImportacao {
readonly maiorLadoRealM: number;
O laudo é este tipo:
ativos.ts
| Tipo e campo | O que guarda | Nesta execução |
|---|---|---|
AjusteDeImportacao.maiorLadoRealM |
a medida da peça no mundo, em metros, escrita por quem a conhece | 0,18 |
AjusteDeImportacao.giroEmXGraus |
o giro em torno de X que traz o vertical do autor ao vertical da cena | −90 |
AjusteDeImportacao.assentarNaBase |
se a base vai ao zero do objeto em que a peça fica pendurada | true |
AjusteDeImportacao.centrarNoPlano |
se o centro vai ao zero em X e em Z | true |
DiagnosticoDeImportacao.arquivo |
de onde a peça veio | '/ativos/morsa.gltf' |
DiagnosticoDeImportacao.comoChegou |
em unidades do arquivo, a caixa envolvente do que chegou: a menor caixa, com as arestas paralelas aos três eixos, que contém todos os vértices (os cantos dos triângulos) | 18 × 12 × 17 |
DiagnosticoDeImportacao.comoFicou |
a mesma caixa depois de girar, escalar e assentar, em metros | 0,18 × 0,17 × 0,12 |
DiagnosticoDeImportacao.fatorDeEscala |
o fator de escala, o número que multiplica todas as medidas da peça | 0,0100 |
DiagnosticoDeImportacao.giroEmXGraus |
o giro aplicado | −90 |
DiagnosticoDeImportacao.deslocamentoCorrigidoM |
quanto a peça andou para a base chegar ao zero | 0 em X, −0,30 m em Y, 0 em Z |
DiagnosticoDeImportacao.divergencias |
uma frase por divergência, a diferença entre o que a peça tem e o que a cena esperaria | três frases: unidade, eixo vertical e origem |
AtivoImportado.malhas, triangulos, materiais |
o que o arquivo trouxe | quatro malhas, 48 triângulos, FerroFundido e AcoUsinado |
AtivoImportado.animacoes |
as animações que vieram no arquivo | AbrirFechar, de 3,0 s |
AtivoImportado.avancar, tocar |
avançar as animações pelo intervalo entre um quadro e o seguinte, e ligar uma pelo nome | avancar roda a cada quadro; a página do módulo não chama tocar |
1.3.2 A repetição
Um lote é o conjunto de cópias de uma peça desenhado numa chamada só, e cada cópia do lote é uma instância; instanciar é desenhar as cópias assim, num lote. O nível de detalhe é, para a peça que está longe, uma versão com menos triângulos.
| Tipo e campo | O que guarda | Nesta execução |
|---|---|---|
CustoDaRepeticao.objetos |
quantos parafusos há na borda | 206 |
CustoDaRepeticao.chamadasSemInstanciar, chamadasInstanciando |
as chamadas de desenho sem instanciar e instanciando | 206 e 1 |
CustoDaRepeticao.triangulosPorObjeto, triangulosTotais |
os triângulos de um parafuso e da borda inteira, que a instanciação não muda | 24 e 4.944 |
NivelSimplificado.triangulos, vertices |
a malha do nível de longe | 110 triângulos e 57 vértices |
NivelSimplificado.simplificou, observacao |
se a ferramenta de simplificação da biblioteca, SimplifyModifier, entregou malha utilizável, e a frase que diz o que aconteceu |
true, “simplificada pela ferramenta, com as normais recalculadas” |
Prateleira.quantidade |
as peças de reposição | 24 |
Prateleira.triangulosPerto, triangulosLonge |
os dois níveis somados nas 24 peças | 5.376 e 2.640 |
Prateleira.atualizar, linhas |
escolher o nível pela câmera, e escrever as linhas da folha | atualizar roda a cada quadro |
1.3.3 A medição
A máquina de referência é o aparelho contra o qual a cena é orçada, e a definição pede que seja a mais modesta das previstas; o código declara esta, e ninguém mediu ainda se ela é a mais modesta:
medicao.ts
Uma malha solta é a malha que não está em lote nenhum; o censo conta malhas soltas e lotes em campos separados:
medicao.ts
export interface Censo {
readonly malhas: number;
readonly lotesInstanciados: number;
readonly instancias: number;
readonly triangulos: number;
readonly materiaisDistintos: number;
/**
* Chamadas de desenho previstas, no mínimo: uma por malha e uma por lote, e
* não uma por instância.
*/
readonly chamadasPrevistas: number;
}
Campo de Censo |
O que guarda | Nesta execução |
|---|---|---|
malhas |
as malhas soltas | 12 |
lotesInstanciados |
os lotes | 3 |
instancias |
as cópias dentro dos lotes | 254 |
triangulos |
o total, com cada lote contado como triângulos por cópia vezes o número de cópias | 13.484 |
materiaisDistintos |
os materiais diferentes | 6 |
chamadasPrevistas |
no mínimo, uma chamada por malha solta e uma por lote, e nenhuma por cópia | 15 |
1.3.4 A árvore da cena
Cada objeto da cena é um nó pendurado num pai, e o conjunto dos nós é a árvore; é nela que o programa guarda o que esta execução montou.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
S["'sala'"] --> BA["'bancada'"]
S --> PR["'prateleira-de-reposicao'<br/>ao fundo da sala<br/>y 0,4 m, z −2,8 m"]
BA --> T["'tampo'<br/>malha solta, 12 triângulos"]
BA --> CA["'cavalete'<br/>malha solta, 12 triângulos"]
T --> AN["o suporte e as cinco peças<br/>6 malhas soltas, 452 triângulos"]
T --> PB["'parafusos-da-borda'<br/>um lote, 206 cópias<br/>4.944 triângulos"]
T --> MO["'morsa.gltf'<br/>4 malhas soltas, 48 triângulos<br/>base no lado de cima do tampo"]
PR --> PP["'reposicao-perto'<br/>lote de 24, 5.376 triângulos<br/>desligado"]
PR --> PL["'reposicao-longe'<br/>lote de 24, 2.640 triângulos<br/>ligado"]
1.4 As funções
Cada função que decide alguma coisa, com o que recebe, o que decide e o que devolve nesta execução.
1.4.1 importarAtivo()
| Recebe | Decide | Devolve |
|---|---|---|
'/ativos/morsa.gltf' e o que quem importa declarou, 0,18 m e −90° |
a ordem fixa: girar, escalar e só então medir o deslocamento que falta; caixa de maior lado zero não tem divisão, e o fator fica 1 | um AtivoImportado com fator 0,0100 e a peça medindo 18,0 × 17,0 × 12,0 cm |
ativos.ts
// 1. Orientação, declarada: o formato fixa o vertical em Y, e nenhum campo que
// o importador seja obrigado a ler diz que o autor usou outro eixo.
no.rotation.x = MathUtils.degToRad(ajuste.giroEmXGraus);
no.updateMatrixWorld(true);
// 2. Escala, medida: o maior lado real dividido pelo maior lado do que chegou,
// medido depois do giro. Caixa de lado zero não tem divisão: o fator fica 1.
const medidoAposGirar: number = maiorLado(dimensoes(new Box3().setFromObject(no)));
const fator: number = medidoAposGirar === 0 ? 1 : ajuste.maiorLadoRealM / medidoAposGirar;
no.scale.setScalar(fator);
no.updateMatrixWorld(true);
// 3. Origem, medida sobre a caixa já girada e escalada: cada correção pedida
// leva ao zero do pai a base (em Y) ou o centro (em X e Z).
const caixaAjustada: Box3 = new Box3().setFromObject(no);
const centro: Vector3 = new Vector3();
caixaAjustada.getCenter(centro);
const correcao: Vector3 = new Vector3(
ajuste.centrarNoPlano ? -centro.x : 0,
ajuste.assentarNaBase ? -caixaAjustada.min.y : 0,
ajuste.centrarNoPlano ? -centro.z : 0,
);
no.position.add(correcao);
no.updateMatrixWorld(true);
%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 24, "rankSpacing": 28}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph girar["(1) e (2) gira e escala"]
direction TB
ini["05_demo.ts chama<br/>importarAtivo('/ativos/morsa.gltf',<br/>ajuste)"] --> carga["new<br/>GLTFLoader().loadAsync(url)"]
carga --> chegou["dimensoes(new<br/>Box3().setFromObject(no))<br/>devolve comoChegou:<br/>18.00 x 12.00 x 17.00"]
chegou --> giro["(1) orientação<br/>DECLARADA:<br/>degToRad(ajuste.giroEmXGraus),<br/>-90 graus em X"]
giro --> remede["(2) escala MEDIDA depois<br/>do giro: maiorLado(...)<br/>devolve 18.00"]
remede --> zero{"medidoAposGirar === 0"}
zero -->|"true"| um["fator = 1: sem medida<br/>não há divisão possível"]
zero -->|"false"| div["fator = 0.18 / 18.00 =<br/>0.0100"]
um --> aplica["no.scale.setScalar(fator)"]
div --> aplica
end
subgraph origem["(3) origem medida"]
direction TB
res["caixaAjustada.getCenter(centro),<br/>sobre a caixa já girada<br/>e escalada"] --> base{"ajuste.assentarNaBase"}
base -->|"true"| sobe["y da correção =<br/>-caixaAjustada.min.y"]
base -->|"false"| nada["y da correção = 0"]
sobe --> plano{"ajuste.centrarNoPlano"}
nada --> plano
plano -->|"true"| centra["x e z da correção =<br/>-centro.x e -centro.z"]
plano -->|"false"| fica["x e z da correção = 0"]
centra --> soma["no.position.add(correcao):<br/>0.30 m"]
fica --> soma
soma --> ficou["comoFicou: 18.0 x 17.0 x<br/>12.0 cm"]
end
subgraph conta["conta e entrega"]
direction TB
censo["no.traverse conta o que<br/>chegou"] --> emalha{"filho instanceof Mesh"}
emalha -->|"sim"| soma2["contarTriangulos(filho.geometry)<br/>e<br/>materiais.add(item.name)"]
emalha -->|"não"| segue["nada a somar neste nó"]
soma2 --> censo
segue --> censo
censo -->|"árvore percorrida"| total["4 malhas, 48 triângulos,<br/>2 materiais:<br/>FerroFundido e<br/>AcoUsinado"]
total --> mistura["new AnimationMixer(no),<br/>que avancar(delta) usará<br/>no laço"]
mistura --> saida["AtivoImportado com no,<br/>diagnostico, animacoes e<br/>avancar()"]
end
girar --> origem --> conta
1.4.2 apontarDivergencias()
| Recebe | Decide | Devolve |
|---|---|---|
| a caixa como chegou, o fator, o que quem importa declarou e a correção | três testes independentes (fator fora de 0,5 a 2, giro diferente de zero, correção maior que 1 cm) e um quarto, o da lista vazia, que só escreve quando nenhum dos três escreveu | três frases: 100 unidades por metro, giro de −90 graus e deslocamento de 0,30 m |
ativos.ts
const divergencias: string[] = [];
if (fator < 0.5 || fator > 2) {
const unidadesPorMetro: number = 1 / fator;
divergencias.push(
`Unidade divergente: o arquivo mede ${maiorLado(comoChegou).toFixed(2)} para uma peça de ` +
`${ajuste.maiorLadoRealM.toFixed(2)} m, o que dá ${unidadesPorMetro.toFixed(0)} unidades ` +
`por metro. O formato de troca fixa metro, e o exportador não obedeceu.`,
);
}
if (ajuste.giroEmXGraus !== 0) {
divergencias.push(
`Eixo vertical divergente: o ativo veio com o vertical em outro eixo, e foi girado ` +
`${ajuste.giroEmXGraus} graus em X para o vertical da cena ser ` +
`${CONVENCAO_DA_CENA.eixoVertical}.`,
);
}
if (correcao.length() > 0.01) {
divergencias.push(
`Origem divergente: a peça vinha deslocada ${correcao.length().toFixed(2)} m da própria ` +
`base, e a correção a assentou na superfície de apoio.`,
);
}
if (divergencias.length === 0) {
divergencias.push('Nenhuma divergência acima da tolerância: o ativo chegou na convenção.');
}
return divergencias;
%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 24, "rankSpacing": 28}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph unidade["unidade"]
direction TB
ini["importarAtivo() chama<br/>apontarDivergencias(comoChegou,<br/>fator, ajuste, correcao)"] --> uni{"fator < 0.5 || fator > 2"}
uni -->|"0.0100, fora da faixa"| conta["unidadesPorMetro = 1 /<br/>fator = 100"]
uni -->|"dentro da faixa"| pulaUni["nada a acusar sobre<br/>unidade"]
conta --> msgUni["'Unidade divergente: o<br/>arquivo mede 18.00 para<br/>uma peça de 0.18 m, o<br/>que dá 100 unidades por<br/>metro'"]
end
subgraph eixoEOrigem["eixo e origem"]
direction TB
eixo{"ajuste.giroEmXGraus !==<br/>0"} -->|"-90"| msgEixo["'Eixo vertical<br/>divergente: girado -90<br/>graus em X para o<br/>vertical da cena ser Y',<br/>lido de<br/>CONVENCAO_DA_CENA.eixoVertical"]
eixo -->|"0"| pulaEixo["o autor já usava o<br/>vertical da cena"]
msgEixo --> org{"correcao.length() > 0.01"}
pulaEixo --> org
org -->|"0.30 m"| msgOrg["'Origem divergente: a<br/>peça vinha deslocada<br/>0.30 m da própria base'"]
org -->|"abaixo da tolerância"| pulaOrg["ruído de exportação, que<br/>a lista não reclama"]
end
subgraph folha["a lista vazia e o laudo"]
direction TB
vazia{"divergencias.length ===<br/>0"} -->|"true"| ok["'Nenhuma divergência<br/>acima da tolerância: o<br/>ativo chegou na<br/>convenção'"]
vazia -->|"false, três itens"| lista["as três frases, uma por<br/>divergência"]
ok --> laudo["linhasDoAtivo(ativo)<br/>escreve as linhas do laudo"]
lista --> laudo
laudo --> cm["cm(v) converte comoFicou<br/>para centímetro;<br/>comoChegou fica na<br/>unidade do arquivo"]
cm --> fim["nove linhas na tela,<br/>terminadas pelas<br/>divergências"]
end
unidade --> eixoEOrigem --> folha
1.4.3 linhasDoAtivo()
| Recebe | Decide | Devolve |
|---|---|---|
o AtivoImportado |
escreve a linha “Como chegou” em unidades do arquivo e a linha “Como ficou” em centímetros; escreve “nenhuma” quando não há animação | as linhas do laudo: seis fixas e as três divergências desta execução |
ativos.ts
/**
* As linhas do laudo, na ordem em que a página do módulo as escreve. A linha
* "Como chegou" fica em unidades do arquivo, e a linha "Como ficou", em
* centímetros: com as duas em unidades diferentes, a troca de unidade aparece
* na própria folha.
*/
export function linhasDoAtivo(ativo: AtivoImportado): string[] {
const d: DiagnosticoDeImportacao = ativo.diagnostico;
const cm = (v: number): string => (v * 100).toFixed(1);
return [
`Arquivo: ${d.arquivo}`,
`Como chegou: ${d.comoChegou.largura.toFixed(2)} x ${d.comoChegou.altura.toFixed(2)} x ` +
`${d.comoChegou.profundidade.toFixed(2)} unidades do arquivo`,
`Como ficou: ${cm(d.comoFicou.largura)} x ${cm(d.comoFicou.altura)} x ` +
`${cm(d.comoFicou.profundidade)} cm`,
`Fator de escala aplicado: ${d.fatorDeEscala.toFixed(4)} (medido, não digitado)`,
`Conteúdo: ${ativo.malhas} malhas, ${ativo.triangulos} triângulos, ` +
`${ativo.materiais.length} materiais (${ativo.materiais.join(', ')})`,
`Animações no arquivo: ${
ativo.animacoes.length === 0
? 'nenhuma'
: ativo.animacoes.map((a) => `${a.name} (${a.duration.toFixed(1)} s)`).join(', ')
}`,
...d.divergencias,
];
}
1.4.4 disporNaBorda()
| Recebe | Decide | Devolve |
|---|---|---|
| o tampo de 1,4 × 0,8 m, o espaçamento de 0,02 m, o recuo de 0,02 m e a altura de 0,026 m | quantas posições por lado, pela divisão do comprimento pelo espaçamento, e os cantos contados uma vez só | 206 posições: 67 em cada lado comprido e 36 em cada lado curto |
repeticao.ts
export function disporNaBorda(
largura: number,
profundidade: number,
espacamento: number,
recuo: number,
altura: number,
): Vector3[] {
const x: number = largura / 2 - recuo;
const z: number = profundidade / 2 - recuo;
const porLado: number = Math.max(2, Math.floor((x * 2) / espacamento));
const porTopo: number = Math.max(2, Math.floor((z * 2) / espacamento));
const posicoes: Vector3[] = [];
for (let i: number = 0; i < porLado; i += 1) {
const t: number = -x + (i * (x * 2)) / (porLado - 1);
posicoes.push(new Vector3(t, altura, -z));
posicoes.push(new Vector3(t, altura, z));
}
// Os cantos já entraram no laço anterior: este começa em 1 e para um antes do
// fim, para não repetir posição.
for (let i: number = 1; i < porTopo - 1; i += 1) {
const t: number = -z + (i * (z * 2)) / (porTopo - 1);
posicoes.push(new Vector3(-x, altura, t));
posicoes.push(new Vector3(x, altura, t));
}
return posicoes;
}
%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 24, "rankSpacing": 28}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph borda["as posições na borda"]
direction TB
ini["05_demo.ts chama<br/>disporNaBorda(1.4, 0.8,<br/>ESPACAMENTO_DOS_PARAFUSOS,<br/>0.02, 0.026)"] --> meia["x = largura / 2 - recuo<br/>= 0.68 e z =<br/>profundidade / 2 - recuo<br/>= 0.38"]
meia --> conta["porLado = Math.max(2,<br/>Math.floor((x * 2) /<br/>espacamento)) = 67 e<br/>porTopo = 38"]
conta --> lados["primeiro laço: i de 0 a<br/>porLado - 1, duas<br/>posições por volta, em<br/>-z e em z"]
lados --> topos["segundo laço: i de 1 a<br/>porTopo - 2, sem repetir<br/>os cantos, em -x e em x"]
topos --> pos["posicoes = 2 * 67 + 2 *<br/>36 = 206"]
end
subgraph lote["um lote só"]
direction TB
perfil["extrudarPerfil(perfilRegular(6,<br/>0.004), 0.003,<br/>'parafuso'): a cabeça<br/>sextavada"] --> montar["montarLoteInstanciado(geometriaDoParafuso,<br/>acoEscovado.material,<br/>posicoes,<br/>'parafusos-da-borda')"]
montar --> novo["new<br/>InstancedMesh(geometria,<br/>material,<br/>posicoes.length)"]
novo --> volta{"i < posicoes.length"}
volta -->|"true"| compoe["matriz.compose(posicao,<br/>semGiro, semEscala) e<br/>lote.setMatrixAt(i,<br/>matriz)"]
compoe --> volta
volta -->|"false, 206 matrizes<br/>escritas"| envia["lote.instanceMatrix.needsUpdate<br/>= true, uma vez só"]
end
subgraph custo["o custo na folha"]
direction TB
pendura["tampo.add(parafusos)"] --> calcula["custoDaRepeticao(geometriaDoParafuso,<br/>206)"]
calcula --> num["contarTriangulos(geometria)<br/>= 24:<br/>chamadasSemInstanciar<br/>206,<br/>chamadasInstanciando 1,<br/>triangulosTotais 4944"]
num --> linhas["linhasDaRepeticao(custoDosParafusos):<br/>quatro linhas na folha"]
end
borda --> lote --> custo
for deixa de fora os dois cantos, que o primeiro já pôs: sobram 36. O mesmo fluxo segue por montarLoteInstanciado(), custoDaRepeticao() e linhasDaRepeticao().
1.4.5 montarLoteInstanciado()
| Recebe | Decide | Devolve |
|---|---|---|
a malha do parafuso, o material, as 206 posições e o nome 'parafusos-da-borda' |
pula a entrada vazia da lista de posições; escreve uma matriz por cópia, a que diz onde ela fica, e avisa a placa uma vez só, no fim | um lote com 206 cópias, que a página do módulo pendura no tampo |
repeticao.ts
/**
* Monta um lote instanciado a partir de uma malha e de uma lista de posições.
*
* O lote inteiro é desenhado numa chamada só. A matriz de cada cópia é escrita
* uma vez, na montagem, e não a cada quadro: o aviso `needsUpdate`, dado ao fim,
* manda a tabela de matrizes para a placa, e como nada a altera depois, a
* biblioteca não volta a enviá-la.
*/
export function montarLoteInstanciado(
geometria: BufferGeometry,
material: Material,
posicoes: readonly Vector3[],
nome: string,
): InstancedMesh {
const lote: InstancedMesh = new InstancedMesh(geometria, material, posicoes.length);
lote.name = nome;
const matriz: Matrix4 = new Matrix4();
const semGiro: Quaternion = new Quaternion();
const semEscala: Vector3 = new Vector3(1, 1, 1);
for (let i: number = 0; i < posicoes.length; i += 1) {
const posicao: Vector3 | undefined = posicoes[i];
if (posicao === undefined) {
continue;
}
matriz.compose(posicao, semGiro, semEscala);
lote.setMatrixAt(i, matriz);
}
lote.instanceMatrix.needsUpdate = true;
return lote;
}
1.4.6 custoDaRepeticao() e linhasDaRepeticao()
| Recebe | Decide | Devolve |
|---|---|---|
| a malha do parafuso e a quantidade, 206 | sem olhar para o lote, põe lado a lado a coluna das chamadas e a dos triângulos: instanciar muda a primeira e deixa a segunda igual | 206 chamadas sem instanciar, 1 instanciando, 24 triângulos por parafuso e 4.944 no total, que linhasDaRepeticao() escreve na folha |
repeticao.ts
/**
* O que a instanciação muda, em número: as chamadas de desenho passam de uma
* por objeto a uma só, e os triângulos ficam iguais, porque a placa continua
* desenhando cada um.
*
* A medida certa aqui é a chamada, e não o triângulo. O que a instanciação tira
* da conta é a preparação fixa de cada objeto, que não depende do tamanho dele.
*/
export function custoDaRepeticao(
geometria: BufferGeometry,
quantidade: number,
): CustoDaRepeticao {
const porObjeto: number = contarTriangulos(geometria);
return {
objetos: quantidade,
chamadasSemInstanciar: quantidade,
chamadasInstanciando: 1,
triangulosPorObjeto: porObjeto,
triangulosTotais: porObjeto * quantidade,
};
}
1.4.7 simplificar()
| Recebe | Decide | Devolve |
|---|---|---|
| a malha da engrenagem grande e a proporção 0,5 | duas conferências: menos de um vértice a tirar deixa o original; malha vazia ou com coordenada que não é número de verdade também | o nível de longe: 110 triângulos e 57 vértices, com as normais recalculadas (a normal é a direção que a luz consulta em cada vértice) |
repeticao.ts
const soPosicao: BufferGeometry = geometria.clone();
soPosicao.deleteAttribute('normal');
soPosicao.deleteAttribute('uv');
const unida: BufferGeometry = mergeVertices(soPosicao);
soPosicao.dispose();
const aRemover: number = Math.floor(contarVertices(unida) * proporcaoRemovida);
if (aRemover < 1) {
return {
geometria,
triangulos: contarTriangulos(geometria),
vertices: contarVertices(geometria),
simplificou: false,
observacao: 'malha pequena demais para valer a simplificação; o original ficou',
};
}
const candidata: BufferGeometry = new SimplifyModifier().modify(unida, aRemover);
const posicoes: ArrayLike<number> = candidata.getAttribute('position').array;
let naoFinitos: number = 0;
for (let i: number = 0; i < posicoes.length; i += 1) {
if (!Number.isFinite(posicoes[i] ?? Number.NaN)) {
naoFinitos += 1;
}
}
if (contarTriangulos(candidata) === 0 || naoFinitos > 0) {
candidata.dispose();
return {
geometria,
triangulos: contarTriangulos(geometria),
vertices: contarVertices(geometria),
simplificou: false,
observacao: 'a ferramenta não devolveu malha utilizável; o original ficou no lugar',
};
}
%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 24, "rankSpacing": 28}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph simplifica["o nível de longe"]
direction TB
ini["05_demo.ts chama<br/>montarPrateleira(engrenagem.geometry,<br/>latao.material,<br/>PECAS_DE_REPOSICAO,<br/>REMOCAO_NO_NIVEL_DISTANTE)"] --> simp["simplificar(geometria,<br/>0.5)"]
simp --> tira["soPosicao =<br/>geometria.clone(), sem<br/>normal e sem uv"]
tira --> une["mergeVertices(soPosicao):<br/>os vértices na mesma<br/>posição viram um só, 338<br/>viram 114"]
une --> quanto["aRemover =<br/>Math.floor(contarVertices(unida)<br/>* proporcaoRemovida)"]
quanto --> pouco{"aRemover < 1"}
pouco -->|"false"| modifica["new<br/>SimplifyModifier().modify(unida,<br/>aRemover)"]
modifica --> confere{"contarTriangulos(candidata)<br/>=== 0 || naoFinitos > 0"}
confere -->|"false"| normais["candidata.computeVertexNormals():<br/>simplificou true, 110<br/>triângulos por peça"]
pouco -->|"true"| original["o original fica: malha<br/>pequena demais"]
confere -->|"true"| original
end
subgraph monta["os dois lotes"]
direction TB
posicoes["24 posições, porFileira<br/>6, em quatro fileiras"] --> perto["montarLoteInstanciado(geometria,<br/>..., 'reposicao-perto'):<br/>224 triângulos por peça"]
perto --> longe["montarLoteInstanciado(nivel.geometria,<br/>..., 'reposicao-longe'),<br/>longe.visible = false"]
longe --> grupo["no = new Group() com os<br/>dois lotes; quem o pendura<br/>na sala é 05_demo.ts:<br/>cena.sala.add(prateleira.no)"]
end
subgraph troca["a troca, a cada quadro"]
direction TB
passo["no laço:<br/>prateleira.atualizar(palco.camera)"] --> dist{"daCamera.distanceTo(centro)<br/>> DISTANCIA_DE_TROCA"}
dist -->|"true: a câmera na<br/>posição em que o palco a<br/>pôs"| ligaLonge["perto.visible = false e<br/>longe.visible = true"]
dist -->|"false: depois de<br/>'Aproximar da prateleira<br/>do fundo', a câmera a<br/>1.29 m do centro"| ligaPerto["perto.visible = true e<br/>longe.visible = false"]
ligaLonge --> linhas["prateleira.linhas():<br/>5376 de perto, 2640 de<br/>longe, 51% a menos"]
ligaPerto --> linhas
end
simplifica -->|"a simplificada, ou o<br/>original"| monta --> troca
montarPrateleira() e pelo atualizar() de cada quadro.
1.4.8 montarPrateleira()
| Recebe | Decide | Devolve |
|---|---|---|
| a malha da engrenagem, o latão, 24 peças e a proporção 0,5 | dois lotes com as mesmas posições, seis por fileira; o de longe começa desligado; a cada quadro, atualizar() liga o de longe quando a câmera está a mais de 2,2 m do centro |
uma Prateleira com 5.376 triângulos de perto e 2.640 de longe, 51% a menos; com a câmera a 4,45 m, atualizar() devolve true |
repeticao.ts
const nivel: NivelSimplificado = simplificar(geometria, proporcaoRemovida);
const posicoes: Vector3[] = [];
const porFileira: number = 6;
for (let i: number = 0; i < quantidade; i += 1) {
const coluna: number = i % porFileira;
const fileira: number = Math.floor(i / porFileira);
posicoes.push(new Vector3(-0.25 + coluna * 0.1, 0.02 + fileira * 0.18, 0));
}
const perto: InstancedMesh = montarLoteInstanciado(
geometria,
material,
posicoes,
'reposicao-perto',
);
const longe: InstancedMesh = montarLoteInstanciado(
nivel.geometria,
material,
posicoes,
'reposicao-longe',
);
longe.visible = false;
const no: Group = new Group();
no.name = 'prateleira-de-reposicao';
no.add(perto);
no.add(longe);
1.4.9 censoDaCena()
| Recebe | Decide | Devolve |
|---|---|---|
cena.sala, a raiz da árvore |
para cada nó, um de três casos: não é malha, é malha solta ou é lote | um Censo com 13.484 triângulos e 15 chamadas previstas |
medicao.ts
export function censoDaCena(raiz: Object3D): Censo {
let malhas: number = 0;
let lotes: number = 0;
let instancias: number = 0;
let triangulos: number = 0;
const materiais: Set<string> = new Set<string>();
raiz.traverse((no: Object3D) => {
if (!(no instanceof Mesh)) {
return;
}
const porMalha: number = contarTriangulos(no.geometry);
if (no instanceof InstancedMesh) {
lotes += 1;
instancias += no.count;
triangulos += porMalha * no.count;
} else {
malhas += 1;
triangulos += porMalha;
}
// Material contado pelo `uuid`, que identifica cada objeto de material, e não
// pelo nome, que dois materiais diferentes podem repetir.
const material: Material | Material[] = no.material;
for (const item of Array.isArray(material) ? material : [material]) {
materiais.add(item.uuid);
}
});
return {
malhas,
lotesInstanciados: lotes,
instancias,
triangulos,
materiaisDistintos: materiais.size,
chamadasPrevistas: malhas + lotes,
};
}
%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 24, "rankSpacing": 28}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph percorre["percorre a árvore"]
direction TB
ini["clique em 'Medir agora'<br/>chama<br/>censoDaCena(cena.sala)"] --> anda["raiz.traverse, um nó por<br/>vez"]
anda --> ehMalha{"no instanceof Mesh"}
ehMalha -->|"false"| volta["nada a somar: luz, grupo<br/>ou eixo"]
volta --> anda
ehMalha -->|"true"| pm["porMalha =<br/>contarTriangulos(no.geometry)"]
pm --> lote{"no instanceof<br/>InstancedMesh"}
lote -->|"true"| somaLote["lotes += 1, instâncias<br/>+= no.count, triângulos<br/>+= porMalha * no.count"]
lote -->|"false"| somaSolta["malhas += 1, triângulos<br/>+= porMalha"]
somaLote --> mat["materiais.add(item.uuid),<br/>uma vez por material do<br/>nó"]
somaSolta --> mat
mat --> anda
end
subgraph resultado["o censo"]
direction TB
saida["Censo: 13484 triângulos,<br/>12 malhas soltas, 3<br/>lotes, 254 instâncias, 6<br/>materiaisDistintos"] --> prev["chamadasPrevistas =<br/>malhas + lotes = 12 + 3<br/>= 15, uma por lote e não<br/>uma por instância"]
end
percorre -->|"árvore percorrida"| resultado
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph K1["caso 1: o nó não é malha"]
direction TB
A1["'no instanceof Mesh' é falso"] --> A2["volta sem somar nada<br/>14 dos 29 nós desta cena<br/>luzes, grupos, contornos"]
end
subgraph K2["caso 2: malha solta"]
direction TB
B1["'no instanceof InstancedMesh' é falso"] --> B2["malhas mais 1<br/>triângulos mais os da malha<br/>12 malhas, 524 triângulos<br/>12 chamadas previstas"]
end
subgraph K3["caso 3: lote"]
direction TB
C1["'no instanceof InstancedMesh' é verdadeiro"] --> C2["lotes mais 1<br/>instâncias mais 'no.count'<br/>triângulos mais os de uma cópia vezes 'no.count'<br/>3 lotes, 254 instâncias, 12.960 triângulos<br/>3 chamadas previstas"]
end
K1 ~~~ K2 ~~~ K3
1.4.10 identificarPlaca()
| Recebe | Decide | Devolve |
|---|---|---|
palco.renderer, que o módulo do grafo de cena montou |
três falhas possíveis (a extensão que revela o nome não existe ou vem bloqueada, a chave dela não é número, o nome vem vazio) e, em todas, a recusa escrita por extenso | o nome que este navegador informou: “ANGLE (Intel, Intel(R) Graphics (0x00007D67) Direct3D11 vs_5_0 ps_5_0, D3D11)” |
medicao.ts
export function identificarPlaca(renderer: WebGLRenderer): string {
const contexto: WebGLRenderingContext | WebGL2RenderingContext = renderer.getContext();
const extensao: unknown = contexto.getExtension('WEBGL_debug_renderer_info');
if (extensao === null || typeof extensao !== 'object') {
return 'placa não identificada (o navegador não expõe o nome do adaptador)';
}
const chave: unknown = (extensao as Record<string, unknown>)['UNMASKED_RENDERER_WEBGL'];
if (typeof chave !== 'number') {
return 'placa não identificada (o navegador não expõe o nome do adaptador)';
}
const nome: unknown = contexto.getParameter(chave);
return typeof nome === 'string' && nome !== '' ? nome : 'placa não identificada';
}
%%{init: {"flowchart": {"useMaxWidth": false, "nodeSpacing": 24, "rankSpacing": 28}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph placa["a placa"]
direction TB
ini["clique em 'Medir agora'<br/>chama<br/>identificarPlaca(palco.renderer)"] --> ctx["renderer.getContext() e<br/>getExtension('WEBGL_debug_renderer_info')"]
ctx --> temExt{"extensao === null ||<br/>typeof extensao !==<br/>'object'"}
temExt -->|"false"| chave["chave =<br/>extensao['UNMASKED_RENDERER_WEBGL']"]
chave --> ehNum{"typeof chave !==<br/>'number'"}
ehNum -->|"false"| nome["contexto.getParameter(chave)"]
nome --> ehStr{"typeof nome === 'string'<br/>&& nome !== ''"}
ehStr -->|"true"| revela["o nome relatado por este<br/>aparelho"]
temExt -->|"true"| naoSabe["'placa não identificada<br/>(o navegador não expõe o<br/>nome do adaptador)'"]
ehNum -->|"true"| naoSabe
ehStr -->|"false"| naoSabe
end
subgraph cabecalho["a máquina antes do número"]
direction TB
folha["linhasDaMedicao(censo,<br/>orcamento.ler(), placa)"] --> cab["MAQUINA_DE_REFERENCIA.descricao,<br/>.navegador e o teto de<br/>16.7 ms na mesma folha"]
cab --> temQuadro{"leitura.quadrosMedidos<br/>=== 0"}
temQuadro -->|"true"| adiar["'Ainda sem quadros<br/>medidos: a leitura<br/>dinâmica entra depois do<br/>primeiro quadro.'"]
end
subgraph leitura["a leitura"]
direction TB
folga["folga = leitura.tetoMs -<br/>leitura.intervaloMedioMs"] --> sinal{"folga >= 0"}
sinal -->|"true"| ms["'… ms de folga'"]
sinal -->|"false"| estouro["'… ms acima do teto',<br/>com o sinal invertido<br/>para não imprimir menos"]
ms --> pior["pior intervalo da<br/>janela, que a média<br/>esconderia"]
estouro --> pior
pior --> dif["censo.triangulos -<br/>leitura.triangulos =<br/>13484 - 8108 = 5376, o<br/>nível de perto da<br/>prateleira, desligado"]
end
placa -->|"o nome ou a recusa"| cabecalho
cabecalho -->|"false, 120 quadros"| leitura
linhasDaMedicao(), que escreve a máquina antes de qualquer número.
1.4.11 linhasDaMedicao()
| Recebe | Decide | Devolve |
|---|---|---|
o censo, o que orcamento.ler() devolveu e o nome da placa |
o cabeçalho com a máquina sai sempre; sem quadro medido, termina antes da média; a diferença entre o teto (o tempo que o programa tem para preparar cada quadro, escrito no código como número) e o intervalo médio entre um quadro e o seguinte sai em milissegundos, escrita como “de folga” ou como “acima do teto” | as linhas da medição: o teto de 16,7 ms; 120 quadros medidos; 14 chamadas e 8.108 triângulos desenhados |
medicao.ts
export function linhasDaMedicao(
censo: Censo,
leitura: LeituraDoOrcamento,
placa: string,
): string[] {
const linhas: string[] = [
`Máquina de referência: ${MAQUINA_DE_REFERENCIA.descricao}`,
`Navegador previsto: ${MAQUINA_DE_REFERENCIA.navegador}`,
`Placa relatada por este aparelho: ${placa}`,
`Teto declarado por quadro: ${leitura.tetoMs.toFixed(1)} ms`,
'',
`Censo estático da cena: ${censo.triangulos} triângulos em ${censo.malhas} malhas soltas e ` +
`${censo.lotesInstanciados} lotes instanciados (${censo.instancias} instâncias)`,
`Chamadas de desenho previstas pelo censo: ${censo.chamadasPrevistas}`,
`Materiais distintos: ${censo.materiaisDistintos}`,
];
if (leitura.quadrosMedidos === 0) {
linhas.push('', 'Ainda sem quadros medidos: a leitura dinâmica entra depois do primeiro quadro.');
return linhas;
}
// A folga sai em milissegundos, a unidade em que se decide o que ainda cabe no
// quadro, e não em fração do teto.
const folga: number = leitura.tetoMs - leitura.intervaloMedioMs;
linhas.push(
'',
`Medido em ${leitura.quadrosMedidos} quadros: ${leitura.chamadasDeDesenho} ${leitura.chamadasDeDesenho === 1 ? 'chamada' : 'chamadas'} e ` +
`${leitura.triangulos} triângulos efetivamente desenhados`,
`Intervalo médio: ${leitura.intervaloMedioMs.toFixed(1)} ms — ` +
`${folga >= 0 ? `${folga.toFixed(1)} ms de folga` : `${(-folga).toFixed(1)} ms acima do teto`}`,
`Pior intervalo da janela: ${leitura.piorIntervaloMs.toFixed(1)} ms`,
`Diferença entre censo e medição: ${censo.triangulos - leitura.triangulos} triângulos não foram ` +
'desenhados neste quadro, por estarem fora do campo de visão ou num nível de detalhe desligado, e voltam quando a câmera se move',
);
return linhas;
}
1.4.12 montarAtivosECusto()
| Recebe | Decide | Devolve |
|---|---|---|
| o lugar da tela em que a folha é escrita e as quatro listas de linhas | a ordem das quatro partes, sempre a mesma, e a medição num bloco de texto que guarda as quebras de linha | nada: reescreve a folha inteira, o que nesta execução acontece três vezes |
custo.ts
/**
* A folha de ativos e de custo: o laudo da importação, o que a instanciação
* comprou, os dois níveis da prateleira e a medição com a máquina ao lado.
*
* As quatro partes ficam numa folha só para que o número de desempenho nunca
* apareça longe da máquina em que foi obtido. A medição chega como linhas
* unidas por quebra de linha, e por isso vai num bloco `pre`.
*/
export function montarAtivosECusto(
raiz: HTMLElement,
ativo: readonly string[],
repeticao: readonly string[],
niveis: readonly string[],
medicao: readonly string[],
): void {
raiz.replaceChildren();
raiz.appendChild(subtitulo('O ativo que veio de fora, e o que foi ajustado nele'));
for (const linha of ativo) {
raiz.appendChild(paragrafo(linha));
}
raiz.appendChild(subtitulo('Repetição tratada como repetição'));
for (const linha of repeticao) {
raiz.appendChild(paragrafo(linha));
}
raiz.appendChild(subtitulo('Detalhe cobrado por distância'));
for (const linha of niveis) {
raiz.appendChild(paragrafo(linha));
}
raiz.appendChild(subtitulo('A medição, e a máquina em que ela foi obtida'));
const bloco: HTMLPreElement = document.createElement('pre');
bloco.textContent = medicao.join('\n');
raiz.appendChild(bloco);
}
1.5 Uma execução, passo a passo
As abas seguem a execução desde a abertura da página do módulo, sem mover a câmera, até o clique em Medir agora. As três primeiras são da montagem, o momento em que a cena é construída, antes do primeiro quadro. Em cada aba, o que roda naquele passo tem borda grossa e fundo amarelo, o que entra na cena ou na folha tem borda mais grossa e fundo verde, e o que já rodou tem borda tracejada e fundo cinza.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2 ativo
class B1 novo
disporNaBorda() devolve 206 posições, montarLoteInstanciado() monta com elas um lote de 206 cópias, que a página do módulo pendura no tampo, e custoDaRepeticao() guarda 206 chamadas sem instanciar contra 1 instanciando.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1 usado
class P0 ativo
class P1 novo
simplificar() une os 338 vértices da engrenagem em 114 e tira 57, e cada peça passa de 224 a 110 triângulos. montarPrateleira() monta dois lotes de 24 peças, com o de longe desligado, e a página do módulo os pendura na sala.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1 usado
class F0 novo
montarAtivosECusto() escreve a folha pela primeira vez: o laudo diz “O ativo externo ainda não foi trazido.”, e a medição diz que sai no clique em “Medir agora”. A página do módulo pede a morsa e segue sem esperar por ela.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0 usado
class L0 ativo
atualizar() mede 4,45 m da câmera ao centro da prateleira; como 4,45 passa de 2,2, o lote de perto fica desligado e o de longe, ligado.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0,L0 usado
class M0 ativo
importarAtivo() mede a caixa envolvente do que veio: 18 × 12 × 17 unidades do arquivo, em X, Y e Z. É a linha “Como chegou” do laudo.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0,L0,M0 usado
class M1 ativo
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0,L0,M0,M1 usado
class M2 ativo
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0,L0,M0,M1,M2 usado
class M3 ativo
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0,L0,M0,M1,M2,M3 usado
class M4 novo
apontarDivergencias() escreve três divergências: 100 unidades por metro, o giro de −90 graus e o deslocamento de 0,30 m. A morsa entra no tampo, com a base encostada no lado de cima dele, e a folha é reescrita com o laudo.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph SB["montagem: a borda"]
direction LR
B0["'disporNaBorda()'<br/>206 posições na borda"] --> B1["'montarLoteInstanciado()'<br/>um lote com 206 cópias no tampo"] --> B2["'custoDaRepeticao()'<br/>206 chamadas contra 1"]
end
subgraph SP["a prateleira e a 1ª folha"]
direction LR
P0["'simplificar()'<br/>338 vértices, 114 unidos, 57 a tirar"] --> P1["'montarPrateleira()'<br/>dois lotes de 24 peças"] --> F0["'montarAtivosECusto()'<br/>a folha, ainda sem laudo"]
end
subgraph SL["o laço, a cada quadro"]
direction LR
L0["'atualizar()'<br/>câmera a 4,45 m: lote de longe"]
end
subgraph SA["a morsa chega e se ajusta"]
direction LR
M0["caixa como chegou<br/>18 × 12 × 17"] --> M1["giro de −90° em X<br/>caixa 18 × 17 × 12"] --> M2["fator = 0,18 ÷ 18 = 0,0100<br/>caixa 0,18 × 0,17 × 0,12 m"]
end
subgraph SD["assenta e ganha o laudo"]
direction LR
M3["base 0,30 m acima do zero<br/>correção de 0,30 m para baixo"] --> M4["'apontarDivergencias()'<br/>três divergências; morsa no tampo"]
end
subgraph SC["clique em Medir agora"]
direction LR
C0["'censoDaCena()'<br/>13.484 triângulos, 15 chamadas"] --> C2["'linhasDaMedicao()'<br/>a medição entra na folha"]
C1["'ler()'<br/>14 chamadas, 8.108 triângulos"] --> C2
end
SB --> SP --> SL --> SA --> SD --> SC
classDef ativo fill:#fff1b8,stroke:#8a5a00,stroke-width:3px,color:#111
classDef usado fill:#ececec,stroke:#7a7a7a,stroke-width:1px,stroke-dasharray:5 3,color:#444
classDef novo fill:#d3efd3,stroke:#1d6b1d,stroke-width:4px,color:#111
class B0,B2,B1,P0,P1,F0,L0,M0,M1,M2,M3,M4 usado
class C0,C1 ativo
class C2 novo
A folha depois do clique, como a página do módulo a escreve. As duas linhas de intervalo (o tempo entre um quadro e o seguinte) vêm com … no lugar dos números, porque medem a máquina em que a página rodou, e não o código; pela mesma razão, a folga pode sair como “acima do teto”, e a linha da placa muda de um aparelho para outro:
O ativo que veio de fora, e o que foi ajustado nele
Arquivo: /ativos/morsa.gltf
Como chegou: 18.00 x 12.00 x 17.00 unidades do arquivo
Como ficou: 18.0 x 17.0 x 12.0 cm
Fator de escala aplicado: 0.0100 (medido, não digitado)
Conteúdo: 4 malhas, 48 triângulos, 2 materiais (FerroFundido, AcoUsinado)
Animações no arquivo: AbrirFechar (3.0 s)
Unidade divergente: o arquivo mede 18.00 para uma peça de 0.18 m, o que dá 100 unidades por metro. O formato de troca fixa metro, e o exportador não obedeceu.
Eixo vertical divergente: o ativo veio com o vertical em outro eixo, e foi girado -90 graus em X para o vertical da cena ser Y.
Origem divergente: a peça vinha deslocada 0.30 m da própria base, e a correção a assentou na superfície de apoio.
Repetição tratada como repetição
Parafusos na borda: 206, a cada 2 cm de perímetro
Chamadas de desenho sem instanciar: 206
Chamadas de desenho instanciando: 1
Triângulos: 24 por parafuso, 4944 no total — e este número não muda com a instanciação, porque ela não é sobre triângulo
Detalhe cobrado por distância
Peças de reposição na prateleira: 24, em dois níveis instanciados
Nível de perto: 224 triângulos por peça, 5376 no total
Nível de longe: 110 triângulos por peça, 2640 no total (51% a menos)
Troca a 2.2 m da câmera — simplificada pela ferramenta, com as normais recalculadas
A engrenagem da bancada é a mesma malha e não recebe nível nenhum: ela fica a meio metro do olho, e simplificar o que está perto é trabalho jogado fora.
A medição, e a máquina em que ela foi obtida
Máquina de referência: desktop x64 do laboratório, com vídeo integrado e sem placa dedicada
Navegador previsto: Chrome com WebGL 2 habilitado
Placa relatada por este aparelho: ANGLE (Intel, Intel(R) Graphics (0x00007D67) Direct3D11 vs_5_0 ps_5_0, D3D11)
Teto declarado por quadro: 16.7 ms
Censo estático da cena: 13484 triângulos em 12 malhas soltas e 3 lotes instanciados (254 instâncias)
Chamadas de desenho previstas pelo censo: 15
Materiais distintos: 6
Medido em 120 quadros: 14 chamadas e 8108 triângulos efetivamente desenhados
Intervalo médio: … ms — … ms de folga
Pior intervalo da janela: … ms
Diferença entre censo e medição: 5376 triângulos não foram desenhados neste quadro, por estarem fora do campo de visão ou num nível de detalhe desligado, e voltam quando a câmera se move1.6 Por que o código é assim
As decisões que o próprio código registra, cada uma no identificador em que mora; a última, que não mora num identificador só, na página do módulo.
CONVENCAO_DA_CENA
Toda importação é medida contra a convenção da cena, e quem se ajusta é o ativo que chega, nunca a convenção. Sem ela escrita, não há contra o que acusar divergência.
maiorLadoRealM
O fator de escala sai da medida real da peça por divisão, e por isso não é digitado: um 0,01 escrito à mão serve ao arquivo em centímetro e erra, sem aviso, no primeiro ativo em milímetro.
importarAtivo()
A ordem é fixa: girar, escalar e só então medir o deslocamento. Assentar antes de girar apoia a peça pelo lado que vai virar lateral, e ela fica flutuando ou enterrada, sem nenhum aviso.
apontarDivergencias()
O critério é de tolerância, e não de igualdade: um fator de 0,998 é ruído de exportação, e a lista existe para acusar troca de unidade, que erra por ordem de grandeza. O eixo trocado é acusado sempre que o giro declarado não é zero.
linhasDoAtivo()
A linha “Como chegou” fica em unidades do arquivo, e a linha “Como ficou”, em centímetros: com as duas em unidades diferentes, a troca de unidade aparece na própria folha.
montarLoteInstanciado() e custoDaRepeticao()
A matriz de cada cópia é escrita uma vez, na montagem da cena, antes do primeiro quadro, e não a cada quadro. A medida certa da repetição é a chamada, e não o triângulo: o que a instanciação tira da conta é a preparação fixa de cada objeto.
simplificar()
A ferramenta só sabe que dois lados planos se tocam quando os dois usam os mesmos vértices na aresta comum, e a malha desta cena dá a cada lado plano cópias próprias dos pontos dos cantos. A união da biblioteca só junta vértices iguais em tudo, e as cópias de um canto diferem na normal: por isso a normal e a coordenada de textura (o ponto da imagem colada que cai sobre o vértice) saem antes de unir. Depois, as normais se recalculam: a antiga daria uma iluminação que não corresponde à forma, e o defeito apareceria como mancha.
montarPrateleira()
A troca liga um lote e desliga o outro. Trocar objeto por objeto pediria um objeto por peça, uma chamada por peça, e desfaria a instanciação. A distância medida é a da câmera ao centro da prateleira: com meio metro de largura, a diferença entre a peça mais próxima e a mais distante não muda a decisão.
censoDaCena()
Lote e malha solta ficam em campos separados, porque somados num número só não mostrariam o que a instanciação mudou. O material se conta pelo uuid, o identificador de cada objeto de material, e não pelo nome, que dois materiais diferentes podem repetir.
identificarPlaca() e linhasDaMedicao()
Um nome de placa inventado faria quem lê acreditar que a medição pode ser repetida, e por isso a recusa sai por extenso. A máquina sai antes de qualquer número, e sem quadro medido a função para antes da média, em vez de imprimir 0,0 ms, que se leria como medição ótima.
TETO_DO_QUADRO_MS
A página do módulo roda só na tela do computador, e por isso mede contra o teto da tela do computador, 16,7 ms. Numa tela de sessenta quadros por segundo, o navegador entrega um quadro a cada 16,7 ms, por mais leve que a cena seja, e contra os 11,1 ms do visor (o aparelho preso à cabeça que cobre os olhos), o teto que a cena adota quando roda nos dois aparelhos, todo quadro passaria do teto.
A cena é montada e desenhada antes de o ativo chegar, e continua de pé se ele não chegar. A medição sai no clique, e não na abertura da página, porque ela só tem valor depois de alguns quadros desenhados de verdade.
1.7 Como o código se confere
O módulo não tem bateria de testes: quem confere o código é a própria folha, e o botão de aproximar, seguido de um segundo clique em Medir agora, mostra a troca de nível.
| O que confere | O que traz | Quando algo falha | Nesta execução |
|---|---|---|---|
| o laudo | as linhas “Como chegou” e “Como ficou”, em unidades diferentes, e a lista de divergências, que, sem nenhuma (fator e deslocamento dentro da tolerância e giro zero), escreve “Nenhuma divergência acima da tolerância: o ativo chegou na convenção.” | ativo que não chega: a folha escreve “O ativo externo não pôde ser trazido.” e a mensagem do erro | 18,00 × 12,00 × 17,00 unidades contra 18,0 × 17,0 × 12,0 cm, e três divergências |
| as linhas da repetição | as chamadas sem instanciar e instanciando, e os triângulos, que não mudam | — | 206 chamadas contra 1; 4.944 triângulos nas duas |
| as linhas dos níveis | os triângulos dos dois níveis, e a observação de simplificar() |
malha pequena: “malha pequena demais para valer a simplificação; o original ficou”; ferramenta sem malha utilizável: “a ferramenta não devolveu malha utilizável; o original ficou no lugar” | 224 e 110 triângulos por peça, “simplificada pela ferramenta, com as normais recalculadas” |
| o censo contra a leitura | a diferença, que é o que não foi desenhado, e nunca erro de contagem | sem quadro medido: “Ainda sem quadros medidos: a leitura dinâmica entra depois do primeiro quadro.”; placa que o navegador não informa: “placa não identificada” | 13.484 − 8.108 = 5.376, o lote de perto desligado |
| o botão Aproximar da prateleira do fundo, seguido de Medir agora | a câmera a 1,2 m do ponto para onde ela olha na prateleira, e a 1,29 m do centro dela, abaixo dos 2,2 m da troca | — | “Medido em 120 quadros: 1 chamada e 5376 triângulos efetivamente desenhados”: só o lote de perto, ligado |
Exemplo deste infográfico: da abertura da página do módulo, sem mover a câmera, com a morsa chegando, até um clique em Medir agora, e o botão de aproximar na última conferência; execução de 27/10/2026.