Sådan traverserer du en 3D Scene Graph i TypeScript
Scenegrafen i Aspose.3D FOSS for TypeScript er et træ af Node objekter rodfæstet ved scene.rootNode. Traversering er rekursiv: hver node eksponerer en childNodes iterabel og en valgfri entity egenskab. Denne guide viser, hvordan man traverserer hele træet, identificerer entitetstyper og indsamler mesh-statistik.
Forudsætninger
- Node.js 18 eller nyere
- TypeScript 5.0 eller nyere
@aspose/3dinstalleret
Trin-for-trin guide
Trin 1: Installér og importér
Installér pakken:
npm install @aspose/3dImporté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 de grundlæggende klasser. ObjLoadOptions bruges i indlæsnings‑eksemplet; erstat med 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 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ønstret. 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, outputtet vil 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. Den constructor.name tjek 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 tjek 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 entiteten er et polygon mesh, før du får adgang til controlPoints, polygonCount, eller vertex-elementer.
Trin 5: Filtrer noder efter entitetstype
For kun at indsamle noder, der indeholder mesh, 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 en valgfri results array, så kaldere kan forudfylde den for at sammenlægge resultater på tværs af flere undertræer.
Trin 6: Indsaml alle meshes og udskriv vertex‑tællinger
Udvid samleren til at udskrive statistik pr. 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`);
}Eksempeloutput for en scene med to 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=“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
- Kontroller altid for null
node.entityfør du får adgang til entitetsspecifikke egenskaber. Mange noder er rene gruppenoder, der ikke indeholder nogen entitet. - Brug
instanceofoverconstructor.nametil typekontrol i logikstier.instanceofer refaktor-sikker; strengsammenligning påconstructor.namegår i stykker ved minificering. - Traversér via
for...ofoverchildNodes: den itererbare håndterer alle array-størrelser sikkert. Undgå numerisk indeksering for fremadkompatibilitet. - Undgå at mutere træet under traversering: tilføj eller fjern ikke noder inde i det rekursive kald. Saml resultater først, og modificér derefter.
- Videregiv et resultatarray som parameter: dette undgår at allokere et nyt array ved hvert rekursivt kald og gør det nemt at flette undertræresultater.
Almindelige problemer
| Symptom | Årsag | Rettelse |
|---|---|---|
childNodes har nul længde på rootNode | Model ikke indlæst | Sørg for scene.open() fuldført uden fejl før gennemløb |
node.entity instanceof Mesh aldrig sand | Forkert Mesh importsti | Import Mesh fra @aspose/3d/entities, ikke fra @aspose/3d root |
| Traversal overser indlejrede mesh’er | Går ikke rekursivt ind i alle børn | Sørg for, at det rekursive kald dækker hvert element i node.childNodes |
mesh.controlPoints.length er 0 | Mesh indlæst, men indeholder ingen geometri | Tjek OBJ-kilden for tomme grupper; brug mesh.polygonCount som en sekundær kontrol |
| Stack overflow på dybe hierarkier | Meget dybt scenetræ (hundredevis af niveauer) | Erstat rekursion med en eksplicit stak ved brug af Array.push / Array.pop |
Ofte stillede spørgsmål
Gør scene.rootNode bærer den selv en entitet? Nej. Rodnoden er en container, der oprettes automatisk af biblioteket. Den har ingen entitet. Din geometri og andre sceneobjekter lever på undernoder én eller flere niveauer nedenunder rootNode.
Hvad er forskellen mellem node.entity og node.entities? node.entity holder den eneste primære entitet (det almindelige tilfælde). Nogle ældre FBX- og COLLADA-filer kan producere noder med flere vedhæftede entiteter; i så fald node.entities (flertal) giver den fulde liste.
Kan jeg traversere i bredde-først rækkefølge i stedet for dybde-først? Ja. Brug en kø i stedet for et rekursivt kald: push scene.rootNode til en array, derefter forskyd og behandl noder, mens du skubber hver nodes childNodes til køens hale.
Er scene.open() synkron? Ja. scene.open() og scene.openFromBuffer() begge blokerer den kaldende tråd, indtil filen er fuldt analyseret. Pak dem ind i en worker-tråd, hvis du har brug for at holde event‑loopet responsivt.
Hvordan får jeg verdensrum‑positioner fra en node? Læs node.globalTransform; den returnerer en skrivebeskyttet GlobalTransform med verdensrum‑matricen, sammensat af alle forældre‑transformeringer. For eksplicit matrix‑matematik, kald node.evaluateGlobalTransform(false).
Hvilke entitetstyper er mulige udover Mesh? Camera, Light, og brugerdefinerede skelet-/knogle‑entiteter. Tjek node.entity.constructor.name eller brug instanceof med den specifikke klasse importeret fra @aspose/3d.