Kako proći kroz 3D graf scene u TypeScriptu

Kako proći kroz 3D graf scene u TypeScriptu

Graf scene u Aspose.3D FOSS za TypeScript je stablo Node objekata s korijenom u scene.rootNode. Prolazak je rekurzivan: svaki čvor izlaže childNodes iterabilni i opcionalno entity svojstvo. Ovaj vodič pokazuje kako proći cijelo stablo, identificirati vrste entiteta i prikupiti statistiku mreže.

Preduvjeti

  • Node.js 18 ili noviji
  • TypeScript 5.0 ili noviji
  • @aspose/3d instaliran

Vodič korak po korak

Korak 1: Instaliraj i uvezi

Instalirajte paket:

npm install @aspose/3d

Uvezite klase koje se koriste u ovom vodiču:

import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';

Scene i Mesh su osnovne klase. ObjLoadOptions se koristi u primjeru učitavanja; zamijenite odgovarajuću klasu opcija za druge formate.


Korak 2: Učitaj scenu iz datoteke

Stvorite Scene i pozovite scene.open() s putanjom do datoteke. Detekcija formata je automatska na temelju binarnih magičnih brojeva, pa ne morate navesti format za GLB, STL ili 3MF datoteke:

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}`);

Također možete učitati iz Buffer u memoriji koristeći scene.openFromBuffer(buffer, options); korisno u serverless pipeline-ovima gdje disk I/O nije dostupan.


Korak 3: Napišite rekurzivnu funkciju za traversiranje

Rekurzija preko childNodes je standardni uzorak. Funkcija posjećuje svaki čvor dubinskim pretraživanjem:

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);

Za scenu s jednim mesh-om pod nazivom Cube, izlaz će izgledati ovako:

[-] 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 je null za grupne čvorove, kosti i lokatore. Provjera constructor.name radi za bilo koju vrstu entiteta: Mesh, Camera, Light, itd.


Korak 4: Pristup vrsti entiteta na svakom čvoru

Za poduzimanje radnje na temelju vrste entiteta, koristite instanceof provjeru nakon null zaštite:

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 je najsigurniji način da se potvrdi da je entitet poligonalna mreža prije pristupa controlPoints, polygonCount ili elementima vrha.


Korak 5: Filtriraj čvorove po vrsti entiteta

Za prikupljanje samo čvorova koji sadrže mrežu bez ispisivanja cijelog stabla, upotrijebite rekurzivni akumulator:

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)`);

Funkcija prihvaća opcionalni results niz kako bi pozivatelji mogli unaprijed popuniti ga za spajanje rezultata kroz više podstabala.


Korak 6: Prikupi sve mreže i ispiši broj vrhova

Proširite kolektor da ispisuje statistiku po mesh‑ovima:

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`);
}

Primjer izlaza za scenu s dva mesha:

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>

Savjeti i najbolje prakse

  • Uvijek provjerite null node.entity prije pristupa svojstvima specifičnim za entitet. Mnogi čvorovi su čisti grupni čvorovi koji ne nose entitet.
  • Koristite instanceof umjesto constructor.name za provjere tipa u logičkim granama. instanceof je siguran pri refaktoriranju; usporedba stringova na constructor.name se ruši pri minifikaciji.
  • Prolazite putem for...of umjesto childNodes: iterabilni objekt sigurno obrađuje sve veličine polja. Izbjegavajte numeričko indeksiranje radi buduće kompatibilnosti.
  • Izbjegavajte mutiranje stabla tijekom prolaza: ne dodajte niti uklanjajte čvorove unutar rekurzivnog poziva. Prvo sakupite rezultate, zatim modificirajte.
  • Proslijedite niz rezultata kao parametar: to izbjegava alociranje novog niza pri svakom rekurzivnom pozivu i olakšava spajanje rezultata podstabla.

Uobičajeni problemi

SimptomUzrokRješenje
childNodes ima nultu duljinu na rootNodeModel nije učitanOsigurajte da je scene.open() dovršen bez greške prije traversiranja
node.entity instanceof Mesh nikada nije istinitoPogrešan put uvoza MeshUvezite Mesh iz @aspose/3d/entities, a ne iz korijena @aspose/3d
Traversal propušta ugniježdene mrežeNe rekurzija kroz sve potomkeOsigurajte da rekurzivni poziv pokriva svaki element u node.childNodes
mesh.controlPoints.length je 0Mreža učitana, ali ne sadrži geometrijuProvjerite OBJ izvor za prazne grupe; koristite mesh.polygonCount kao sekundarnu provjeru
Stack overflow na dubokim hijerarhijamaVrlo duboko stablo scene (stotine razina)Zamijenite rekurziju eksplicitnim stogom koristeći Array.push / Array.pop

Često postavljana pitanja

Da li scene.rootNode sam nosi entitet?
Ne. Korijenski čvor je kontejner koji se automatski stvara od strane biblioteke. On nema entitet. Vaša geometrija i ostali objekti scene nalaze se na podčvorovima jedan ili više razina ispod rootNode.

Koja je razlika između node.entity i node.entities?
node.entity sadrži jedinstveni primarni entitet (uobičajeni slučaj). Neki stariji FBX i COLLADA datoteke mogu proizvesti čvorove s više povezanih entiteta; u tom slučaju node.entities (množina) pruža cijeli popis.

Mogu li prolaziti u širinskom redoslijedu umjesto dubinskog?
Da. Koristite red umjesto rekurzivnog poziva: umetnite scene.rootNode u niz, zatim pomaknite i obradite čvorove dok u red na kraj dodajete childNodes svakog čvora.

Je li scene.open() sinkroniziran? Da. scene.open() i scene.openFromBuffer() blokiraju pozivni thread sve dok se datoteka potpuno ne parsira. Umotajte ih u radni thread ako trebate da petlja događaja ostane responzivna.

Kako dobiti pozicije u svjetskom prostoru iz čvora? Pročitajte node.globalTransform; vraća samo‑za‑čitanje GlobalTransform s matricom svjetskog prostora, sastavljenom od svih transformacija pretka. Za izričitu matricnu matematiku, pozovite node.evaluateGlobalTransform(false).

Koje vrste entiteta su moguće osim Mesh?
Camera, Light i prilagođeni entiteti kostura/kosti. Provjerite node.entity.constructor.name ili koristite instanceof s određenom klasom uvezenu iz @aspose/3d.

Vidi također

 Hrvatski