Com recórrer un gràfic de escena 3D en TypeScript
El graf de l’escena a Aspose.3D FOSS per a TypeScript és un arbre d’objectes Node amb arrel a scene.rootNode. El recorregut és recursiu: cada node exposa un iterable childNodes i una propietat opcional entity. Aquesta guia mostra com recórrer tot l’arbre, identificar els tipus d’entitat i recopilar estadístiques de malla.
Requisits previs
- Node.js 18 o posterior
- TypeScript 5.0 o posterior
@aspose/3dinstal·lat
Guia pas a pas
Pas 1: Instal·la i importa
Instal·la el paquet:
npm install @aspose/3dImporta les classes utilitzades en aquesta guia:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene i Mesh són les classes principals. ObjLoadOptions s’utilitza en l’exemple de càrrega; substituïu la classe d’opcions corresponent per a altres formats.
Pas 2: Carrega una escena des d’un fitxer
Creeu una Scene i truqueu scene.open() amb una ruta de fitxer. La detecció del format és automàtica a partir dels números màgics binaris, de manera que no cal especificar el format per a fitxers GLB, STL o 3MF:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
const scene = new Scene();
scene.open('model.obj', new ObjLoadOptions());
console.log(`Root node: "${scene.rootNode.name}"`);
console.log(`Top-level children: ${scene.rootNode.childNodes.length}`);També podeu carregar des d’un Buffer en memòria utilitzant scene.openFromBuffer(buffer, options); útil en pipelines sense servidor on l’E/S de disc no està disponible.
Pas 3: Escriu una funció de recorregut recursiu
La recursió sobre childNodes és el patró estàndard. La funció visita cada node en profunditat:
function traverse(node: any, depth = 0): void {
const indent = ' '.repeat(depth);
const entityType = node.entity ? node.entity.constructor.name : '-';
console.log(`${indent}[${entityType}] ${node.name}`);
for (const child of node.childNodes) {
traverse(child, depth + 1);
}
}
traverse(scene.rootNode);Per a una escena amb una malla anomenada Cube, la sortida es veurà així:
[-] RootNode
[Mesh] Cube<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
node.entity és null per a nodes de grup, ossos i localitzadors. La comprovació constructor.name funciona per a qualsevol tipus d’entitat: Mesh, Camera, Light, etc.
Pas 4: Accedir al tipus d’entitat a cada node
Per prendre acció segons el tipus d’entitat, utilitzeu una comprovació instanceof després del control de nul·litat:
import { Mesh } from '@aspose/3d/entities';
function visitWithTypeCheck(node: any, depth = 0): void {
const indent = ' '.repeat(depth);
if (node.entity instanceof Mesh) {
const mesh = node.entity as Mesh;
console.log(`${indent}MESH "${node.name}": ${mesh.controlPoints.length} vertices`);
} else if (node.entity) {
console.log(`${indent}${node.entity.constructor.name} "${node.name}"`);
} else {
console.log(`${indent}GROUP "${node.name}"`);
}
for (const child of node.childNodes) {
visitWithTypeCheck(child, depth + 1);
}
}
visitWithTypeCheck(scene.rootNode);instanceof Mesh és la manera més segura de confirmar que l’entitat és una malla de polígons abans d’accedir a controlPoints, polygonCount o elements de vèrtex.
Pas 5: Filtrar nodes per tipus d’entitat
Per recollir només els nodes que contenen malles sense imprimir l’arbre complet, utilitzeu un acumulador recursiu:
import { Mesh } from '@aspose/3d/entities';
function collectMeshes(
node: any,
results: Array<{ name: string; mesh: Mesh }> = []
): Array<{ name: string; mesh: Mesh }> {
if (node.entity instanceof Mesh) {
results.push({ name: node.name, mesh: node.entity as Mesh });
}
for (const child of node.childNodes) {
collectMeshes(child, results);
}
return results;
}
const meshNodes = collectMeshes(scene.rootNode);
console.log(`Found ${meshNodes.length} mesh node(s)`);La funció accepta una matriu opcional results perquè els cridadors la puguin pre‑poblar per combinar resultats a través de múltiples subarbres.
Pas 6: Recull totes les malles i imprimeix el recompte de vèrtexs
Amplia el col·lector per imprimir estadístiques per malla:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';
function collectMeshes(node: any, results: Array<{name: string, mesh: Mesh}> = []) {
if (node.entity instanceof Mesh) {
results.push({ name: node.name, mesh: node.entity as Mesh });
}
for (const child of node.childNodes) {
collectMeshes(child, results);
}
return results;
}
const scene = new Scene();
scene.open('model.obj', new ObjLoadOptions());
const meshes = collectMeshes(scene.rootNode);
for (const { name, mesh } of meshes) {
console.log(`${name}: ${mesh.controlPoints.length} vertices, ${mesh.polygonCount} polygons`);
}Exemple de sortida per a una escena de dues malles:
Cube: 8 vertices, 6 polygons
Sphere: 482 vertices, 480 polygons<button class=“hextra-code-copy-btn hx-group/copybtn hx-transition-all active:hx-opacity-50 hx-bg-primary-700/5 hx-border hx-border-black/5 hx-text-gray-600 hover:hx-text-gray-900 hx-rounded-md hx-p-1.5 dark:hx-bg-primary-300/10 dark:hx-border-white/10 dark:hx-text-gray-400 dark:hover:hx-text-gray-50” title=“Copy code”
<div class="copy-icon group-[.copied]/copybtn:hx-hidden hx-pointer-events-none hx-h-4 hx-w-4"></div>
<div class="success-icon hx-hidden group-[.copied]/copybtn:hx-block hx-pointer-events-none hx-h-4 hx-w-4"></div>
Consells i bones pràctiques
- Comproveu sempre que no sigui nul
node.entityabans d’accedir a les propietats específiques de l’entitat. Molts nodes són nodes de grup purs que no contenen cap entitat. - Utilitzeu
instanceofen comptes deconstructor.nameper a comprovacions de tipus en rutes lògiques.instanceofés segur per a refactoritzacions; la comparació de cadenes ambconstructor.namees trenca amb la minificació. - Recorreu via
for...ofen comptes dechildNodes: l’iterable gestiona totes les mides d’array de manera segura. Eviteu l’indexació numèrica per a una compatibilitat futura. - Eviteu mutar l’arbre durant el recorregut: no afegiu ni elimineu nodes dins de la crida recursiva. Recolliu els resultats primer, després modifiqueu.
- Passeu una matriu de resultats com a paràmetre: això evita assignar una nova matriu en cada crida recursiva i facilita la fusió dels resultats dels subarbres.
Problemes comuns
| Símptoma | Causa | Solució |
|---|---|---|
childNodes té longitud zero a rootNode | Model no carregat | Assegureu-vos que scene.open() s’ha completat sense errors abans de recórrer |
node.entity instanceof Mesh mai és cert | Camí d’importació de Mesh incorrecte | Importeu Mesh des de @aspose/3d/entities, no des de l’arrel @aspose/3d |
| El recorregut omet malles anidades | No es recorre recursivament tots els fills | Assegureu que la crida recursiva cobreixi cada element a node.childNodes |
mesh.controlPoints.length és 0 | Malla carregada però no conté geometria | Comproveu la font OBJ per a grups buits; utilitzeu mesh.polygonCount com a comprovació secundària |
| Desbordament de pila en jerarquies profundes | Arbre d’escena molt profund (centenars de nivells) | Substituïu la recursió per una pila explícita utilitzant Array.push / Array.pop |
Preguntes freqüents
Porta scene.rootNode en si mateixa una entitat?
No. El node arrel és un contenidor creat automàticament per la biblioteca. No té cap entitat. La teva geometria i altres objectes de l’escena viuen en nodes fills un o més nivells per sota de rootNode.
Quina és la diferència entre node.entity i node.entities?
node.entity conté l’entitat primària única (el cas comú). Alguns fitxers FBX i COLLADA més antics poden generar nodes amb diverses entitats adjuntes; en aquest cas node.entities (plural) proporciona la llista completa.
Puc recórrer en ordre d’amplada en lloc d’ordre de profunditat?
Sí. Utilitzeu una cua en lloc d’una crida recursiva: empenyeu scene.rootNode a una matriu, després desplaceu i processeu els nodes mentre empenyeu el childNodes de cada node al final de la cua.
És scene.open() sincrònic?
Sí. scene.open() i scene.openFromBuffer() bloquegen el fil d’execució que els crida fins que el fitxer estigui completament analitzat. Envolta’ls en un fil de treball si necessites mantenir el bucle d’esdeveniments responsiu.
Com puc obtenir posicions en l’espai mundial d’un node?
Llegeix node.globalTransform; retorna un GlobalTransform només de lectura amb la matriu en l’espai mundial, composta per totes les transformacions dels avantpassats. Per a càlculs explícits de matrius, crida node.evaluateGlobalTransform(false).
Quins tipus d’entitat són possibles a més de Mesh?
Camera, Light i entitats personalitzades d’esquelet/os. Consulteu node.entity.constructor.name o utilitzeu instanceof amb la classe específica importada des de @aspose/3d.