Cum să parcurgi un grafic de scenă 3D în TypeScript

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/3d instalat

Ghid pas cu pas

Pasul 1: Instalează și importă

Instalați pachetul:

npm install @aspose/3d

Importaț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 de constructor.name pentru verificări de tip în căile logice. instanceof este sigur la refactorizare; compararea șirurilor pe constructor.name se rupe la minificare.
  • Parcurgeți prin for...of în loc de childNodes: 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

SimptomCauzăRemediere
childNodes are lungime zero pe rootNodeModelul nu este încărcatAsigurați-vă că scene.open() s-a finalizat fără eroare înainte de traversare
node.entity instanceof Mesh nu este niciodată adevăratCalea de importare Mesh incorectăImportați Mesh din @aspose/3d/entities, nu din rădăcina @aspose/3d
Traversarea omite plasele încorporateNu se recursionează în toți copiiiAsigurați-vă că apelul recursiv acoperă fiecare element din node.childNodes
mesh.controlPoints.length este 0Plasa a fost încărcată, dar nu conține geometrieVerificați sursa OBJ pentru grupuri goale; utilizați mesh.polygonCount ca verificare secundară
Depășire de stivă în ierarhii adânciArbore 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.

Vezi și

 Română