Kako pretraživati 3D graf scene u TypeScript-u
Scene graph u Aspose.3D FOSS za TypeScript je stablo od Node objekata ukorenjenih u scene.rootNode. Traversal je rekurzivan: svaki čvor izlaže childNodes iterable i opcioni entity property. Ovaj vodič pokazuje kako proći kroz celo stablo, identifikovati tipove entiteta i sakupiti mesh statistiku.
Preduslovi
- Node.js 18 ili noviji
- TypeScript 5.0 ili noviji
@aspose/3dinstalirano
Vodič korak po korak
Korak 1: Instalacija i uvoz
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 primeru učitavanja; zamenite odgovarajuću klasu opcija za druge formate.
Korak 2: Učitajte scenu iz fajla
Kreirajte Scene i pozovite scene.open() sa putanjom do fajla. Detekcija formata je automatska na osnovu binarnih magičnih brojeva, tako da ne morate da navodite format za GLB, STL ili 3MF fajlove:
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đe možete učitati iz Buffer u memoriji koristeći scene.openFromBuffer(buffer, options); korisno u serverless pipeline-ovima gde disk I/O nije dostupan.
Korak 3: Napišite rekurzivnu funkciju za pretragu
Rekurzija preko childNodes je standardni obrazac. Funkcija posećuje svaki čvor dubinski:
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 sa jednim mrežom pod imenom 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. The constructor.name provera radi za bilo koji tip entiteta: Mesh, Camera, Light, itd.
Korak 4: Pristup tipu entiteta na svakom čvoru
Da biste preduzeli akciju na osnovu tipa entiteta, koristite an instanceof proveri 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 polygon mesh pre pristupa controlPoints, polygonCount, ili elemente vrha.
Korak 5: Filtriraj čvorove po tipu entiteta
Da biste sakupili samo čvorove koji sadrže mrežu, a da ne ispisujete celo stablo, koristite 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 prihvata opcioni results niz kako bi pozivaoci mogli unapred da ga popune za spajanje rezultata kroz više podstabala.
Korak 6: Prikupi sve mreže i prikaži broj vrhova
Proširite kolektor da ispisuje statistiku po svakoj mreži:
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`);
}Primer izlaza za scenu sa dve mreže:
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>
Saveti i najbolje prakse
- Uvek proveravaj null
node.entitypre pristupa svojstvima specifičnim za entitet. Mnogi čvorovi su čisti grupni čvorovi koji ne nose entitet. - Koristi
instanceofprekoconstructor.nameza proveru tipova u logičkim granama.instanceofje siguran za refaktorisanje; poređenje stringova naconstructor.namese ruši pri minifikaciji. - Prođite putem
for...ofprekochildNodes: iterabilni objekat bezbedno obrađuje sve veličine nizova. Izbegavajte numeričko indeksiranje radi buduće kompatibilnosti. - Izbegavajte mutiranje stabla tokom prolaza: ne dodajte niti uklanjajte čvorove unutar rekurzivnog poziva. Prvo sakupite rezultate, zatim izmenite.
- Prosledite niz rezultata kao parametar: ovo sprečava alociranje novog niza pri svakom rekurzivnom pozivu i olakšava spajanje rezultata podstabla.
Uobičajeni problemi
| Simptom | Uzrok | Ispravka |
|---|---|---|
childNodes ima nultu dužinu na rootNode | Model nije učitan | Osiguraj scene.open() završeno bez greške pre prelaženja |
node.entity instanceof Mesh nikada tačno | Pogrešno Mesh putanja uvoza | Uvoz Mesh od @aspose/3d/entities, ne od @aspose/3d root |
| Pretraga propušta ugnježdene meshes | Nije rekurzija u sve children | Osigurajte da rekurzivni poziv pokriva svaki element u node.childNodes |
mesh.controlPoints.length je 0 | Mesh učitan, ali ne sadrži geometriju | Proverite OBJ izvor za prazne grupe; koristite mesh.polygonCount kao sekundarnu proveru |
| Stack overflow na dubokim hijerarhijama | Veoma duboko stablo scene (stotine nivoa) | Zamenite rekurziju eksplicitnim stekom koristeći Array.push / Array.pop |
Često postavljana pitanja
Da li scene.rootNode sam nosi entitet? Ne. Korenski čvor je kontejner koji biblioteka automatski kreira. On nema entitet. Vaša geometrija i ostali objekti scene nalaze se na čvorovima‑deci jedan ili više nivoa ispod rootNode.
Koja je razlika između node.entity i node.entities? node.entity sadrži jedinstvenu primarnu entitet (uobičajeni slučaj). Neki stariji FBX i COLLADA fajlovi mogu proizvesti čvorove sa više prikačenih entiteta; u tom slučaju node.entities (plural) pruža kompletnu listu.
Mogu li da pretražujem u breadth‑first redosledu umesto depth‑first? Da. Koristite red umesto rekurzivnog poziva: push scene.rootNode u niz, zatim shift i procesirajte čvorove dok push‑ujete svaki čvorov childNodes na kraj reda.
Da li je scene.open() sinhronizovan? Da. scene.open() i scene.openFromBuffer() oba blokiraju pozivajući thread dok se fajl potpuno ne parsira. Umotajte ih u radni thread ako treba da održite odzivnost event loop‑a.
Kako da dobijem pozicije u svetskom prostoru iz čvora? Čitaj node.globalTransform; vraća samo za čitanje GlobalTransform sa matricom u svetskom prostoru, sastavljenom od svih transformacija pretka. Za eksplicitnu matricnu matematiku, pozovite node.evaluateGlobalTransform(false).
Koje vrste entiteta su moguće pored Mesh? Camera, Light, i prilagođene entitete skeleta/kosti. Proveri node.entity.constructor.name ili koristite instanceof sa specifičnom klasom uvezenom iz @aspose/3d.