Kuinka käydä läpi 3D-skenegraafi TypeScriptissä
Kohtausgrafi Aspose.3D FOSS for TypeScript -kirjastossa on puu Node-objekteista, jonka juuri on scene.rootNode. Traversointi on rekursiivista: jokainen solmu tarjoaa childNodes-iteroinnin ja valinnaisen entity-ominaisuuden. Tämä opas näyttää, miten käydään läpi koko puu, tunnistetaan entiteettityypit ja kerätään verkkojen tilastot.
Edellytykset
- Node.js 18 tai uudempi
- TypeScript 5.0 tai uudempi
@aspose/3dasennettu
Vaiheittainen opas
Vaihe 1: Asenna ja tuo
Asenna paketti:
npm install @aspose/3dTuo tässä oppaassa käytetyt luokat:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene ja Mesh ovat ydinklassit. ObjLoadOptions on käytetty latausesimerkissä; korvaa se vastaavalla asetuskoodilla muille formaateille.
Vaihe 2: Lataa kohtaus tiedostosta
Luo Scene ja kutsu scene.open() tiedostopolulla. Muodon tunnistus tapahtuu automaattisesti binaarisista taikamerkeistä, joten sinun ei tarvitse määrittää formaattia GLB-, STL- tai 3MF‑tiedostoille:
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}`);Voit myös ladata Buffer muistista käyttämällä scene.openFromBuffer(buffer, options); hyödyllistä serverittömissä putkissa, joissa levy‑I/O ei ole käytettävissä.
Vaihe 3: Kirjoita rekursiivinen läpikäyntifunktio
Rekursio childNodes:n yli on vakiomalli. Funktio käy läpi jokaisen solmun syvyysensimmäisenä:
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);Kun kohtauksessa on yksi verkko nimeltä Cube, tuloste näyttää tältä:
[-] 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 on null ryhmäsolmuille, luuille ja paikannuksille. constructor.name tarkistus toimii kaikille entiteettityypeille: Mesh, Camera, Light, jne.
Vaihe 4: Hae entiteettityyppi jokaiselta solmulta
Suorittaaksesi toiminnon entiteettityypin perusteella, käytä instanceof tarkistusta nollasuojaimen jälkeen:
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 on turvallisin tapa varmistaa, että entiteetti on polygoniverkko ennen kuin käytetään controlPoints, polygonCount tai vertex-elementtejä.
Vaihe 5: Suodata solmut entiteettityypin mukaan
Kerääksesi vain verkkoa kantavat solmut ilman koko puun tulostamista, käytä rekursiivista kertyjää:
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)`);Funktio hyväksyy valinnaisen results-taulukon, jotta kutsujat voivat esitäyttää sen yhdistääkseen tuloksia useiden alipuiden välillä.
Vaihe 6: Kerää kaikki verkot ja tulosta solmujen määrät
Laajenna kerääjää tulostamaan per‑verkko‑tilastot:
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`);
}Esimerkkituloste kahden verkon kohtaukselle:
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>
Vinkkejä ja parhaita käytäntöjä
- Tarkista aina null
node.entityennen kuin käytät entiteettiin liittyviä ominaisuuksia. Monet solmut ovat puhtaita ryhmäsolmuja, jotka eivät sisällä entiteettiä. - Käytä
instanceofsen sijaan, että käyttäisitconstructor.nametyyppitarkistuksiin loogisissa poluissa.instanceofon refaktorointiturvallinen; merkkijonojen vertailuconstructor.name:ssa rikkoutuu minifioinnissa. - Kulje
for...of:n kauttachildNodes:n sijaan: iteroitava käsittelee kaikki taulukon koot turvallisesti. Vältä numeerista indeksointia tulevaisuuden yhteensopivuuden vuoksi. - Vältä puun muokkaamista kulun aikana: älä lisää tai poista solmuja rekursiivisen kutsun sisällä. Kerää tulokset ensin, sitten muokkaa.
- Välitä tuloslista parametrina: tämä estää uuden taulukon varaamisen jokaisessa rekursiivisessa kutsussa ja tekee alipuiden tulosten yhdistämisestä helppoa.
Yleiset ongelmat
| Oire | Syy | Korjaus |
|---|---|---|
childNodes on nollapituinen kohteessa rootNode | Malli ei ladattu | Varmista, että scene.open() on suoritettu ilman virheitä ennen läpikäyntiä |
node.entity instanceof Mesh ei koskaan tosi | Väärä Mesh tuontipolku | Tuo Mesh kohteesta @aspose/3d/entities, äläkä kohteesta @aspose/3d juuri |
| Läpikäynti ohittaa sisäkkäiset verkot | Ei rekursiota kaikkiin lapsiin | Varmista, että rekursiivinen kutsu kattaa jokaisen elementin kohteessa node.childNodes |
mesh.controlPoints.length on 0 | Verkko ladattu, mutta ei sisällä geometriaa | Tarkista OBJ-lähde tyhjien ryhmien varalta; käytä mesh.polygonCount toissijaisena tarkistuksena |
| Pinon ylivuoto syvissä hierarkioissa | Erittäin syvä kohtauspuu (satoja tasoja) | Korvaa rekursio eksplisiittisellä pinolla käyttäen Array.push / Array.pop |
Usein kysytyt kysymykset
Sisältääkö scene.rootNode itse entiteetin?
Ei. Juurisolmu on kirjastoautomaatisesti luotu säiliö. Sillä ei ole entiteettiä. Geometriasi ja muut kohtausobjektit sijaitsevat lapsisolmuissa yhden tai useamman tason alapuolella rootNode.
Mikä on ero node.entity ja node.entities välillä?
node.entity sisältää yhden ensisijaisen entiteetin (yleinen tapa). Jotkut vanhemmat FBX- ja COLLADA-tiedostot saattavat tuottaa solmuja, joissa on useita liitettyjä entiteettejä; siinä tapauksessa node.entities (monikko) tarjoaa koko luettelon.
Voinko kulkea leveyssuuntaisesti syvyysjärjestyksen sijaan?
Kyllä. Käytä queuea rekursiivisen kutsun sijaan: push scene.rootNode arrayen, sitten shift ja käsittele solmut samalla kun push jokaisen solmun childNodes queue:n hännän.
Onko scene.open() synkroninen?
Kyllä. scene.open() ja scene.openFromBuffer() estävät kutsuvan säikeen toiminnan, kunnes tiedosto on täysin jäsennetty. Kääri ne työntekijäsäikeeseen, jos sinun täytyy pitää tapahtumasilmukka reagoivana.
Miten saan maailmanavaruuden sijainnit solmusta?
Lue node.globalTransform; se palauttaa vain‑luku GlobalTransform, jossa on maailmanavaruuden matriisi, koostettuna kaikista esivanhempien muunnoksista. Jos haluat tehdä eksplisiittistä matriisilaskentaa, kutsu node.evaluateGlobalTransform(false).
Mitä entiteettityyppejä on mahdollista käyttää Meshn lisäksi?Camera, Light ja mukautetut luuranko/luu‑entiteetit. Tarkista node.entity.constructor.name tai käytä instanceof yhdessä erityisen luokan kanssa, joka on tuotu @aspose/3dsta.