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/3dinstaliran
Vodič korak po korak
Korak 1: Instaliraj i uvezi
Instalirajte paket:
npm install @aspose/3dUvezite 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.entityprije pristupa svojstvima specifičnim za entitet. Mnogi čvorovi su čisti grupni čvorovi koji ne nose entitet. - Koristite
instanceofumjestoconstructor.nameza provjere tipa u logičkim granama.instanceofje siguran pri refaktoriranju; usporedba stringova naconstructor.namese ruši pri minifikaciji. - Prolazite putem
for...ofumjestochildNodes: 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
| Simptom | Uzrok | Rješenje |
|---|---|---|
childNodes ima nultu duljinu na rootNode | Model nije učitan | Osigurajte da je scene.open() dovršen bez greške prije traversiranja |
node.entity instanceof Mesh nikada nije istinito | Pogrešan put uvoza Mesh | Uvezite Mesh iz @aspose/3d/entities, a ne iz korijena @aspose/3d |
| Traversal propušta ugniježdene mreže | Ne rekurzija kroz sve potomke | Osigurajte da rekurzivni poziv pokriva svaki element u node.childNodes |
mesh.controlPoints.length je 0 | Mreža učitana, ali ne sadrži geometriju | Provjerite OBJ izvor za prazne grupe; koristite mesh.polygonCount kao sekundarnu provjeru |
| Stack overflow na dubokim hijerarhijama | Vrlo 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.