Lavorare con il grafo della scena

Lavorare con il grafo della scena

Tutto il contenuto 3D in Aspose.3D FOSS per TypeScript si trova all’interno di un Scene oggetto organizzato come un albero di Node oggetti. Comprendere questa gerarchia è la base per costruire, caricare e processare qualsiasi file 3D.

Installazione

Installa il pacchetto da npm prima di eseguire qualsiasi codice in questa guida:

npm install @aspose/3d

Assicurati che il tuo tsconfig.json include "module": "commonjs" e "moduleResolution": "node" per una corretta risoluzione degli import di sotto-percorso.

Concetti del grafo della scena

Il grafo della scena ha tre livelli:

LivelloClasseRuolo
ScenaSceneContenitore di livello superiore. Contiene rootNode, animationClips, e assetInfo.
NodoNodeNodo dell’albero con nome. Può avere nodi figli, un’entità, una trasformazione e materiali.
EntitàMesh, Camera, Light, …Contenuto collegato a un nodo. Un nodo trasporta al massimo un’entità.

La catena di ereditarietà per i blocchi principali è:

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

scene.rootNode viene creato automaticamente. Non lo crei manualmente; crei nodi figlio sotto di esso.

Passo 1: Crea una scena

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 nuovo Scene inizia con un nodo radice vuoto e nessuna clip di animazione. Costruisci il contenuto collegando nodi figlio.

Passo 2: Aggiungere nodi figli

Usa createChildNode() per far crescere l’albero. Il metodo restituisce il nuovo Node, così puoi concatenare ulteriori chiamate da qualsiasi livello:

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)

I nomi dei nodi sono stringhe arbitrarie. I nomi non devono essere unici, ma usare nomi significativi rende più facile il debug del codice di traversata.

Passo 3: Creare una Mesh e impostare i vertici

Mesh è la classe geometrica primaria. Aggiungi le posizioni dei vertici spingendo Vector4 valori in mesh.controlPoints, quindi chiama createPolygon() per definire le facce tramite indice dei vertici:

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 utilizza coordinate omogenee: il w componente è 1 per le posizioni e 0 per i vettori direzionali.

Passo 4: Imposta le trasformazioni dei nodi

Ogni nodo ha un transform proprietà con translation, rotation, e scaling. Imposta translation per spostare il nodo rispetto al suo genitore:

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 fornisce la matrice di trasformazione nello spazio mondo (sola lettura), calcolata concatenando tutte le trasformazioni dei genitori.

Passo 5: Attraversa l’albero

Scrivi una funzione ricorsiva per visitare ogni nodo. Verifica node.entity e node.childNodes a ogni livello:

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);

Per la gerarchia creata sopra, l’output sarà:

 [none]
  parent [none]
    child [Mesh]

Il nome del nodo radice è una stringa vuota perché Scene lo crea senza argomento name.

Proteggi sempre l’accesso all’entità con un controllo null prima di effettuare il cast a un tipo specifico. Non tutti i nodi contengono un’entità.

Passo 6: Salva in glTF o GLB

Usa GltfSaveOptions per controllare il formato di output. Imposta binaryMode = true per produrre un unico file autonomo .glb file; lascialo false per il JSON .gltf + .bin coppia 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');

Passa GltfFormat.getInstance() come argomento format affinché la libreria utilizzi il codificatore corretto indipendentemente dall’estensione del file.

Suggerimenti e migliori pratiche

  • Usa createChildNode() invece di costruire Node direttamente: createChildNode() collega automaticamente la relazione genitore-figlio e registra il nodo nell’albero.
  • Verifica node.entity prima di accedere alle proprietà dell’entità: molti nodi (nodi di gruppo, ossa, localizzatori) non hanno alcuna entità. Controlla sempre con un controllo null o instanceof test.
  • Imposta translation sui nodi figlio, non sui vertici della mesh: modifica transform.translation è non distruttivo e composabile con le trasformazioni genitore.
  • Preferisci binaryMode = true per GLB: un singolo .glb il file è più facile da distribuire, caricare nei browser e importare nei motori di gioco rispetto al diviso .gltf + .bin formato.
  • Attraversa tramite for...of su childNodes: evita l’indicizzazione numerica; usa direttamente l’iterabile per la compatibilità futura.

Problemi comuni

SintomoProbabile causaCorrezione
child.entity = mesh non ha alcun effetto sull’esportazioneEntità assegnata al livello di nodo erratoAssegna entity al nodo foglia, non a un nodo gruppo
node.entity è sempre nullSolo verifica rootNode se stessoRicorsione in node.childNodes; rootNode tipicamente non ha entità
Trasformazione non riflessa nel visualizzatore GLBglobalTransform non aggiornatoglobalTransform viene calcolato al salvataggio; impostare transform.translation prima di chiamare scene.save()
GLB produce un separato .bin sidecarbinaryMode predefinito a falseImposta saveOpts.binaryMode = true

Vedi anche

 Italiano