Cum să parcurgi un grafic de scenă 3D în TypeScript
Graful de scenă în Aspose.3D FOSS pentru TypeScript este un arbore de obiecte Node cu rădăcina la scene.rootNode. Traversarea este recursivă: fiecare nod expune un iterabil childNodes și o proprietate opțională entity. Acest ghid arată cum să parcurgi întregul arbore, să identifici tipurile de entități și să colectezi statistici despre mesh-uri.
Cerințe preliminare
- Node.js 18 sau o versiune ulterioară
- TypeScript 5.0 sau o versiune ulterioară
@aspose/3dinstalat
Ghid pas cu pas
Pasul 1: Instalează și importă
Instalați pachetul:
npm install @aspose/3dImportați clasele utilizate în acest ghid:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene și Mesh sunt clasele de bază. ObjLoadOptions este utilizat în exemplul de încărcare; înlocuiţi clasa de opţiuni corespunzătoare pentru alte formate.
Pasul 2: Încarcă o scenă dintr-un fișier
Creează un Scene și apelează scene.open() cu o cale de fișier. Detectarea formatului este automată pe baza numerelor magice binare, astfel că nu este necesar să specifici formatul pentru fișierele GLB, STL sau 3MF:
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}`);De asemenea, puteți încărca dintr-un Buffer în memorie utilizând scene.openFromBuffer(buffer, options); util în conductele serverless în care I/O pe disc nu este disponibil.
Pasul 3: Scrie o funcție de traversare recursivă
Recursiunea peste childNodes este modelul standard. Funcția vizitează fiecare nod în adâncime:
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);Pentru o scenă cu un singur mesh numit Cube, rezultatul va arăta astfel:
[-] 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=“Copiază codul”
<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 este null pentru noduri de grup, oase și localizatoare. Verificarea constructor.name funcționează pentru orice tip de entitate: Mesh, Camera, Light, etc.
Pasul 4: Accesați tipul de entitate pe fiecare nod
Pentru a acționa în funcție de tipul entității, utilizați o verificare instanceof după protecția null:
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 este cea mai sigură modalitate de a confirma că entitatea este o plasă poligonală înainte de a accesa controlPoints, polygonCount sau elementele de vârf.
Pasul 5: Filtrarea nodurilor după tipul entității
Pentru a colecta doar nodurile care conțin mesh fără a tipări întregul arbore, folosiți un acumulatoare recursiv:
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)`);Funcția acceptă un tablou opțional results, astfel încât apelanții să îl poată pre-popula pentru a îmbina rezultatele din mai multe subarbori.
Pasul 6: Colectează toate mesh-urile și afișează numărul de vârfuri
Extindeți colectorul pentru a afișa statistici pe fiecare 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`);
}Exemplu de ieșire pentru o scenă cu două mesh-uri:
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=“Copiază codul”
<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>
Sfaturi și cele mai bune practici
- Verificați întotdeauna nulitatea
node.entityînainte de a accesa proprietățile specifice entității. Multe noduri sunt noduri de grup pure care nu conțin nicio entitate. - Utilizați
instanceofîn loc deconstructor.namepentru verificări de tip în căile logice.instanceofeste sigur la refactorizare; compararea șirurilor peconstructor.namese rupe la minificare. - Parcurgeți prin
for...ofîn loc dechildNodes: iteratorul gestionează în siguranță toate dimensiunile de array. Evitați indexarea numerică pentru compatibilitate viitoare. - Evitați modificarea arborelui în timpul parcurgerii: nu adăugați sau eliminați noduri în interiorul apelului recursiv. Colectați rezultatele mai întâi, apoi modificați.
- Transmiteți un tablou de rezultate ca parametru: aceasta evită alocarea unui nou tablou la fiecare apel recursiv și facilitează îmbinarea rezultatelor subarborelui.
Probleme comune
| Simptom | Cauză | Remediere |
|---|---|---|
childNodes are lungime zero pe rootNode | Modelul nu este încărcat | Asigurați-vă că scene.open() s-a finalizat fără eroare înainte de traversare |
node.entity instanceof Mesh nu este niciodată adevărat | Calea de importare Mesh incorectă | Importați Mesh din @aspose/3d/entities, nu din rădăcina @aspose/3d |
| Traversarea omite plasele încorporate | Nu se recursionează în toți copiii | Asigurați-vă că apelul recursiv acoperă fiecare element din node.childNodes |
mesh.controlPoints.length este 0 | Plasa a fost încărcată, dar nu conține geometrie | Verificați sursa OBJ pentru grupuri goale; utilizați mesh.polygonCount ca verificare secundară |
| Depășire de stivă în ierarhii adânci | Arbore de scenă foarte adânc (sute de niveluri) | Înlocuiți recursiunea cu o stivă explicită utilizând Array.push / Array.pop |
Întrebări frecvente
Are scene.rootNode în sine o entitate?
Nu. Nodul rădăcină este un container creat automat de bibliotecă. Nu are entitate. Geometria și alte obiecte ale scenei trăiesc pe noduri copil la unul sau mai multe niveluri sub rootNode.
Care este diferența dintre node.entity și node.entities?node.entity conține entitatea principală unică (cazul obișnuit). Unele fișiere FBX și COLLADA mai vechi pot genera noduri cu mai multe entități atașate; în acest caz node.entities (plural) furnizează lista completă.
Pot să parcurg în ordine breadth-first în loc de depth-first?
Da. Folosește o coadă în loc de un apel recursiv: împinge scene.rootNode într-un array, apoi efectuează shift și procesează nodurile în timp ce împingi childNodes fiecărui nod în coada de la coadă.
Este scene.open() sincron?
Da. scene.open() și scene.openFromBuffer() blochează firul de execuție apelant până când fișierul este complet analizat. Înfășurați-le într-un fir de lucru dacă trebuie să mențineți bucla de evenimente receptivă.
Cum pot obține poziții în spațiul mondial de la un nod?
Citește node.globalTransform; returnează un GlobalTransform numai în citire cu matricea în spațiul mondial, compusă din toate transformările strămoșilor. Pentru matematică explicită a matricelor, apelează node.evaluateGlobalTransform(false).
Ce tipuri de entități sunt posibile în afară de Mesh?Camera, Light și entități personalizate de schelet/os. Verificați node.entity.constructor.name sau utilizați instanceof cu clasa specifică importată din @aspose/3d.