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/3dinstalleret
Trin-for-trin guide
Trin 1: Installér og importér
Installer 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 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.entityfør du får adgang til entitet‑specifikke egenskaber. Mange noder er rene gruppenoder, der ikke indeholder nogen entitet. - Brug
instanceofi stedet forconstructor.nametil typekontrol i logiske forløb.instanceofer refaktor‑sikker; strengsammenligning påconstructor.namegår i stykker ved minificering. - Traversér via
for...ofi stedet forchildNodes: 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 | Årsag | Løsning |
|---|---|---|
childNodes har nul længde på rootNode | Model ikke indlæst | Sørg for at scene.open() er fuldført uden fejl før du traverserer |
node.entity instanceof Mesh er aldrig sand | Forkert Mesh importsti | Importér Mesh fra @aspose/3d/entities, ikke fra roden @aspose/3d |
| 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 ved dybe hierarkier | Meget 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.