%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph S1["a forma da grande"]
direction LR
P["'perfilDeEngrenagem()'<br/>14 dentes, 56 pontos"] --> E["'extrudarPerfil()'<br/>338 vértices<br/>224 triângulos"]
end
subgraph S2["a árvore vestida"]
direction LR
A["'montarCena()'<br/>a árvore do módulo anterior"] --> C["'vestirCena()'<br/>forma, superfície<br/>e volume de cada peça"]
end
subgraph S3["o teste e o relatório"]
direction LR
D["'compararFolgas()'<br/>um ponto a 12 cm"] --> F["'montarConteudo()'<br/>escreve o relatório"]
end
S1 --> S2 --> S3
S3 --> K["o relatório na tela<br/>440 triângulos nas cinco peças, 476 na cena<br/>4 de 4 superfícies, aplicadas a 8 malhas<br/>5 caixas, cada folga com a razão<br/>folga de 2,0 cm: não captura<br/>folga de 6,0 cm: captura"]
1 Técnicas de modelagem de ambientes virtuais — Infográfico do código
O código deste módulo escreve ponto a ponto a forma de três peças de um ambiente virtual (um lugar desenhado pelo computador, em que dá para olhar em volta e mexer nas coisas), declara de que cada peça é feita e guarda, para cada peça que alguém vai pegar, uma caixa invisível contra a qual se testa se a mão encostou nela. Este infográfico acompanha uma execução só: a abertura da página do módulo, a que esse código monta no navegador, até um clique em Mostrar os volumes de contato.
1.1 O caminho
Da figura plana da engrenagem grande, no alto, até o texto que a página do módulo escreve embaixo do desenho, 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 |
|---|---|---|
malha.ts |
o tipo Ponto2; a classe ConstrutorDeMalha, com vertice(), triangulo(), quadrilatero() e finalizar(); as funções contarTriangulos(), contarVertices(), extrudarPerfil(), perfilDeEngrenagem() e perfilRegular(); e as auxiliares polar(), ponto() e indiceDe() |
BufferGeometry e Float32BufferAttribute, da biblioteca |
materiais.ts |
os tipos DescricaoDeMaterial, MaterialDaOficina, Catalogo, UsoDaSuperficie e UsoDoCatalogo, e as funções superficie(), montarCatalogo(), censoDoCatalogo(), itensDoCatalogo() e linhasDoCatalogo() |
MeshStandardMaterial, da biblioteca |
colisao.ts |
o tipo VolumeDeContato; as funções volumeDaMalha(), volumeDeclarado(), contemPonto(), distanciaAoCentro() e contornoDoVolume(); e a classe RegistroDeContato, com registrar(), volume(), alternarExibicao() e linhas() |
Box3, EdgesGeometry e LineSegments, da biblioteca |
pecas.ts |
os tipos LinhaDoInventario, Conteudo e ComparacaoDeFolga; as constantes ESPESSURA_DA_ENGRENAGEM (0,016 m) e FOLGAS; e as funções exigirMalha(), vestirCena(), trocarMaterial(), trocarMalha() e compararFolgas() |
o tipo CenaDaBancada, do módulo do grafo de cena; o tipo PecaId, do primeiro módulo |
conteudo.ts |
montarConteudo(), que escreve o relatório |
paragrafo() e subtitulo(), do módulo de dispositivos |
cena.ts |
uma versão nova do arquivo do módulo do grafo de cena, com uma mudança só: repousoDaPeca() devolve, para a engrenagem pequena, x = 47 cm, 2 cm mais longe da grande |
montarCena(), que chama repousoDaPeca(), e o resto do arquivo, como estavam |
04_demo.ts |
a página do módulo: as constantes TETO_DO_QUADRO_MS (o mesmo valor de TETO_DESKTOP_MS), DISTANCIA_DE_TESTE_M (0,12) e FOLGA_GENEROSA_M (0,06); os seis botões; as funções criarNo(), criarBotao(), frasesDaFolga(), exigirMalhaDaCena(), exigirLuz(), geometriaComCascaInvertida() e enquadrar(); e o que roda a cada quadro, cada imagem que o aparelho mostra: o eixo gira, e a engrenagem grande também, quando Girar as peças está ligado |
montarPalco(), montarCena(), Relogio, Orcamento, TETO_DESKTOP_MS e montarLaco(), do módulo do grafo de cena |
volumeDeclarado() e distanciaAoCentro() estão escritas e não rodam na página do módulo: nenhum código deste módulo as chama.
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 malha
Cada canto da malha é um vértice. Enquanto a malha é escrita, ConstrutorDeMalha guarda as quatro listas dela: três listas paralelas (uma entrada por vértice em cada uma, e a linha 2 de cada uma fala do mesmo vértice) e a lista de triângulos. Um vértice entra nas três de uma vez, e vertice() devolve o número dele:
malha.ts
| Lista | O que guarda por entrada | Na engrenagem grande |
|---|---|---|
posicoes |
a posição: onde o ponto está, em x, y e z | 338 vértices, 1.014 números |
normais |
a normal: para que lado a superfície está virada naquele ponto; é essa direção que a luz consulta | 338 direções |
uvs |
a coordenada de textura: que ponto de uma imagem colada na peça cai sobre o vértice; a imagem colada é a textura, e nenhuma peça tem textura ainda | 338 pares |
indices |
cada triângulo (na malha, a face) pelos índices dos seus três vértices: o índice é o número de uma linha das outras três listas | 224 triângulos, 672 números |
A malha da grande, na ordem em que extrudarPerfil() pede cada vértice a vertice(): as duas tampas (o lado plano de cima e o de baixo) e a lateral, ou casca.
| Vértices | Onde ficam | Normal | Nesta execução |
|---|---|---|---|
| 0 | no centro da tampa de cima | (0, 1, 0): para cima | posição (0; 0,8; 0) cm, coordenada de textura (0,5; 0,5) |
| 1 a 56 | na borda da tampa de cima, um por ponto do perfil | (0, 1, 0) | o 1 em (6,8; 0,8; 0) cm |
| 57 | no centro da tampa de baixo | (0, −1, 0): para baixo | posição (0; −0,8; 0) cm |
| 58 a 113 | na borda da tampa de baixo | (0, −1, 0) | o 58 em (6,8; −0,8; 0) cm |
| 114 a 337 | na casca, quatro por trecho da borda | a do trecho, deitada no plano do chão | 114 a 117, o primeiro retângulo, com a normal (0,68; 0; −0,74) |
| Triângulos | Onde ficam | Nesta execução |
|---|---|---|
| 0 a 55 | na tampa de cima | o 0 é (0, 2, 1): centro, ponto seguinte, ponto atual |
| 56 a 111 | na tampa de baixo | o 56 é (57, 58, 59): centro, ponto atual, ponto seguinte |
| 112 a 223 | na casca, dois por retângulo | o 112 e o 113 são (114, 115, 116) e (114, 116, 117) |
Um ponto do perfil é um Ponto2, com x e z no plano do chão: o primeiro ponto da grande é (6,8; 0) cm.
1.3.2 As superfícies
A superfície de uma peça se declara pelo que ela é, com dois números de 0 a 1: a metalicidade diz quanto ela se comporta como metal, que reflete o entorno, e a rugosidade, quanto ela espalha o reflexo, de perto de 0, como um espelho, a perto de 1, como papel. A biblioteca chama essa descrição de material, e quatro superfícies são quatro materiais. Cada uma carrega duas frases, o que entrega e o que cobra:
materiais.ts
| Tipo e campo | O que guarda | Nesta execução |
|---|---|---|
DescricaoDeMaterial.nome, entrega, custo |
o nome, o que a superfície entrega ao olho e o que ela cobra | quatro descrições: aco-escovado, latao, plastico-fosco e madeira |
MaterialDaOficina.material |
a instância: cada material que o programa cria; duas peças podem apontar para a mesma, como dois cartões com o mesmo endereço | quatro instâncias, uma por superfície |
Catalogo |
o catálogo: as superfícies que o programa criou | acoEscovado, latao, plasticoFosco e madeira |
UsoDaSuperficie.malhas |
as malhas da cena que apontam para a instância | latao: engrenagem-grande e engrenagem-pequena |
UsoDoCatalogo.foraDoCatalogo |
as malhas cuja superfície não veio do catálogo | nenhuma |
| Superfície | Rugosidade | Metalicidade | Malhas que apontam para ela |
|---|---|---|---|
aco-escovado |
0,35 | 0,90 | base-do-suporte e eixo |
latao |
0,45 | 0,75 | engrenagem-grande e engrenagem-pequena |
plastico-fosco |
0,85 | 0,05 | corpo e tampa, a tampa do mecanismo |
madeira |
0,80 | 0,00 | tampo e cavalete |
1.3.3 Os volumes
A caixa de contato soma uma margem de cada lado da peça, e essa margem se chama folga. O volume guarda a caixa já com a folga, e a razão da folga ao lado do número:
colisao.ts
export interface VolumeDeContato {
/** O nó cuja forma visível este volume representa. */
readonly no: Mesh;
/** Meia-extensão da caixa, no espaço local do nó, já com a folga somada. */
readonly meiaExtensao: Vector3;
/** Centro da caixa no espaço local do nó. Raramente é a origem: malha assimétrica desloca. */
readonly centroLocal: Vector3;
/** Folga adotada, em metros, e a justificativa dela em uma frase. */
readonly folga: number;
readonly razaoDaFolga: string;
}
Nas razões, a silhueta é o recorte que sobraria se a peça fosse pintada de preto contra um fundo branco, e o “raio” da razão do eixo é a linha reta que sai do controle, e não o raio de um círculo.
| Campo | O que guarda | Nesta execução, na engrenagem grande |
|---|---|---|
no |
a peça cuja forma visível o volume representa | engrenagem-grande |
meiaExtensao |
a meia-extensão da caixa em cada direção, a distância do centro até a parede, já com a folga | 9,75 × 2,8 × 9,80 cm |
centroLocal |
o centro da caixa no espaço local da peça, em que tudo se mede a partir da própria peça: do zero dela e das três direções dela | (0, 0, 0); na pequena, que não é simétrica, 0,34 mm para o lado negativo de x e 0,23 mm para o positivo de z |
folga, razaoDaFolga |
a folga, em metros, e a frase que a justifica | 0,02 e “a peça mais larga que a mão apanha inteira, fechada sobre a silhueta: leva a maior folga” |
RegistroDeContato guarda os cinco volumes pela chave da peça, os cinco contornos de depuração (os desenhos em linhas que tornam cada caixa visível enquanto alguém constrói a cena) e exibindo, que começa false. As folgas saem de FOLGAS, uma por peça, cada uma com a sua razão:
| Peça | Folga | Razão escrita no código |
|---|---|---|
corpo |
1,2 cm | peça de apoio, apanhada pela lateral com a mão inteira |
eixo |
0,8 cm | peça fina: com folga maior, ela capturaria de longe o raio dirigido à vizinha |
engrenagem-grande |
2,0 cm | a peça mais larga que a mão apanha inteira, fechada sobre a silhueta: leva a maior folga |
engrenagem-pequena |
1,8 cm | mesma pega da grande, com um pouco menos por conviver perto dela |
tampa, a tampa do mecanismo |
1,2 cm | placa chata: sem folga vertical o dedo passa por cima sem tocar |
| Tipo e campo | O que guarda | Nesta execução |
|---|---|---|
LinhaDoInventario |
a peça, a origem da forma e as contagens; autorada é a forma escrita pelo programa, e primitiva, a caixa pedida pronta à biblioteca | cinco linhas; a da grande: autorada, 224 triângulos, 338 vértices |
Conteudo.totalDeTriangulos, triangulosDaCena |
os triângulos das cinco peças e os da cena inteira, com o mobiliário | 440 e 476 |
ComparacaoDeFolga |
a distância do ponto de teste, as duas folgas e o veredito de cada uma: o volume captura o ponto quando ele fica dentro da caixa | 0,12 m; 0,02 m, false; 0,06 m, true |
1.3.4 A árvore da cena
Cada objeto da cena é um nó. Um nó pode ficar pendurado em outro, o pai dele, e acompanha o pai quando ele se move; os nós pendurados uns nos outros formam a árvore da cena. Esta é a árvore desta execução, antes do clique:
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
S["'sala'"] --> L["duas luzes<br/>'luz-ambiente', 'luz-direcional'"]
S --> B["'bancada'"]
B --> T["'tampo'<br/>madeira"]
B --> CA["'cavalete'<br/>madeira"]
T --> SU["'suporte'"] --> BS["'base-do-suporte'<br/>aço escovado"]
T --> PE["as cinco peças<br/>'corpo', 'eixo', 'engrenagem-grande',<br/>'engrenagem-pequena', 'tampa'"]
PE --> VO["um contorno em cada peça<br/>'volume-corpo' … 'volume-tampa'<br/>escondidos"]
T --> PA["'suporte-do-painel'"]
T --> DE["'engrenagem-grande-com-defeito'<br/>escondida"]
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 VO,DE novo
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 perfilDeEngrenagem() e perfilRegular()
| Recebe | Decide | Devolve |
|---|---|---|
| 14 dentes, raiz de 0,068 m e crista de 0,078 m; no eixo, 12 lados e raio de 0,012 m | cada dente ocupa uma fatia igual da volta e ganha quatro pontos: no começo da fatia, na raiz, a 6,8 cm do centro; a 25% e a 55% da fatia, na crista, a 7,8 cm; e a 80%, de volta à raiz. perfilRegular() põe um ponto a cada fatia igual da volta |
56 pontos na grande, 36 na pequena e 12 no eixo, percorridos no sentido horário vistos de cima |
No código, a fatia de cada dente se chama passo, e o raio da crista, raioDoTopo:
malha.ts
/**
* Perfil de uma engrenagem de dentes retos, com quatro pontos por dente: sobe ao
* topo, atravessa a crista, desce à raiz e atravessa o vale.
*
* O número de dentes NÃO é botão de densidade: é forma. Ele fixa, junto com o
* da engrenagem parceira, a razão de transmissão do mecanismo, e trocá-lo para
* poupar triângulos muda o mecanismo. O custo existe do mesmo jeito — cada dente
* acrescenta dezesseis triângulos à malha final —, mas é custo de forma, pago
* porque a peça é aquela. Resolução, aqui, são os quatro pontos por dente: mais
* pontos arredondariam o flanco sem mudar o que a peça é.
*/
export function perfilDeEngrenagem(
dentes: number,
raioDaRaiz: number,
raioDoTopo: number,
): Ponto2[] {
const pontos: Ponto2[] = [];
const passo: number = (Math.PI * 2) / dentes;
for (let k = 0; k < dentes; k += 1) {
const inicio: number = k * passo;
pontos.push(polar(raioDaRaiz, inicio));
pontos.push(polar(raioDoTopo, inicio + passo * 0.25));
pontos.push(polar(raioDoTopo, inicio + passo * 0.55));
pontos.push(polar(raioDaRaiz, inicio + passo * 0.8));
}
return pontos;
}
/** Perfil de um prisma regular. É dele que o eixo do mecanismo tira os seus doze lados. */
export function perfilRegular(lados: number, raio: number): Ponto2[] {
const pontos: Ponto2[] = [];
for (let k = 0; k < lados; k += 1) {
pontos.push(polar(raio, (k * Math.PI * 2) / lados));
}
return pontos;
}
1.4.2 extrudarPerfil()
| Recebe | Decide | Devolve |
|---|---|---|
o perfil de 56 pontos, a espessura de 0,016 m e o nome 'engrenagem-grande' |
três blocos, nesta ordem: a tampa de cima, com a ordem dos pontos invertida; a de baixo, na ordem natural; e a casca, com a normal de cada trecho apontando para fora e a coordenada de textura acompanhando a distância percorrida na borda | uma malha de 338 vértices e 224 triângulos |
As duas tampas saem deste trecho; no primeiro laço, o da tampa de cima, cada triângulo cita o ponto seguinte antes do atual, e no último, o da tampa de baixo, o atual antes do seguinte:
malha.ts
for (let i = 0; i < n; i += 1) {
construtor.triangulo(centroTopo, indiceDe(topo, i + 1), indiceDe(topo, i));
}
const centroBase: number = construtor.vertice(0, -meia, 0, 0, -1, 0, 0.5, 0.5);
const base: number[] = [];
for (let i = 0; i < n; i += 1) {
const p: Ponto2 = ponto(perfil, i);
base.push(
construtor.vertice(
p.x,
-meia,
p.z,
0,
-1,
0,
0.5 + p.x / (2 * raioMaximo),
0.5 - p.z / (2 * raioMaximo),
),
);
}
for (let i = 0; i < n; i += 1) {
construtor.triangulo(centroBase, indiceDe(base, i), indiceDe(base, i + 1));
}
A casca sai deste outro; nx e nz são as duas linhas da normal:
malha.ts
// Casca lateral. Cada aresta do perfil vira um retângulo com normal própria,
// perpendicular a ela: é isso que dá o dente com flanco chapado em vez de
// ombro arredondado. A coordenada horizontal da textura acompanha o perímetro
// percorrido, e a vertical vai da base ao topo.
let percorrido: number = 0;
for (let i = 0; i < n; i += 1) {
const p: Ponto2 = ponto(perfil, i);
const q: Ponto2 = ponto(perfil, i + 1);
const comprimento: number = Math.hypot(q.x - p.x, q.z - p.z);
// Normal para fora: o produto vetorial da direção vertical pela direção da
// aresta. Na ordem inversa ela apontaria para dentro da peça, e a luz
// acertaria a face pelo lado errado — a peça fica escura e ninguém acha o
// motivo, porque a silhueta continua perfeita.
const nx: number = (q.z - p.z) / comprimento;
const nz: number = -(q.x - p.x) / comprimento;
const u0: number = percorrido / perimetro;
const u1: number = (percorrido + comprimento) / perimetro;
const a: number = construtor.vertice(p.x, meia, p.z, nx, 0, nz, u0, 1);
const b: number = construtor.vertice(q.x, meia, q.z, nx, 0, nz, u1, 1);
const c: number = construtor.vertice(q.x, -meia, q.z, nx, 0, nz, u1, 0);
const d: number = construtor.vertice(p.x, -meia, p.z, nx, 0, nz, u0, 0);
construtor.quadrilatero(a, b, c, d);
percorrido += comprimento;
}
%%{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: a tampa de cima"]
direction TB
A1["um vértice no centro e um em cada ponto<br/>normal para cima, y = 0,8 cm"] --> A2["um triângulo por trecho da borda<br/>centro, ponto seguinte, ponto atual<br/>57 vértices, 56 triângulos"]
end
subgraph K2["caso 2: a tampa de baixo"]
direction TB
B1["um vértice no centro e um em cada ponto<br/>normal para baixo, y = −0,8 cm"] --> B2["um triângulo por trecho da borda<br/>centro, ponto atual, ponto seguinte<br/>57 vértices, 56 triângulos"]
end
subgraph K3["caso 3: a casca"]
direction TB
C1["um retângulo em pé por trecho<br/>quatro vértices próprios<br/>a mesma normal nos quatro"] --> C2["'quadrilatero()': dois triângulos<br/>224 vértices, 112 triângulos"]
end
K1 ~~~ K2 ~~~ K3
1.4.3 contarTriangulos()
| Recebe | Decide | Devolve |
|---|---|---|
| a malha de uma peça | dois casos: com lista de índices, os triângulos são os índices divididos por 3; sem ela, as posições divididas por 3 | 224 na grande; nesta execução toda malha tem lista de índices, e o segundo caso não roda |
1.4.4 vestirCena() e exigirMalha()
Trocar o que há dentro de nós que já existem é o que o código chama de vestir a árvore.
| Recebe | Decide | Devolve |
|---|---|---|
a cena que montarCena() montou |
exigirMalha() para a execução se a cena não trouxer a peça como malha; a grande, a pequena e o eixo ganham malha escrita, e o corpo e a tampa do mecanismo ficam com a caixa pronta; cada peça busca a sua folga em FOLGAS, e peça sem folga para a execução; a origem é 'primitiva' para o corpo e a tampa do mecanismo, e 'autorada' para as outras três |
um Conteudo com cinco linhas de inventário, 440 triângulos nas cinco peças e 476 na cena |
A busca da folga, peça a peça, e o inventário:
pecas.ts
for (const [id, malha] of [
['corpo', corpo],
['eixo', eixo],
['engrenagem-grande', grande],
['engrenagem-pequena', pequena],
['tampa', tampa],
] as ReadonlyArray<readonly [PecaId, Mesh]>) {
const folga = FOLGAS.get(id);
if (folga === undefined) {
throw new Error(`A peça "${id}" está na cena e não tem folga de contato declarada.`);
}
contato.registrar(id, volumeDaMalha(malha, folga.metros, folga.razao));
inventario.push({
peca: id,
origem: id === 'corpo' || id === 'tampa' ? 'primitiva' : 'autorada',
triangulos: contarTriangulos(malha.geometry),
vertices: contarVertices(malha.geometry),
});
}
1.4.5 trocarMalha() e trocarMaterial()
Liberar uma malha é avisar à biblioteca que a memória dela pode ser devolvida.
| Recebe | Decide | Devolve |
|---|---|---|
| o nó e a malha nova, ou o material novo | trocarMalha() libera a malha antiga antes de pôr a nova; trocarMaterial() libera o material antigo só quando ele não é o novo |
nada; nesta execução, três malhas trocadas e oito materiais trocados; os oito materiais antigos foram liberados, porque nenhum deles era o novo |
pecas.ts
/**
* Troca o material de um nó liberando o anterior, pela mesma razão de
* `trocarMalha`. Liberar é seguro aqui porque cada material provisório da cena
* do módulo anterior pertence a uma malha só; material compartilhado não se
* libera na troca de um dos donos.
*/
function trocarMaterial(no: Mesh, novo: MeshStandardMaterial): void {
const antigos: readonly Material[] = Array.isArray(no.material) ? no.material : [no.material];
for (const antigo of antigos) {
if (antigo !== novo) {
antigo.dispose();
}
}
no.material = novo;
}
/**
* Troca a malha de um nó liberando a anterior.
*
* Liberar não é zelo: geometria vive na memória da placa, e trocar sem liberar
* deixa a antiga lá. Numa troca isso não se nota; num ambiente que recarrega
* conteúdo várias vezes por sessão, a memória de vídeo acaba — e o sintoma é o
* navegador perder o contexto de desenho, que é a tela ficando preta sem
* mensagem de erro.
*/
function trocarMalha(no: Mesh, nova: Mesh['geometry']): void {
no.geometry.dispose();
no.geometry = nova;
}
1.4.6 montarCatalogo() e superficie()
| Recebe | Decide | Devolve |
|---|---|---|
nada; superficie() recebe o nome, a cor de base, a rugosidade, a metalicidade, a entrega e o custo |
quatro superfícies, cada uma criada uma vez | o Catalogo, com as quatro |
materiais.ts
// Metálico e pouco rugoso é o par que produz reflexo direcional. Ele depende
// inteiramente da luz da cena: sob uma fonte só, a peça metálica fica escura
// e chapada, e o grupo culpa o material quando o problema é a iluminação.
acoEscovado: superficie(
'aco-escovado',
0xb8bcc6,
0.35,
0.9,
'reflexo alongado que denuncia a direção da luz e dá leitura de peça usinada',
'metálico exige que a cena tenha o que refletir; sem ambiente, escurece',
),
No comentário, “o grupo” é quem está construindo a cena; no custo, “sem ambiente” quer dizer sem uma imagem do entorno para o metal refletir.
1.4.7 censoDoCatalogo() e linhasDoCatalogo()
| Recebe | Decide | Devolve |
|---|---|---|
| o catálogo e a sala, o nó de onde a árvore inteira pende | para cada nó: se não é malha, pula; se aponta para uma instância do catálogo, entra na lista dela; se aponta para outra, vai para foraDoCatalogo. A conferência é por identidade, e um latão igual em valor, criado à parte, cairia fora. linhasDoCatalogo() conta só as superfícies em uso e monta a linha das malhas de fora do catálogo só quando ela não está vazia |
oito malhas nas quatro superfícies, nenhuma fora; a linha “Superfícies do catálogo em uso na cena: 4 de 4, aplicadas a 8 malhas” |
materiais.ts
/**
* Percorre a cena e anota, para cada superfície do catálogo, quais malhas
* apontam para a instância dela. A conferência é por identidade: um latão
* idêntico em valor, mas criado à parte, cai em `foraDoCatalogo`.
*/
export function censoDoCatalogo(catalogo: Catalogo, raiz: Object3D): UsoDoCatalogo {
const itens: readonly MaterialDaOficina[] = itensDoCatalogo(catalogo);
const porItem: Map<MaterialDaOficina, string[]> = new Map<MaterialDaOficina, string[]>();
for (const item of itens) {
porItem.set(item, []);
}
const fora: string[] = [];
raiz.traverse((no: Object3D) => {
if (!(no instanceof Mesh)) {
return;
}
const materiais: readonly Material[] = Array.isArray(no.material) ? no.material : [no.material];
for (const material of materiais) {
const item: MaterialDaOficina | undefined = itens.find((i) => i.material === material);
if (item === undefined) {
fora.push(no.name);
} else {
porItem.get(item)?.push(no.name);
}
}
});
return {
superficies: itens.map((item) => ({ item, malhas: porItem.get(item) ?? [] })),
foraDoCatalogo: fora,
};
}
O censo, a contagem que o próprio programa faz do que a cena construída tem, roda antes de a cópia com defeito existir: por isso ela, que aponta para o mesmo latão, não está nas oito.
%%{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: não é malha"]
direction TB
A1["'no instanceof Mesh' é falso"] --> A2["volta sem anotar nada<br/>11 dos 19 nós desta cena<br/>sala, luzes, grupos, contornos"]
end
subgraph K2["caso 2: do catálogo"]
direction TB
B1["'itens.find' acha a instância"] --> B2["a malha entra na lista dela<br/>8 malhas em 4 superfícies"]
end
subgraph K3["caso 3: fora do catálogo"]
direction TB
C1["'itens.find' não acha"] --> C2["a malha vai para 'foraDoCatalogo'<br/>nenhuma nesta execução"]
end
K1 ~~~ K2 ~~~ K3
1.4.8 volumeDaMalha()
Aqui, a geometria é a forma da peça, guardada na malha.
| Recebe | Decide | Devolve |
|---|---|---|
| a malha da grande, a folga de 0,02 m e a razão | a caixa sai da caixa envolvente da geometria, a menor caixa alinhada às direções da peça que contém todos os vértices; malha sem caixa envolvente para a execução; a folga se soma uma vez, ali | meia-extensão de 9,75 × 2,8 × 9,80 cm, que é a caixa de 19,5 × 5,6 × 19,6 cm do relatório |
colisao.ts
export function volumeDaMalha(no: Mesh, folga: number, razaoDaFolga: string): VolumeDeContato {
// A caixa sai da geometria, e não do nó montado na cena. Duas razões. A caixa
// da geometria já está no espaço local da peça, que é onde o volume tem de
// viver — assim ele gira, escala e é reparentado junto, de graça. E ela ignora
// o que estiver pendurado no nó, inclusive o contorno de depuração que este
// mesmo arquivo acrescenta: volume derivado do nó inteiro engordaria a cada
// vez que fosse recalculado.
no.geometry.computeBoundingBox();
const caixa: Box3 | null = no.geometry.boundingBox;
if (caixa === null) {
throw new Error(`A malha de "${no.name}" não tem caixa envolvente, e o volume sai dela.`);
}
const centroLocal: Vector3 = caixa.getCenter(new Vector3());
const tamanho: Vector3 = caixa.getSize(new Vector3());
return {
no,
meiaExtensao: new Vector3(
tamanho.x / 2 + folga,
tamanho.y / 2 + folga,
tamanho.z / 2 + folga,
),
centroLocal,
folga,
razaoDaFolga,
};
}
1.4.9 contemPonto()
| Recebe | Decide | Devolve |
|---|---|---|
| um volume e um ponto em coordenadas de mundo, em que tudo se mede a partir da cena | leva o ponto ao espaço local da peça, desconta o centro e compara cada coordenada, sem o sinal, com a meia-extensão: o ponto está dentro só se as três couberem | false contra a folga adotada e true contra a generosa |
colisao.ts
/**
* Um ponto do mundo está dentro do volume?
*
* O ponto é levado ao espaço local do nó antes do teste, e é essa conversão que
* torna o volume solidário à peça: a peça girada gira o volume junto, de graça.
* Testar em coordenadas de mundo obrigaria a recalcular a caixa a cada quadro, e
* a caixa recalculada de um objeto girado cresce — o volume aumenta sozinho
* quando a peça inclina, e a captura fica maior num ângulo do que no outro.
*/
export function contemPonto(volume: VolumeDeContato, pontoDeMundo: Vector3): boolean {
volume.no.updateWorldMatrix(true, false);
const local: Vector3 = volume.no.worldToLocal(pontoDeMundo.clone()).sub(volume.centroLocal);
return (
Math.abs(local.x) <= volume.meiaExtensao.x &&
Math.abs(local.y) <= volume.meiaExtensao.y &&
Math.abs(local.z) <= volume.meiaExtensao.z
);
}
1.4.10 contornoDoVolume() e RegistroDeContato
| Recebe | Decide | Devolve |
|---|---|---|
| um volume | contornoDoVolume() monta as 12 arestas da caixa em linhas verdes, pendura o contorno no nó da peça e o deixa escondido; registrar() guarda o volume e o contorno; alternarExibicao() inverte exibindo e aplica o valor aos cinco contornos; linhas() monta uma linha de texto por volume, em centímetros, com a folga e a razão |
cinco contornos escondidos; no clique, alternarExibicao() devolve true |
colisao.ts
/**
* A exibição de depuração dos volumes: um contorno em linha, filho do próprio nó.
*
* Isto não é enfeite. O volume é a única parte do ambiente que decide
* comportamento sem aparecer, e um recurso barato que o torna visível durante a
* construção poupa a confusão de quando a manipulação chegar — a peça que gruda
* longe e a que atravessa o encaixe têm o mesmo sintoma para quem olha só a tela.
*/
export function contornoDoVolume(volume: VolumeDeContato): LineSegments {
const caixa: BoxGeometry = new BoxGeometry(
volume.meiaExtensao.x * 2,
volume.meiaExtensao.y * 2,
volume.meiaExtensao.z * 2,
);
const contorno: LineSegments = new LineSegments(
new EdgesGeometry(caixa),
new LineBasicMaterial({ color: 0x4ade80 }),
);
contorno.name = `volume-${volume.no.name}`;
contorno.position.copy(volume.centroLocal);
contorno.visible = false;
volume.no.add(contorno);
caixa.dispose();
return contorno;
}
1.4.11 compararFolgas() e frasesDaFolga()
| Recebe | Decide | Devolve |
|---|---|---|
| o conteúdo, 0,12 m e 0,06 m | sem volume da grande, para a execução; põe o ponto de teste 12 cm adiante do centro da grande, na direção x, e o testa contra o volume registrado e contra um volume novo, de folga 6,0 cm; frasesDaFolga() monta a frase com “captura” ou “não captura”, conforme cada resultado |
capturaComAFolgaAdotada igual a false, capturaComAFolgaGenerosa igual a true, e a frase do relatório |
pecas.ts
/**
* A demonstração dos dois defeitos simétricos, medida em vez de descrita.
*
* Um ponto de teste é colocado a uma distância declarada da engrenagem grande,
* ao lado dela, e a pergunta é se o volume o captura. Com a folga adotada, não
* captura: o ponto está fora, e é isso o que se quer, porque ali a mão ainda não
* encostou. Com uma folga generosa, captura — e a peça passa a grudar em quem
* apenas passou perto.
*
* O defeito oposto não precisa de demonstração própria: é o mesmo teste com o
* ponto encostado na ponta de um dente e a folga em zero: a caixa vira a
* extensão exata da malha, e o ponto fica na borda dela, recusado ao menor desvio.
*/
export function compararFolgas(
conteudo: Conteudo,
distanciaDeTeste: number,
folgaGenerosa: number,
): ComparacaoDeFolga {
const volume: VolumeDeContato | undefined = conteudo.contato.volume('engrenagem-grande');
if (volume === undefined) {
throw new Error('A engrenagem grande não tem volume registrado, e a comparação depende dele.');
}
const centro: Vector3 = volume.no.localToWorld(volume.centroLocal.clone());
const pontoDeTeste: Vector3 = centro.clone().add(new Vector3(distanciaDeTeste, 0, 0));
const generoso: VolumeDeContato = volumeDaMalha(
volume.no,
folgaGenerosa,
'folga deliberadamente exagerada, só para a comparação',
);
return {
distanciaDeTeste,
folgaAdotada: volume.folga,
capturaComAFolgaAdotada: contemPonto(volume, pontoDeTeste),
folgaGenerosa,
capturaComAFolgaGenerosa: contemPonto(generoso, pontoDeTeste),
};
}
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart LR
subgraph K1["folga adotada: 2,0 cm"]
direction TB
A1["meia-extensão na direção x<br/>7,75 + 2,0 = 9,75 cm"] --> A2["o ponto está em x = 12 cm<br/>12 passa de 9,75"] --> A3["'contemPonto()' devolve false<br/>não captura"]
end
subgraph K2["folga generosa: 6,0 cm"]
direction TB
B1["meia-extensão na direção x<br/>7,75 + 6,0 = 13,75 cm"] --> B2["o ponto está em x = 12 cm<br/>12 cabe em 13,75; y e z são 0"] --> B3["'contemPonto()' devolve true<br/>captura"]
end
K1 ~~~ K2
1.4.12 montarConteudo()
| Recebe | Decide | Devolve |
|---|---|---|
| o lugar do relatório na tela, as linhas do inventário, as do catálogo, as dos volumes e a frase da comparação | apaga o que havia e escreve três blocos, sempre nesta ordem, cada um com o seu título | nada; o relatório é escrito uma vez, e nenhum botão o reescreve |
conteudo.ts
/**
* O inventário do conteúdo do módulo de modelagem: de onde veio a forma de cada
* peça, quanto ela custa em triângulos, que superfícies a cena usa e — o que
* nenhuma outra seção mostra — qual é o volume de contato de cada peça e por que
* a folga é aquela.
*
* O volume vive aqui em texto porque ele decide comportamento sem aparecer na
* tela. Ler a caixa em centímetros ao lado da razão escrita é o que permite
* discutir a escolha antes de o encaixe existir para reclamar dela.
*/
export function montarConteudo(
raiz: HTMLElement,
inventario: readonly string[],
materiais: readonly string[],
volumes: readonly string[],
comparacao: string,
): void {
raiz.replaceChildren();
raiz.appendChild(subtitulo('De onde veio a forma de cada peça'));
for (const linha of inventario) {
raiz.appendChild(paragrafo(linha));
}
raiz.appendChild(subtitulo('As superfícies, e o que cada uma cobra'));
for (const linha of materiais) {
raiz.appendChild(paragrafo(linha));
}
raiz.appendChild(subtitulo('O que se vê e o que colide'));
for (const linha of volumes) {
raiz.appendChild(paragrafo(linha));
}
raiz.appendChild(paragrafo(comparacao));
}
1.4.13 geometriaComCascaInvertida()
| Recebe | Decide | Devolve |
|---|---|---|
a malha da grande e o nome 'engrenagem-grande-com-defeito' |
quantos pontos tem o perfil, pelo total de vértices: (338 − 2) ÷ 6 = 56, e para se a conta não der inteira; confere, vértice a vértice, que a normal vertical está onde a ordem da extrusão a põe; inverte a normal do vértice 114 em diante, onde começa a casca | uma cópia com as mesmas posições e 224 normais invertidas |
04_demo.ts
/**
* Cópia da geometria com a normal da casca lateral multiplicada por −1.
*
* Posições, faces e coordenadas de textura ficam idênticas: a silhueta das duas
* peças coincide ponto a ponto, e só a resposta à luz muda. A casca se localiza
* pela ordem em que `extrudarPerfil` emite os vértices — centro e n pontos do
* topo, centro e n pontos da base, depois quatro por aresta —, então para um
* perfil de n pontos ela ocupa os índices de 2n + 2 até 6n + 2, exclusivo. A
* normal vertical de topo e base é conferida antes da inversão, para que uma
* mudança naquela ordem quebre aqui com mensagem em vez de inverter a face errada.
*/
function geometriaComCascaInvertida(original: BufferGeometry, nome: string): BufferGeometry {
const copia: BufferGeometry = original.clone();
copia.name = nome;
const normal = copia.getAttribute('normal');
if (!(normal instanceof BufferAttribute)) {
throw new Error('A geometria da engrenagem não tem atributo de normal editável.');
}
const total: number = normal.count;
const pontosDoPerfil: number = (total - 2) / 6;
if (!Number.isInteger(pontosDoPerfil)) {
throw new Error(`${total} vértices não correspondem a uma extrusão de perfil fechado.`);
}
const inicioDaCasca: number = 2 * pontosDoPerfil + 2;
for (let i = 0; i < total; i += 1) {
const naCasca: boolean = i >= inicioDaCasca;
if (naCasca !== (normal.getY(i) === 0)) {
throw new Error(`O vértice ${i} não está onde a ordem da extrusão o coloca.`);
}
if (naCasca) {
normal.setXYZ(i, -normal.getX(i), 0, -normal.getZ(i));
}
}
normal.needsUpdate = true;
return copia;
}
1.4.14 enquadrar(), exigirMalhaDaCena() e exigirLuz()
| Recebe | Decide | Devolve |
|---|---|---|
nada; enquadrar() lê dois estados, o par à vista e o eixo pedido |
com o eixo pedido, a câmera vai para perto do eixo, mesmo com o par à vista; só com o par, vai para diante do par; sem nenhum, volta ao ponto guardado. exigirMalhaDaCena() e exigirLuz() param a execução se a cena não trouxer o nó do tipo esperado |
as duas últimas acham a engrenagem grande e as duas luzes; enquadrar() só roda nos cliques da seção de conferência |
04_demo.ts
// A câmera da cena é fixa; as duas aproximações a movem e a devolvem ao ponto
// guardado aqui. O eixo tem precedência sobre o par quando os dois estão
// ligados, porque a faceta só se vê a poucos centímetros.
const posicaoInicial: Vector3 = palco.camera.position.clone();
const orientacaoInicial: Quaternion = palco.camera.quaternion.clone();
let parVisivel: boolean = false;
let pertoDoEixo: boolean = false;
let girando: boolean = false;
function enquadrar(): void {
const camera = palco.camera;
if (pertoDoEixo) {
// Nove centímetros à frente do eixo e um pouco acima do centro dele: perto
// o bastante para que cada um dos doze lados ocupe uma faixa visível, e
// longe o bastante para não cruzar o plano de corte próximo, de 5 cm.
const alvo: Vector3 = cena.eixo.getWorldPosition(new Vector3());
camera.position.copy(alvo).add(new Vector3(0, 0.03, 0.09));
camera.lookAt(alvo);
} else if (parVisivel) {
// Trinta centímetros à frente do ponto médio do par, a uns 21 graus de
// elevação: alto o bastante para ver as duas tampas de cima, baixo e perto o
// bastante para que a casca lateral, de 1,6 cm, apareça como faixa e não
// como linha.
const alvo: Vector3 = grande
.getWorldPosition(new Vector3())
.add(defeituosa.getWorldPosition(new Vector3()))
.multiplyScalar(0.5);
camera.position.copy(alvo).add(new Vector3(0, 0.11, 0.28));
camera.lookAt(alvo);
} else {
camera.position.copy(posicaoInicial);
camera.quaternion.copy(orientacaoInicial);
}
}
1.4.15 repousoDaPeca()
| Recebe | Decide | Devolve |
|---|---|---|
| o nome da peça | um caso por peça: onde ela repousa sobre o tampo | para a engrenagem pequena, (47; 3,3; −5) cm, 2 cm mais longe da grande do que no módulo anterior |
cena.ts
/** Onde cada peça repousa sobre o tampo, antes de qualquer montagem. */
function repousoDaPeca(id: PecaId): [number, number, number] {
switch (id) {
case 'corpo':
return [0, 0.055, 0];
case 'eixo':
return [-0.32, 0.135, 0.12];
case 'engrenagem-grande':
return [0.3, 0.033, 0.1];
case 'engrenagem-pequena':
// Dois centímetros mais longe da grande do que na etapa anterior: com a
// geometria de dentes e as folgas de contato, a 45 cm as duas caixas se
// cruzavam em repouso (1,2 cm em x, 1,3 cm em z).
return [0.47, 0.033, -0.05];
case 'tampa':
return [-0.15, 0.031, -0.18];
}
}
1.5 Uma execução, passo a passo
As abas seguem a abertura da página do módulo até o primeiro clique em Mostrar os volumes de contato. Em cada aba, o que roda naquela aba tem borda grossa e fundo amarelo, o que fica pronto nela 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 R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0 ativo
montarCena(), do módulo anterior, monta a árvore com as cinco peças provisórias, caixas e cilindros pedidos prontos à biblioteca. Na versão deste módulo, repousoDaPeca() devolve para a engrenagem pequena x = 47 cm, e montarCena() a põe ali, porque a 45 cm a caixa dela e a da grande se cruzavam.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0 usado
class P0 ativo
perfilDeEngrenagem(14, 0.068, 0.078) devolve 56 pontos, quatro por dente, no sentido horário vistos de cima: o primeiro na raiz, em (6,8; 0) cm, e o segundo já na crista, a 7,8 cm do centro.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0 usado
class P1,P2 ativo
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2 usado
class P3 ativo
class M novo
finalizar() entrega a malha pronta, com 57 + 57 + 224 = 338 vértices e 56 + 56 + 112 = 224 triângulos.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M usado
class V0 ativo
montarCatalogo() já criou as quatro superfícies. trocarMalha() libera o cilindro provisório da engrenagem grande e põe no lugar a malha de 338 vértices; trocarMaterial() libera o material provisório e aponta a peça para o latão.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0 usado
class V1 ativo
perfilRegular(12, 0.012) e fica de aço. O corpo e a tampa do mecanismo continuam caixas prontas e ganham o plástico; o tampo e o cavalete ganham a madeira, e a base do suporte, o aço.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0,V1 usado
class C0 ativo
vestirCena() acha a folga em FOLGAS, volumeDaMalha() mede o volume e registrar() o guarda: na grande, a caixa envolvente de 15,5 × 1,6 × 15,6 cm, mais 2,0 cm de cada lado, dá 19,5 × 5,6 × 19,6 cm. contornoDoVolume() pendura em cada peça o contorno dela, escondido.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0,V1,C0 usado
class C1 ativo
censoDoCatalogo() acha as oito malhas nas quatro superfícies, e nenhuma fora do catálogo.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0,V1,C0,C1 usado
class D0 ativo
compararFolgas() põe um ponto 12 cm adiante do centro da engrenagem grande, na direção x. Com a folga adotada, a caixa vai a 9,75 cm do centro nessa direção, e o ponto fica fora: não captura. Com a folga de 6,0 cm, ela vai a 13,75 cm, e o ponto fica dentro: captura.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0,V1,C0,C1,D0 usado
class D1 ativo
class F novo
montarConteudo() escreve os três blocos: de onde veio a forma de cada peça, as superfícies e os volumes, com a frase da comparação no fim. É o texto que aparece abaixo destas abas.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0,V1,C0,C1,D0,D1,F usado
class E0 ativo
geometriaComCascaInvertida() copia a malha da grande e inverte as normais dos vértices 114 a 337, os 224 da casca. A cópia, com o mesmo latão, fica 20 cm à direita da grande, escondida até o botão Mostrar o par com defeito.
%%{init: {"flowchart": {"useMaxWidth": false, "rankSpacing": 30, "nodeSpacing": 30}, "sequence": {"useMaxWidth": false}, "class": {"useMaxWidth": false}, "state": {"useMaxWidth": false}, "er": {"useMaxWidth": false}}}%%
flowchart TD
subgraph R1["a cena e o perfil"]
direction LR
A0["'montarCena()'<br/>a árvore do módulo anterior<br/>'repousoDaPeca()': a pequena em x = 47 cm"] --> P0["'perfilDeEngrenagem()'<br/>14 dentes, 4 pontos por dente<br/>56 pontos"] --> P1["tampa de cima<br/>57 vértices, 56 triângulos<br/>ordem inversa"]
end
subgraph R2["a malha da grande"]
direction LR
P2["tampa de baixo<br/>57 vértices, 56 triângulos<br/>ordem natural"] --> P3["casca<br/>56 retângulos em pé<br/>224 vértices, 112 triângulos"] --> M["'finalizar()'<br/>a malha da grande<br/>338 vértices, 224 triângulos"]
end
subgraph R3["vestir e dar as caixas"]
direction LR
V0["a grande<br/>'trocarMalha()': sai o cilindro<br/>'trocarMaterial()': latão"] --> V1["as outras sete malhas<br/>a pequena com o mesmo latão<br/>eixo de 12 lados, de aço<br/>corpo, tampa do mecanismo e mobiliário"] --> C0["'volumeDaMalha()' nas cinco peças<br/>a grande: 19,5 × 5,6 × 19,6 cm<br/>'contornoDoVolume()': contorno escondido"]
end
subgraph R4["contar, testar e escrever"]
direction LR
C1["inventário: 440 triângulos<br/>cena inteira: 476<br/>'censoDoCatalogo()': 4 de 4, 8 malhas"] --> D0["'compararFolgas()'<br/>ponto a 12 cm do centro<br/>2,0 cm: não captura<br/>6,0 cm: captura"] --> D1["'montarConteudo()'<br/>três blocos de texto"] --> F["o relatório na tela"]
end
subgraph R5["depois do relatório"]
direction LR
E0["'geometriaComCascaInvertida()'<br/>224 normais invertidas<br/>a cópia, escondida, 20 cm à direita"] --> E1["clique em Mostrar os volumes<br/>'alternarExibicao()' devolve true<br/>cinco contornos à vista"]
end
R1 --> R2 --> R3 --> R4 --> R5
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 A0,P0,P1,P2,P3,M,V0,V1,C0,C1,D0,D1,F,E0 usado
class E1 ativo
alternarExibicao() passa exibindo de false para true e mostra os cinco contornos. O botão passa a dizer “Esconder os volumes de contato”, e o relatório fica como estava.
O relatório desta execução, como a página do módulo o escreve. O nome das peças e das superfícies sai sem acento e com hífen, como o programa o guarda, e o número, com ponto no lugar da vírgula:
De onde veio a forma de cada peça
corpo: forma primitiva, 12 triângulos e 24 vértices.
eixo: forma autorada, 48 triângulos e 74 vértices.
engrenagem-grande: forma autorada, 224 triângulos e 338 vértices.
engrenagem-pequena: forma autorada, 144 triângulos e 218 vértices.
tampa: forma primitiva, 12 triângulos e 24 vértices.
Total das cinco peças: 440 triângulos. A cena inteira, com tampo, cavalete e base do suporte: 476.
As superfícies, e o que cada uma cobra
Superfícies do catálogo em uso na cena: 4 de 4, aplicadas a 8 malhas
aco-escovado: rugosidade 0.35, metalicidade 0.90, em base-do-suporte, eixo
latao: rugosidade 0.45, metalicidade 0.75, em engrenagem-grande, engrenagem-pequena
plastico-fosco: rugosidade 0.85, metalicidade 0.05, em corpo, tampa
madeira: rugosidade 0.80, metalicidade 0.00, em tampo, cavalete
O que se vê e o que colide
corpo: caixa de 14.4 x 8.4 x 14.4 cm, folga de 1.2 cm — peça de apoio, apanhada pela lateral com a mão inteira
eixo: caixa de 4.0 x 23.6 x 4.0 cm, folga de 0.8 cm — peça fina: com folga maior, ela capturaria de longe o raio dirigido à vizinha
engrenagem-grande: caixa de 19.5 x 5.6 x 19.6 cm, folga de 2.0 cm — a peça mais larga que a mão apanha inteira, fechada sobre a silhueta: leva a maior folga
engrenagem-pequena: caixa de 12.9 x 5.2 x 13.0 cm, folga de 1.8 cm — mesma pega da grande, com um pouco menos por conviver perto dela
tampa: caixa de 15.4 x 3.6 x 15.4 cm, folga de 1.2 cm — placa chata: sem folga vertical o dedo passa por cima sem tocar
Um ponto a 12 cm do centro da engrenagem grande: com a folga adotada de 2.0 cm, o volume não captura; com uma folga de 6.0 cm, captura. O segundo caso é a peça grudando em quem só passou perto.1.6 Por que o código é assim
As decisões que o próprio código registra, cada uma no identificador em que mora.
ConstrutorDeMalha
Cada lado plano da peça ganha cópias próprias dos seus vértices, sempre. A engrenagem sai com mais vértices, e em troca o canto fica vivo: um vértice usado por dois lados teria uma normal só para os dois, e a luz arredondaria o canto. Peça mecânica com canto macio parece brinquedo.
finalizar()
A esfera envolvente, a esfera que envolve a malha inteira, é o que a biblioteca consulta para decidir se a peça entra no campo de visão da câmera. Sem ela, a biblioteca a calcula sozinha no primeiro teste, no meio do desenho de um quadro; finalizar() a calcula na construção da malha, uma vez.
extrudarPerfil(): a ordem das tampas
A biblioteca desenha só a face cuja borda gira no sentido anti-horário vista de fora, e a face no sentido errado não dá erro nem aviso: abre um buraco que só aparece de um certo ângulo. O perfil gira no sentido horário visto de cima, e por isso no anti-horário visto de baixo: a tampa de baixo usa a ordem natural, e a de cima inverte a ordem.
extrudarPerfil(): a normal da casca
nx e nz dão a normal que aponta para fora. Com o sinal de menos no outro termo, ela apontaria para dentro da peça, e a luz acertaria a lateral pelo lado errado: a peça fica escura com a silhueta perfeita, e ninguém acha o motivo olhando a forma.
extrudarPerfil(): a coordenada de textura
A coordenada horizontal acompanha a distância percorrida na borda, e a vertical vai da base ao topo, embora nenhuma peça tenha textura ainda. Deixada para depois, a coordenada mal distribuída só apareceria com a primeira imagem, como desenho esticado, e seria procurada na imagem, e não na malha.
perfilDeEngrenagem()
O número de dentes não é parâmetro de resolução, a classe de parâmetros que muda a densidade (quantos triângulos a malha tem) sem mudar a forma. Com o da engrenagem parceira, ele fixa a razão de transmissão, a proporção em que as duas giram, e cada dente custa 16 triângulos porque a peça é aquela. Resolução, aqui, são os quatro pontos por dente.
superficie() e Catalogo
A superfície se declara pelo que é, metálica e pouco rugosa, e a luz que a cena declara decide a aparência: o projeto não estima a luz da sala real, e no regime aumentado (o celular que mostra a sala real com as peças desenhadas por cima) a peça recebe as mesmas duas luzes virtuais da tela do computador. Quatro superfícies bastam para a cena inteira, porque cada superfície diferente em uso é pelo menos uma troca de estado a mais por quadro: a passagem de uma superfície declarada para outra, entre desenhar uma peça e desenhar a seguinte.
DescricaoDeMaterial
custo é obrigatório ao lado de entrega: sem a frase do que a superfície cobra, escolher entre aço e plástico é palpite, e o programa nem compila sem ela.
vestirCena(): um latão para as duas engrenagens
As duas engrenagens apontam para a mesma instância de latão, e não para dois latões iguais. A biblioteca compara identidade, e não valores: com dois latões, ela reenviaria à placa de vídeo (a parte do computador que faz o desenho) os valores de cor, rugosidade e metalicidade do segundo ao passar de uma engrenagem para a outra.
censoDoCatalogo() e linhasDoCatalogo()
O censo confere por identidade, e por isso um latão igual em valor, criado à parte, aparece fora do catálogo. A primeira linha conta as superfícies em uso nas malhas da cena, e não as do catálogo, porque é esse número que diz quantas trocas de estado o quadro paga, no mínimo.
volumeDaMalha()
A caixa sai da geometria da peça, e não do nó montado na cena, por duas razões. A geometria já está no espaço local da peça, e o volume gira, muda de tamanho e troca de pai junto com ela. E a caixa da geometria ignora o que está pendurado no nó, inclusive o contorno de depuração: tirado do nó inteiro, o volume engordaria a cada recálculo.
contemPonto()
O ponto é levado ao espaço local da peça antes do teste. Testar em coordenadas de mundo obrigaria a recalcular a caixa a cada quadro, e a caixa recalculada de uma peça inclinada cresce: o volume aumentaria sozinho, e a captura seria maior num ângulo do que no outro.
contornoDoVolume()
O volume é a única parte do ambiente que decide comportamento sem aparecer. Desenhá-lo, escondido e ligado por um botão, é o recurso barato que separa a peça que gruda de longe da peça que deixa passar, sem prender, o que já encostou nela: sem o desenho, os dois defeitos chegam a quem olha só a tela como a mesma queixa.
FOLGAS
As folgas não são iguais, de propósito. A que serve para apanhar uma engrenagem de 15 cm afogaria o eixo, de 2,4 cm de largura: a caixa dele seria quase toda folga, e capturaria qualquer raio do controle que cruzasse a vizinhança. Cada número leva a razão ao lado, e é ela que o relatório mostra.
exigirMalha() e a busca em FOLGAS
A peça que não chega como malha, ou que não tem folga declarada, para a execução com uma mensagem que diz o nome dela. Seguir em silêncio deixaria a peça com a forma provisória, ou com uma folga que ninguém escolheu.
trocarMalha() e trocarMaterial()
A malha antiga continua na memória da placa de vídeo se ninguém a liberar. Numa troca isso não se nota; num ambiente que recarrega o conteúdo várias vezes, a memória de vídeo acaba, e o navegador deixa a tela preta, sem mensagem de erro. O material provisório se libera porque cada um pertence a uma malha só; material compartilhado não se libera na troca de um dos donos.
vestirCena(): vestir depois
A árvore do módulo anterior nasce igual e só depois é vestida: assim a cena de caixas e a cena vestida podem ser mostradas lado a lado. O corpo e a tampa do mecanismo ficam com a caixa pronta por decisão escrita: escrever à mão um bloco retangular é trabalho sem retorno, e a forma escrita vai onde ela significa alguma coisa, no dente e na faceta do eixo (o lado plano que se distingue do vizinho).
compararFolgas()
O defeito da folga grande é medido, e não descrito: o mesmo ponto, ao lado da peça, testado contra as duas caixas. O defeito oposto não tem teste próprio, porque é o mesmo teste com o ponto encostado na ponta de um dente e a folga em zero: o ponto fica na parede da caixa e é recusado ao menor desvio.
repousoDaPeca()
A engrenagem pequena repousa 2 cm mais longe da grande do que no módulo anterior. Com os dentes e as folgas, a 45 cm as caixas das duas se cruzavam com as peças paradas: 1,2 cm na direção x e 1,3 cm na direção z.
geometriaComCascaInvertida()
A cópia usa a mesma instância de latão da peça certa, e por isso a única diferença entre as duas é a normal da casca. Antes de inverter, a função confere que cada normal vertical está onde a ordem da extrusão a põe: se essa ordem mudar, a função para com uma mensagem, em vez de inverter a normal errada.
alternarExibicao()
A função devolve o novo valor de exibindo, e é dele que o botão tira o rótulo: essa é a única razão de ela devolver alguma coisa.
1.7 Como o código se confere
O módulo não tem bateria de testes: quem confere o código é o próprio relatório, as paradas com mensagem, os botões de olhar e a verificação de tipos.
| O que confere | O que traz | Quando algo falha | Nesta execução |
|---|---|---|---|
| o inventário | uma linha por peça, com a origem e as contagens, e os dois totais | a contagem que não fecha à mão com o perfil (57 + 57 + 224 = 338 na grande) aponta a extrusão | 440 triângulos nas cinco peças e 476 na cena |
| a primeira linha do catálogo | as superfícies em uso e as malhas que apontam para elas | uma malha com material criado à parte ganha a linha “Malhas com material fora do catálogo” | “4 de 4, aplicadas a 8 malhas”, e nenhuma linha de fora do catálogo |
| as linhas dos volumes | a caixa em centímetros, a folga e a razão de cada peça | — | cinco linhas, da caixa de 4,0 × 23,6 × 4,0 cm do eixo à de 19,5 × 5,6 × 19,6 cm da grande |
| a frase da comparação | o veredito das duas folgas para o mesmo ponto | — | 2,0 cm: não captura; 6,0 cm: captura |
as paradas com mensagem, em exigirMalha(), na busca em FOLGAS, em volumeDaMalha(), em compararFolgas() e em geometriaComCascaInvertida() |
a execução para e nomeia o que faltou | por exemplo, “A peça”…” está na cena e não tem folga de contato declarada.” | nenhuma parada, e nenhum erro no navegador |
| os botões Mostrar o par com defeito, Aproximar do eixo, Trocar a luz de lugar e Deixar uma luz só | a conferência a olho da normal, da faceta e da dependência da luz | — | a cópia aparece e some; a luz direcional (a que vem de uma direção só, como a luz do sol) vai de x = 1,2 m para −1,2 m; a luz hemisférica (a que ilumina cada face pelo lado para onde ela olha, com cor de céu em cima e de chão embaixo) apaga; cada botão troca o próprio rótulo, e o relatório não muda |
| a verificação de tipos do TypeScript, em modo estrito | todo o código do módulo, junto com o dos módulos anteriores | um erro de tipo impede o programa de compilar | nenhum erro |
Exemplo deste infográfico: a abertura da página do módulo, até um clique em Mostrar os volumes de contato, e os botões de olhar na última conferência; execução de 27/10/2026.