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/3dinstallerad
Steg-för-steg-guide
Steg 1: Installera och importera
Installera paketet:
npm install @aspose/3dImportera 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.entityinnan du får åtkomst till entitetsspecifika egenskaper. Många noder är rena gruppnoder som inte har någon entitet. - Använd
instanceoföverconstructor.nameför typkontroller i logikvägar.instanceofär refaktor-säker; strängjämförelse påconstructor.namegår sönder vid minifiering. - Traversera via
for...oföverchildNodes: 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
| Symptom | Orsak | Åtgärd |
|---|---|---|
childNodes har noll längd på rootNode | Modell inte laddad | Säkerställ scene.open() slutförd utan fel innan traversering |
node.entity instanceof Mesh aldrig sann | Fel Mesh importväg | Import Mesh från @aspose/3d/entities, inte från @aspose/3d rot |
| Traversering missar nästlade meshar | Rekursion sker inte i alla barn | Säkerställ att det rekursiva anropet täcker varje element i node.childNodes |
mesh.controlPoints.length är 0 | Mesh laddad men innehåller ingen geometri | Kontrollera OBJ-källan för tomma grupper; använd mesh.polygonCount som en sekundär kontroll |
| Stack overflow på djupa hierarkier | Mycket 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.