Hoe een 3D‑scènegraph te doorlopen in TypeScript

Hoe een 3D‑scènegraph te doorlopen in TypeScript

De scene graph in Aspose.3D FOSS voor TypeScript is een boom van Node objecten met scene.rootNode als wortel. Traversal is recursief: elke node exposeert een childNodes iterable en een optionele entity property. Deze gids laat zien hoe je de volledige boom doorloopt, entiteitstypen identificeert en mesh‑statistieken verzamelt.

Voorvereisten

  • Node.js 18 of hoger
  • TypeScript 5.0 of hoger
  • @aspose/3d geïnstalleerd

Stapsgewijze handleiding

Stap 1: Installeren en importeren

Installeer het pakket:

npm install @aspose/3d

Importeer de klassen die in deze gids worden gebruikt:

import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';

Scene en Mesh zijn de kernklassen. ObjLoadOptions wordt gebruikt in het laadvoorbeeld; vervang de bijbehorende optiesklasse voor andere formaten.


Stap 2: Laad een scène vanuit een bestand

Maak een Scene en roep scene.open() aan met een bestandspad. Formaatdetectie gebeurt automatisch op basis van binaire magic numbers, dus u hoeft het formaat voor GLB-, STL- of 3MF-bestanden niet op te geven:

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

U kunt ook laden vanuit een Buffer in het geheugen met behulp van scene.openFromBuffer(buffer, options); handig in serverless‑pijplijnen waar schijf‑I/O niet beschikbaar is.


Stap 3: Schrijf een recursieve traversiefunctie

Recursie over childNodes is het standaardpatroon. De functie bezoekt elk knooppunt diepte‑eerste:

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

Voor een scène met één mesh genaamd Cube, zal de output er als volgt uitzien:

[-] 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=“Kopieer 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 is null voor groepsknooppunten, botten en locators. De constructor.name controle werkt voor elk entiteitstype: Mesh, Camera, Light, enz.


Stap 4: Toegang tot het entiteitstype op elke knoop

Om actie te ondernemen op basis van het entiteitstype, gebruik een instanceof controle na de null‑guard:

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 is de veiligste manier om te bevestigen dat de entiteit een polygon mesh is voordat controlPoints, polygonCount of vertex‑elementen worden benaderd.


Stap 5: Filter knooppunten op entiteitstype

Om alleen mesh-bevattende knooppunten te verzamelen zonder de volledige boom af te drukken, gebruik een recursieve accumulator:

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

De functie accepteert een optionele results array zodat aanroepers deze vooraf kunnen vullen voor het samenvoegen van resultaten over meerdere subbomen.


Stap 6: Verzamel alle meshes en print vertexaantallen

Breid de collector uit om per‑mesh‑statistieken af te drukken:

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

Voorbeeldoutput voor een scène met twee meshes:

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=“Kopieer 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>

Tips en Best Practices

  • Controleer altijd op null node.entity voordat u entiteit‑specifieke eigenschappen benadert. Veel knooppunten zijn zuivere groepsknooppunten die geen entiteit dragen.
  • Gebruik instanceof in plaats van constructor.name voor typecontroles in logische paden. instanceof is refactor‑veilig; tekenreeksvergelijking op constructor.name breekt bij minificatie.
  • Traverseer via for...of in plaats van childNodes: de iterable behandelt alle arraygroottes veilig. Vermijd numerieke indexering voor toekomstige compatibiliteit.
  • Vermijd het muteren van de boom tijdens traverseren: voeg geen knooppunten toe of verwijder ze niet binnen de recursieve oproep. Verzamel eerst de resultaten, wijzig daarna.
  • Geef een resultatenarray als parameter door: dit voorkomt het toewijzen van een nieuwe array bij elke recursieve oproep en maakt het eenvoudig om subboomresultaten samen te voegen.

Veelvoorkomende problemen

SymptoomOorzaakOplossing
childNodes heeft nul lengte op rootNodeModel niet geladenZorg ervoor dat scene.open() voltooid is zonder fouten vóór het traverseren
node.entity instanceof Mesh is nooit waarVerkeerd Mesh importpadImporteer Mesh vanuit @aspose/3d/entities, niet vanuit de @aspose/3d root
Traversie mist geneste meshesNiet recursief door alle kinderen gaanZorg ervoor dat de recursieve aanroep elk element in node.childNodes omvat
mesh.controlPoints.length is 0Mesh geladen maar bevat geen geometrieControleer de OBJ-bron op lege groepen; gebruik mesh.polygonCount als secundaire controle
Stack overflow bij diepe hiërarchieënZeer diepe sceneboom (honderden niveaus)Vervang recursie door een expliciete stack met gebruik van Array.push / Array.pop

Veelgestelde vragen

Draagt scene.rootNode zelf een entiteit? Nee. Het root‑knooppunt is een container die automatisch door de bibliotheek wordt aangemaakt. Het heeft geen entiteit. Uw geometrie en andere scène‑objecten bevinden zich op kind‑knooppunten één of meer niveaus onder rootNode.

Wat is het verschil tussen node.entity en node.entities?
node.entity bevat de enkele primaire entiteit (het gebruikelijke geval). Sommige oudere FBX- en COLLADA‑bestanden kunnen knooppunten produceren met meerdere gekoppelde entiteiten; in dat geval biedt node.entities (meervoud) de volledige lijst.

Kan ik in breedte‑eerst volgorde traverseren in plaats van diepte‑eerst?
Ja. Gebruik een queue in plaats van een recursieve aanroep: push scene.rootNode in een array, verschuif vervolgens en verwerk knooppunten terwijl je elke knooppunt‑childNodes naar de queue‑tail pusht.

Is scene.open() synchroon? Ja. scene.open() en scene.openFromBuffer() blokkeren beide de aanroepende thread totdat het bestand volledig is geparseerd. Plaats ze in een worker thread als je de event loop responsief wilt houden.

Hoe krijg ik wereldruimteposities van een knooppunt?
Lees node.globalTransform; het retourneert een alleen‑lezen GlobalTransform met de wereldruimtematrix, samengesteld uit alle vooroudertransformaties. Voor expliciete matrixberekeningen, roep node.evaluateGlobalTransform(false) aan.

Welke entiteitstypen zijn mogelijk naast Mesh?
Camera, Light, en aangepaste skelet/bot‑entiteiten. Controleer node.entity.constructor.name of gebruik instanceof met de specifieke klasse geïmporteerd uit @aspose/3d.

Zie ook

 Nederlands