Sådan traverserer du en 3D‑scengraf i TypeScript

Sådan traverserer du en 3D‑scengraf i TypeScript

Scenegrafen i Aspose.3D FOSS for TypeScript er et træ af Node‑objekter med roden i scene.rootNode. Traversering er rekursiv: hver node eksponerer en childNodes‑iterabel og en valgfri entity‑egenskab. Denne vejledning viser, hvordan man gennemløber hele træet, identificerer entitetstyper og indsamler mesh‑statistik.

Forudsætninger

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

Trin-for-trin guide

Trin 1: Installér og importér

Installer pakken:

npm install @aspose/3d

Importér de klasser, der bruges i denne vejledning:

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

Scene og Mesh er kerneklasserne. ObjLoadOptions bruges i indlæsnings‑eksemplet; erstat den tilsvarende options‑klasse for andre formater.


Trin 2: Indlæs en scene fra en fil

Opret en Scene og kald scene.open() med en filsti. Formatdetektering er automatisk ud fra binære magiske tal, så du behøver ikke at angive 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å indlæse fra en Buffer i hukommelsen ved hjælp af scene.openFromBuffer(buffer, options); nyttigt i serverløse pipelines, hvor disk I/O ikke er tilgængelig.


Trin 3: Skriv en rekursiv traverseringsfunktion

Rekursion over childNodes er standardmønsteret. Funktionen besøger hver node i dybde‑første rækkefølge:

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 ét mesh ved navn Cube, vil outputtet se sådan ud:

[-] 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, knogler og lokatorer. constructor.name-kontrollen fungerer for enhver entitetstype: Mesh, Camera, Light osv.


Trin 4: Få adgang til entitetstypen på hver node

For at udføre handling baseret på entitetstype, brug en instanceof kontrol efter null‑beskyttelsen:

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åde at bekræfte, at enheden er et polygonnet, før du får adgang til controlPoints, polygonCount eller vertex-elementer.


Trin 5: Filtrer noder efter entitetstype

For kun at indsamle mesh‑bærende noder uden at udskrive hele træet, brug 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)`);

Funktionen accepterer et valgfrit results‑array, så kaldere kan forudfylde det for at sammenlægge resultater på tværs af flere undertræer.


Trin 6: Indsaml alle mesh’er og udskriv vertex‑tællinger

Udvid samleren til at udskrive per‑mesh‑statistikker:

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å output 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 bedste praksis

  • Foretag altid null‑tjek node.entity før du får adgang til entitet‑specifikke egenskaber. Mange noder er rene gruppenoder, der ikke indeholder nogen entitet.
  • Brug instanceof i stedet for constructor.name til typekontrol i logiske forløb. instanceof er refaktor‑sikker; strengsammenligning på constructor.name går i stykker ved minificering.
  • Traversér via for...of i stedet for childNodes: den iterable håndterer alle array‑størrelser sikkert. Undgå numerisk indeksering for fremtidig kompatibilitet.
  • Undgå at mutere træet under traversering: tilføj eller fjern ikke noder inde i det rekursive kald. Indsaml resultater først, og modificér derefter.
  • Send et resultatarray som parameter: dette undgår at allokere et nyt array ved hvert rekursivt kald og gør det nemt at sammenlægge undertræ‑resultater.

Almindelige problemer

SymptomÅrsagLøsning
childNodes har nul længde på rootNodeModel ikke indlæstSørg for at scene.open() er fuldført uden fejl før du traverserer
node.entity instanceof Mesh er aldrig sandForkert Mesh importstiImportér Mesh fra @aspose/3d/entities, ikke fra roden @aspose/3d
Traversal overser indlejrede mesh’erGår ikke rekursivt ind i alle børnSørg for at det rekursive kald dækker hvert element i node.childNodes
mesh.controlPoints.length er 0Mesh indlæst men indeholder ingen geometriTjek OBJ‑kilden for tomme grupper; brug mesh.polygonCount som en sekundær kontrol
Stack overflow ved dybe hierarkierMeget dybt scenetræ (hundredevis af niveauer)Erstat rekursion med en eksplicit stack ved brug af Array.push / Array.pop

Ofte stillede spørgsmål

Bærer scene.rootNode selv en entity?
Nej. Rodnoden er en container, der oprettes automatisk af biblioteket. Den har ingen entity. Din geometri og andre scene‑objekter lever på undernoder et eller flere niveauer under rootNode.

Hvad er forskellen mellem node.entity og node.entities? node.entity indeholder den enkelte primære enhed (det almindelige tilfælde). Nogle ældre FBX- og COLLADA-filer kan producere noder med flere tilknyttede enheder; i så fald giver node.entities (flertal) den fulde liste.

Kan jeg traversere i bredde‑første rækkefølge i stedet for dybde‑første?
Ja. Brug en kø i stedet for et rekursivt kald: push scene.rootNode ind i et array, derefter shift og behandl noder mens du push hver nodes childNodes ind i køens hale.

Er scene.open() synkron?
Ja. scene.open() og scene.openFromBuffer() blokerer begge den kaldende tråd, indtil filen er fuldt analyseret. Pak dem ind i en worker‑tråd, hvis du har brug for at holde begivenhedsløkken responsiv.

Hvordan får jeg verdensrumpositioner fra en node? Læs node.globalTransform; den returnerer en skrivebeskyttet GlobalTransform med verdensrummatricen, sammensat af alle forfædres transformationer. For eksplicit matrixmatematik, kald node.evaluateGlobalTransform(false).

Hvilke entitetstyper er mulige udover Mesh?
Camera, Light og brugerdefinerede skelet/ben‑entiteter. Tjek node.entity.constructor.name eller brug instanceof med den specifikke klasse importeret fra @aspose/3d.

Se også

 Dansk