Com recórrer un gràfic de escena 3D en TypeScript

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/3d instal·lat

Guia pas a pas

Pas 1: Instal·la i importa

Instal·la el paquet:

npm install @aspose/3d

Importa 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.entity abans d’accedir a les propietats específiques de l’entitat. Molts nodes són nodes de grup purs que no contenen cap entitat.
  • Utilitzeu instanceof en comptes de constructor.name per a comprovacions de tipus en rutes lògiques. instanceof és segur per a refactoritzacions; la comparació de cadenes amb constructor.name es trenca amb la minificació.
  • Recorreu via for...of en comptes de childNodes: 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ímptomaCausaSolució
childNodes té longitud zero a rootNodeModel no carregatAssegureu-vos que scene.open() s’ha completat sense errors abans de recórrer
node.entity instanceof Mesh mai és certCamí d’importació de Mesh incorrecteImporteu Mesh des de @aspose/3d/entities, no des de l’arrel @aspose/3d
El recorregut omet malles anidadesNo es recorre recursivament tots els fillsAssegureu que la crida recursiva cobreixi cada element a node.childNodes
mesh.controlPoints.length és 0Malla carregada però no conté geometriaComproveu la font OBJ per a grups buits; utilitzeu mesh.polygonCount com a comprovació secundària
Desbordament de pila en jerarquies profundesArbre 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.

Vegeu també

 Català