Trabajando con el Grafo de Escena

Trabajando con el Grafo de Escena

Todo el contenido 3D en Aspose.3D FOSS para TypeScript se encuentra dentro de un Scene objeto organizado como un árbol de Node objetos. Comprender esta jerarquía es la base para crear, cargar y procesar cualquier archivo 3D.

Installation

Instala el paquete desde npm antes de ejecutar cualquier código de esta guía:

npm install @aspose/3d

Asegúrate de que tu tsconfig.json incluya "module": "commonjs" y "moduleResolution": "node" para una correcta resolución de importación de subrutas.

Conceptos del Grafo de Escena

El grafo de escena tiene tres niveles:

NivelClaseRol
EscenaSceneContenedor de nivel superior. Contiene rootNode, animationClips, y assetInfo.
NodoNodeNodo de árbol con nombre. Puede tener nodos hijos, una entidad, una transformación y materiales.
EntidadMesh, Camera, Light, …Contenido adjunto a un nodo. Un nodo lleva como máximo una entidad.

La cadena de herencia para los bloques de construcción principales es:

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

scene.rootNode se crea automáticamente. No lo creas manualmente; creas nodos hijos bajo él.

Paso 1: Crear una Escena

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

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

Un nuevo Scene comienza con un nodo raíz vacío y sin clips de animación. Construyes contenido adjuntando nodos hijos.

Paso 2: Añadir Nodos Hijos

Usa createChildNode() para expandir el árbol. El método devuelve el nuevo Node, así que puedes encadenar llamadas adicionales desde cualquier nivel:

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)

Los nombres de los nodos son cadenas arbitrarias. No es necesario que los nombres sean únicos, pero usar nombres significativos facilita la depuración del código de recorrido.

Paso 3: Crear un Mesh y Definir Vértices

Mesh es la clase principal de geometría. Añade posiciones de vértices empujando Vector4 valores en mesh.controlPoints, luego llama a createPolygon() para definir caras 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 utiliza coordenadas homogéneas: el w componente es 1 para posiciones y 0 para vectores de dirección.

Paso 4: Configurar transformaciones de nodo

Cada nodo tiene un transform propiedad con translation, rotation, y scaling. Establecer translation para mover el nodo relativo a su padre:

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 proporciona la matriz de transformación en espacio mundial (solo lectura), calculada concatenando todas las transformaciones de los ancestros.

Paso 5: Recorrer el árbol

Escribe una función recursiva para visitar cada nodo. Comprueba node.entity y node.childNodes en cada nivel:

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 la jerarquía creada arriba, la salida será:

 [none]
  parent [none]
    child [Mesh]

El nombre del nodo raíz es una cadena vacía porque Scene lo crea sin argumento de nombre.

Siempre protege el acceso a la entidad con una verificación de null antes de convertir a un tipo específico. No todos los nodos llevan una entidad.

Paso 6: Guardar a glTF o GLB

Usa GltfSaveOptions para controlar el formato de salida. Establece binaryMode = true para producir un único auto‑contenedor .glb archivo; déjalo false para el 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');

Pasa GltfFormat.getInstance() como argumento de formato para que la biblioteca use el codificador correcto sin importar la extensión del archivo.

Consejos y mejores prácticas

  • Usa createChildNode() en lugar de construir Node directamente: createChildNode() conecta automáticamente la relación padre‑hijo y registra el nodo en el árbol.
  • Verifica node.entity antes de acceder a las propiedades de la entidad: muchos nodos (nodos de grupo, huesos, localizadores) no llevan entidad. Siempre protege con una null check o instanceof prueba.
  • Establecer translation en nodos hijos, no en vértices de malla: modificando transform.translation es no destructivo y composable con las transformaciones del padre.
  • Preferir binaryMode = true para GLB: un único .glb archivo es más fácil de distribuir, cargar en navegadores e importar a motores de juego que el dividido .gltf + .bin formato.
  • Recorrer vía for...of sobre childNodes: evite la indexación numérica; use el iterable directamente para compatibilidad futura.

Problemas comunes

SíntomaCausa probableSolución
child.entity = mesh no tiene efecto en la exportaciónEntidad asignada al nivel de nodo incorrectoAsignar entity al nodo hoja, no a un nodo de grupo
node.entity siempre es nullSolo comprobando rootNode sí mismoRecursar en node.childNodes; rootNode normalmente no tiene entidad
Transformación no reflejada en el visor GLBglobalTransform no actualizadoglobalTransform se calcula al guardar; establecer transform.translation antes de llamar scene.save()
GLB produce un archivo separado .bin sidecarbinaryMode por defecto es falseEstablecer saveOpts.binaryMode = true

Ver también

 Español