Com recórrer un gràfic de escena 3D en TypeScript
El gràfic de escena a Aspose.3D FOSS per a TypeScript és un arbre de Node objectes arrelats a scene.rootNode. El recorregut és recursiu: cada node exposa un childNodes iterable i una opcional entity propietat. Aquesta guia mostra com recórrer tot l’arbre, identificar els tipus d’entitat i recopilar estadístiques de malles.
Requisits
- Node.js 18 o posterior
- TypeScript 5.0 o posterior
@aspose/3dinstal·lat
Guia pas a pas
Pas 1: Instal·lar i importar
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 a l’exemple de càrrega; substituïu la classe d’opcions corresponent per a altres formats.
Pas 2: Carregar una escena des d’un fitxer
Creeu un Scene i crideu 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: Escriure una funció de recorregut recursiu
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, l’output serà 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. El constructor.name comprovació 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 un instanceof comproveu després de la protecció contra 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 a recollir només els nodes que continguin malles sense imprimir tot l’arbre, 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 un paràmetre opcional results matriu perquè els cridadors la puguin pre-poblar per combinar resultats a través de múltiples subarbres.
Pas 6: Recollir totes les malles i imprimir el recompte de vèrtexs
Amplieu el recullidor 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 amb 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 la nul·litat
node.entityabans d’accedir a propietats específiques de l’entitat. Molts nodes són nodes de grup purs que no contenen cap entitat. - Utilitzeu
instanceofsobreconstructor.nameper a comprovacions de tipus en rutes lògiques.instanceofés segur per a refactoritzacions; comparació de cadenes aconstructor.namees trenca amb la minificació. - Recorre via
for...ofsobrechildNodes: l’iterable gestiona totes les mides d’array de manera segura. Evita l’indexació numèrica per a una compatibilitat futura. - Evita mutar l’arbre durant el recorregut: no afegeixis ni eliminis nodes dins la crida recursiva. Recull els resultats primer, després modifica.
- Passa un array de resultats com a paràmetre: això evita assignar un array nou en cada crida recursiva i facilita la fusió dels resultats del subarbre.
Problemes comuns
| Símptoma | Causa | Correcció |
|---|---|---|
childNodes té una longitud zero en rootNode | Model no carregat | Assegura’t scene.open() s’ha completat sense errors abans de recórrer |
node.entity instanceof Mesh mai cert | Incorrecte Mesh ruta d’importació | Importa Mesh des de @aspose/3d/entities, no prové de @aspose/3d arrel |
| El recorregut no detecta malles imbricades | No s’està recorrent tots els fills | Assegureu-vos que la crida recursiva cobreixi cada element a node.childNodes |
mesh.controlPoints.length és 0 | Malla carregada però no conté cap 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
Fa scene.rootNode porta ell mateix una entitat? No. El node arrel és un contenidor creat automàticament per la biblioteca. No té cap entitat. La vostra geometria i altres objectes de l’escena viuen en nodes fills un o més nivells més avall 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 cada node’s childNodes al final de la cua.
És scene.open() sincrònic? Sí. scene.open() i scene.openFromBuffer() ambdós bloquegen el fil que crida fins que el fitxer s’ha analitzat completament. Envolta’ls en un fil de treball si necessites mantenir el bucle d’esdeveniments responsiu.
Com puc obtenir les posicions en l’espai mundial d’un node? Llegeix node.globalTransform; retorna una només de lectura GlobalTransform amb la matriu d’espai mundial, composta a partir de 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 d’esquelet/os personalitzades. Comprova node.entity.constructor.name o utilitza instanceof amb la classe específica importada des de @aspose/3d.