Hur man traverserar ett 3D‑scengraf i TypeScript

Hur man traverserar ett 3D‑scengraf i TypeScript

Scengrafen i Aspose.3D FOSS för TypeScript är ett träd av Node objekt med rot i scene.rootNode. Traversering är rekursiv: varje nod exponerar en childNodes itererbar och en valfri entity egenskap. Denna guide visar hur man traverserar hela trädet, identifierar entitetstyper och samlar mesh-statistik.

Förutsättningar

  • Node.js 18 eller senare
  • TypeScript 5.0 eller senare
  • @aspose/3d installerad

Steg-för-steg-guide

Steg 1: Installera och importera

Installera paketet:

npm install @aspose/3d

Importera klasserna som används i den här guiden:

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

Scene och Mesh är de grundläggande klasserna. ObjLoadOptions används i laddningsexemplet; ersätt med den matchande options-klassen för andra format.


Steg 2: Läs in en scen från en fil

Skapa en Scene och anropa scene.open() med en filsökväg. Formatdetektering sker automatiskt från binära magiska tal, så du behöver inte ange formatet för 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 också ladda från en Buffer i minnet med hjälp av scene.openFromBuffer(buffer, options); användbart i serverlösa pipelines där disk‑I/O inte är tillgängligt.


Steg 3: Skriv en rekursiv traverseringsfunktion

Rekursion över childNodes är det standardmönstret. Funktionen besöker varje nod djupfö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);

För en scen med ett mesh som heter Cube, utdata kommer att se ut så här:

[-] 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 är null för gruppnoder, ben och lokatorer. Den constructor.name kontrollen fungerar för alla entitetstyper: Mesh, Camera, Light, osv.


Steg 4: Åtkomst till entitetstypen på varje nod

För att vidta åtgärder baserat på entitetstyp, använd en instanceof kontroll efter null-skyddet:

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 är det säkraste sättet att bekräfta att entiteten är ett polygonnät innan åtkomst till controlPoints, polygonCount, eller vertex-element.


Steg 5: Filtrera noder efter entitetstyp

För att samla endast mesh‑innehållande noder utan att skriva ut hela trädet, använd en rekursiv ackumulator:

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

Funktionen accepterar en valfri results array så att anroparna kan förfylla den för att slå samman resultat över flera underträd.


Steg 6: Samla alla meshar och skriv ut vertexantal

Utöka samlaren för att skriva ut statistik per-mesh:

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

Exempelutdata för en scen med två mesh:

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 och bästa praxis

  • Kontrollera alltid null node.entity innan du får åtkomst till entitetsspecifika egenskaper. Många noder är rena gruppnoder som inte har någon entitet.
  • Använd instanceof över constructor.name för typkontroller i logikvägar. instanceof är refaktor-säker; strängjämförelse på constructor.name går sönder vid minifiering.
  • Traversera via for...of över childNodes: den itererbara hanterar alla arraystorlekar säkert. Undvik numerisk indexering för framtida kompatibilitet.
  • Undvik att mutera trädet under traversering: lägg inte till eller ta bort noder inom det rekursiva anropet. Samla resultat först, modifiera sedan.
  • Skicka en resultatarray som parameter: detta undviker att allokera en ny array vid varje rekursivt anrop och gör det enkelt att slå ihop delträdresultat.

Vanliga problem

SymptomOrsakÅtgärd
childNodes har noll längd på rootNodeModell inte laddadSäkerställ scene.open() slutförd utan fel innan traversering
node.entity instanceof Mesh aldrig sannFel Mesh importvägImport Mesh från @aspose/3d/entities, inte från @aspose/3d rot
Traversering missar nästlade mesharRekursion sker inte i alla barnSäkerställ att det rekursiva anropet täcker varje element i node.childNodes
mesh.controlPoints.length är 0Mesh laddad men innehåller ingen geometriKontrollera OBJ-källan för tomma grupper; använd mesh.polygonCount som en sekundär kontroll
Stack overflow på djupa hierarkierMycket djupt scenträd (hundratals nivåer)Ersätt rekursion med en explicit stack med hjälp av Array.push / Array.pop

Vanliga frågor

Gör scene.rootNode bär den själv en entitet? Nej. Rotnoden är en behållare som skapas automatiskt av biblioteket. Den har ingen entitet. Din geometri och andra scenobjekt finns på barnnoder en eller flera nivåer nedanför rootNode.

Vad är skillnaden mellan node.entity och node.entities? node.entity innehåller den enda primära entiteten (det vanliga fallet). Vissa äldre FBX- och COLLADA-filer kan skapa noder med flera bifogade entiteter; i så fall node.entities (plural) ger den fullständiga listan.

Kan jag gå igenom i bredd-först-ordning istället för djup-först? Ja. Använd en kö istället för ett rekursivt anrop: push scene.rootNode till en array, sedan skifta och bearbeta noder medan varje nods childNodes in i kösvansen.

Är scene.open() synkron? Ja. scene.open() och scene.openFromBuffer() båda blockerar den anropande tråden tills filen är helt parsad. Packa in dem i en arbetstråd om du behöver hålla händelseslingan responsiv.

Hur får jag världsrumspositioner från en nod? Läs node.globalTransform; den returnerar en skrivskyddad GlobalTransform med världsrumsmatrisen, sammansatt från alla förfäders transformationer. För explicit matrisberäkning, anropa node.evaluateGlobalTransform(false).

Vilka entitetstyper är möjliga förutom Mesh? Camera, Light, och anpassade skelett-/benentiteter. Kontrollera node.entity.constructor.name eller använd instanceof med den specifika klassen importerad från @aspose/3d.

Se även

 Svenska