Hvordan traversere en 3D-scengraf i TypeScript

Hvordan traversere en 3D-scengraf i TypeScript

Scenegrafen i Aspose.3D FOSS for TypeScript er et tre av Node‑objekter med roten scene.rootNode. Traversering er rekursiv: hver node eksponerer en childNodes‑iterabel og en valgfri entity‑egenskap. Denne guiden viser hvordan du går gjennom hele treet, identifiserer entitetstyper og samler inn mesh‑statistikk.

Forutsetninger

  • Node.js 18 eller nyere
  • TypeScript 5.0 eller nyere
  • @aspose/3d installert

Trinn-for-trinn guide

Trinn 1: Installer og importer

Installer pakken:

npm install @aspose/3d

Importer klassene som brukes i denne veiledningen:

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

Scene og Mesh er kjerneklassene. ObjLoadOptions brukes i lasteksemplet; erstatt den tilsvarende alternativklassen for andre formater.


Steg 2: Last inn en scene fra en fil

Opprett en Scene og kall scene.open() med en filsti. Formatgjenkjenning er automatisk fra binære magiske tall, så du trenger ikke å spesifisere formatet for GLB-, STL- eller 3MF-filer:

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

Du kan også laste fra en Buffer i minnet ved å bruke scene.openFromBuffer(buffer, options); nyttig i serverløse pipelines der disk‑I/O ikke er tilgjengelig.


Steg 3: Skriv en rekursiv traverseringsfunksjon

Rekursjon over childNodes er standardmønsteret. Funksjonen besøker hver node dybde‑først:

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

For en scene med ett mesh som heter Cube, vil output se slik ut:

[-] 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 er null for gruppenoder, bein og lokatorer. Sjekken constructor.name fungerer for enhver entitetstype: Mesh, Camera, Light, osv.


Steg 4: Få tilgang til entitetstypen på hver node

For å utføre handling basert på entitetstype, bruk en instanceof sjekk etter nullbeskyttelsen:

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 er den sikreste måten å bekrefte at enheten er en polygon mesh før du får tilgang til controlPoints, polygonCount eller vertex-elementer.


Steg 5: Filtrer noder etter entitetstype

For å samle kun mesh‑bærende noder uten å skrive ut hele treet, bruk en rekursiv akkumulator:

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

Funksjonen godtar en valgfri results-array slik at kallere kan forhåndsutfylle den for å slå sammen resultater på tvers av flere undertrær.


Steg 6: Samle alle meshene og skriv ut vertex‑tellinger

Utvid samleren til å skrive ut per‑mesh‑statistikk:

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

Eksempel på utdata for en scene med to mesh-er:

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>

Tips og beste praksis

  • Alltid null‑sjekk node.entity før du får tilgang til entitetsspesifikke egenskaper. Mange noder er rene gruppenoder som ikke har noen entitet.
  • Bruk instanceof over constructor.name for typekontroller i logiske veier. instanceof er refaktor‑sikker; strengsammenligning på constructor.name bryter ved minifisering.
  • Traverser via for...of over childNodes: den iterable håndterer alle array‑størrelser trygt. Unngå numerisk indeksering for fremtidig kompatibilitet.
  • Unngå å mutere treet under traversering: ikke legg til eller fjern noder inne i det rekursive kallet. Samle resultater først, deretter endre.
  • Pass en resultarray som parameter: dette unngår å allokere en ny array ved hvert rekursivt kall og gjør det enkelt å slå sammen deltreresultater.

Vanlige problemer

SymptomÅrsakLøsning
childNodes har null lengde på rootNodeModellen er ikke lastetSørg for at scene.open() er fullført uten feil før traversering
node.entity instanceof Mesh er aldri sannFeil importsti for MeshImporter Mesh fra @aspose/3d/entities, ikke fra roten til @aspose/3d
Traversering går glipp av nestede mesh-erGår ikke rekursivt inn i alle barnSørg for at det rekursive kallet dekker hvert element i node.childNodes
mesh.controlPoints.length er 0Mesh lastet, men inneholder ingen geometriSjekk OBJ‑kilden for tomme grupper; bruk mesh.polygonCount som en sekundær sjekk
Stack overflow ved dype hierarkierVeldig dyp scenetre (hundrevis av nivåer)Erstatt rekursjon med en eksplisitt stack ved bruk av Array.push / Array.pop

Ofte stilte spørsmål

Bærer scene.rootNode selv en enhet?
Nei. Rotnoden er en beholder som opprettes automatisk av biblioteket. Den har ingen enhet. Geometrien din og andre scenobjekter ligger på undernoder ett eller flere nivåer under rootNode.

Hva er forskjellen mellom node.entity og node.entities? node.entity inneholder den eneste primære enheten (det vanlige tilfellet). Noen eldre FBX- og COLLADA-filer kan generere noder med flere tilknyttede enheter; i så fall gir node.entities (flertall) den fullstendige listen.

Kan jeg traversere i breadth-first rekkefølge i stedet for depth-first?
Ja. Bruk en queue i stedet for en recursive call: push scene.rootNode inn i en array, så shift og prosesser noder mens du push hver nodes childNodes inn i queue‑tail.

Er scene.open() synkron? Ja. scene.open() og scene.openFromBuffer() blokkerer begge den påkallende tråden til filen er fullstendig analysert. Pakk dem inn i en arbeidstråd hvis du trenger å holde hendelsesløkken responsiv.

Hvordan får jeg verdensromsposisjoner fra en node?
Les node.globalTransform; den returnerer en skrivebeskyttet GlobalTransform med verdensromsmatrisen, sammensatt fra alle foreldertransformasjoner. For eksplisitt matrisematematikk, kall node.evaluateGlobalTransform(false).

Hvilke entitetstyper er mulige i tillegg til Mesh?
Camera, Light, og egendefinerte skjelett-/bein‑entiteter. Sjekk node.entity.constructor.name eller bruk instanceof med den spesifikke klassen importert fra @aspose/3d.

Se også

 Norsk