Trabalhando com o Scene Graph

Todo o conteúdo 3D em Aspose.3D FOSS para TypeScript reside dentro de um Scene objeto organizado como uma árvore de Node objetos. Compreender essa hierarquia é a base para construir, carregar e processar qualquer arquivo 3D.

Instalação

Instale o pacote do npm antes de executar qualquer código deste guia:

npm install @aspose/3d

Garanta que seu tsconfig.json inclui "module": "commonjs" e "moduleResolution": "node" para a resolução correta de importação de subcaminhos.

Conceitos de Scene Graph

O scene graph tem três níveis:

NívelClasseFunção
CenaSceneContêiner de nível superior. Contém rootNode, animationClips, e assetInfo.
NodeNó de árvore nomeado. Pode ter nós filhos, uma entidade, uma transformação e materiais.
EntidadeMesh, Camera, Light, …Conteúdo anexado a um nó. Um nó carrega no máximo uma entidade.

A cadeia de herança dos principais blocos de construção é:

A3DObject
  └─ SceneObject
       ├─ Node          (tree structure)
       └─ Entity
            └─ Geometry
                 └─ Mesh   (polygon geometry)

scene.rootNode é criado automaticamente. Você não o cria manualmente; você cria nós filhos sob ele.

Step 1: Create a Scene

import { Scene } from '@aspose/3d';

const scene = new Scene();
console.log(scene.rootNode.name); // '' (empty string — the root node is created with no name)

Um novo Scene começa com um nó raiz vazio e sem clipes de animação. Você constrói o conteúdo anexando nós filhos.

Etapa 2: Adicionar nós filhos

Use createChildNode() para expandir a árvore. O método retorna o novo Node, assim você pode encadear chamadas adicionais a partir de qualquer nível:

import { Scene } from '@aspose/3d';

const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');

console.log(scene.rootNode.childNodes.length); // 1 (parent)
console.log(parent.childNodes.length);         // 1 (child)

Os nomes dos nós são strings arbitrárias. Os nomes não precisam ser únicos, mas usar nomes significativos facilita a depuração do código de travessia.

Etapa 3: Criar um Mesh e definir vértices

Mesh é a classe de geometria principal. Adicione posições de vértices usando push Vector4 valores em mesh.controlPoints, então chame createPolygon() para definir faces por índice de vértice:

import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4 } from '@aspose/3d/utilities';

const scene = new Scene();
const child = scene.rootNode.createChildNode('parent').createChildNode('child');

const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3); // quad face using all four vertices

child.entity = mesh;

console.log(mesh.controlPoints.length); // 4
console.log(mesh.polygonCount);         // 1

Vector4 usa coordenadas homogêneas: o w componente é 1 para posições e 0 para vetores de direção.

Step 4: Set Node Transforms

Cada nó tem um transform propriedade com translation, rotation, e scaling. Defina translation para mover o nó em relação ao seu pai:

import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';

const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');

const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;

child.transform.translation = new Vector3(2.0, 0.0, 0.0);

globalTransform fornece a matriz de transformação no espaço mundial (somente leitura), calculada concatenando todas as transformações dos ancestrais.

Etapa 5: Percorrer a Árvore

Escreva uma função recursiva para visitar cada nó. Verifique node.entity e node.childNodes em cada nível:

function traverse(node: any, depth = 0): void {
    const indent = '  '.repeat(depth);
    const entityType = node.entity ? node.entity.constructor.name : 'none';
    console.log(`${indent}${node.name} [${entityType}]`);
    for (const child of node.childNodes) {
        traverse(child, depth + 1);
    }
}

traverse(scene.rootNode);

Para a hierarquia criada acima, a saída será:

 [none]
  parent [none]
    child [Mesh]

O nome do nó raiz é uma string vazia porque Scene cria-o sem argumento de nome.

Sempre proteja o acesso à entidade com uma verificação de null antes de fazer cast para um tipo específico. Nem todo nó possui uma entidade.

Etapa 6: Salvar em glTF ou GLB

Use GltfSaveOptions para controlar o formato de saída. Defina binaryMode = true para produzir um único auto-contido .glb arquivo; deixe-o false para o JSON .gltf + .bin par sidecar:

import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';

const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');

const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;

child.transform.translation = new Vector3(2.0, 0.0, 0.0);

const saveOpts = new GltfSaveOptions();
saveOpts.binaryMode = true;                             // write a single .glb file
scene.save('scene.glb', GltfFormat.getInstance(), saveOpts);

console.log('Scene saved to scene.glb');

Passe GltfFormat.getInstance() como argumento de formato para que a biblioteca use o codificador correto independentemente da extensão do arquivo.

Dicas e Melhores Práticas

  • Use createChildNode() em vez de construir Node diretamente: createChildNode() conecta automaticamente a relação pai‑filho e registra o nó na árvore.
  • Verifique node.entity antes de acessar as propriedades da entidade: muitos nós (nós de grupo, ossos, localizadores) não possuem entidade. Sempre proteja com uma verificação de null ou instanceof teste.
  • Definir translation em nós filhos, não nos vértices da malha: modificando transform.translation não é destrutivo e pode ser composto com as transformações dos pais.
  • Prefira binaryMode = true para GLB: um único .glb arquivo é mais fácil de distribuir, carregar em navegadores e importar em engines de jogo do que o dividido .gltf + .bin formato.
  • Percorrer via for...of sobre childNodes: evite indexação numérica; use o iterável diretamente para compatibilidade futura.

Problemas Comuns

SintomaCausa ProvávelCorreção
child.entity = mesh não tem efeito na exportaçãoEntidade atribuída ao nível de nó erradoAtribuir entity ao nó folha, não a um nó de grupo
node.entity é sempre nullApenas verificando rootNode ele mesmoRecursar em node.childNodes; rootNode geralmente não tem entidade
Transform não refletido no visualizador GLBglobalTransform não atualizadoglobalTransform é calculado ao salvar; definir transform.translation antes de chamar scene.save()
GLB produz um separado .bin sidecarbinaryMode padrão é falseDefinir saveOpts.binaryMode = true

Veja Também

 Português