Jak procházet 3D scénový graf v TypeScriptu

Jak procházet 3D scénový graf v TypeScriptu

Scénový graf v Aspose.3D FOSS pro TypeScript je stromem Node objektů zakořeněných v scene.rootNode. Procházení je rekurzivní: každý uzel poskytuje childNodes iterovatelný objekt a volitelný entity vlastnost. Tento průvodce ukazuje, jak projít celý strom, identifikovat typy entit a shromáždit statistiky mesh.

Požadavky

  • Node.js 18 nebo novější
  • TypeScript 5.0 nebo novější
  • @aspose/3d nainstalováno

Průvodce krok za krokem

Krok 1: Instalace a import

Nainstalujte balíček:

npm install @aspose/3d

Importujte třídy použité v tomto návodu:

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

Scene a Mesh jsou základní třídy. ObjLoadOptions se používá v příkladu načítání; nahraďte odpovídající třídu možností pro jiné formáty.


Krok 2: Načtení scény ze souboru

Vytvořte Scene a zavolejte scene.open() s cestou k souboru. Detekce formátu je automatická na základě binárních magických čísel, takže není nutné specifikovat formát pro soubory GLB, STL nebo 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}`);

Můžete také načíst z Buffer v paměti pomocí scene.openFromBuffer(buffer, options); užitečné v serverless pipelinech, kde není k dispozici diskové I/O.


Krok 3: Napsat rekurzivní funkci pro procházení

Rekurze přes childNodes je standardní vzor. Funkce prochází každý uzel do hloubky:

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

Pro scénu s jedním meshem pojmenovaným Cube, výstup bude vypadat takto:

[-] 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=“Zkopírovat kód”

<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 pro skupinové uzly, kosti a lokátory. Tento constructor.name kontrola funguje pro libovolný typ entity: Mesh, Camera, Light, atd.


Krok 4: Získání typu entity u každého uzlu

Pro provedení akce na základě typu entity použijte instanceof kontrolu po kontrole na 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 je nejbezpečnější způsob, jak potvrdit, že entita je polygonová síť, před přístupem k controlPoints, polygonCount, nebo k vrcholovým prvkům.


Krok 5: Filtrování uzlů podle typu entity

Pro sběr pouze uzlů obsahujících mesh bez výpisu celého stromu použijte rekurzivní akumulátor:

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

Funkce přijímá volitelný results pole, aby volající mohli předem naplnit pro sloučení výsledků napříč více podstromy.


Krok 6: Shromáždit všechny meshe a vypsat počty vrcholů

Rozšiřte sběrač tak, aby vypisoval statistiky pro každý 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`);
}

Ukázkový výstup pro scénu se dvěma meshy:

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=“Zkopírovat kód”

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

Tipy a osvědčené postupy

  • Vždy kontrolujte null node.entity před přístupem k vlastnostem specifickým pro entitu. Mnoho uzlů jsou čisté skupinové uzly, které neobsahují žádnou entitu.
  • Použijte instanceof namísto constructor.name pro kontrolu typů v logických cestách. instanceof je bezpečný při refaktoringu; porovnání řetězců na constructor.name selže při minifikaci.
  • Procházet pomocí for...of přes childNodes: iterable bezpečně zvládá všechny velikosti pole. Vyhněte se číselnému indexování pro budoucí kompatibilitu.
  • Vyhněte se mutaci stromu během procházení: nepřidávejte ani neodstraňujte uzly uvnitř rekurzivního volání. Nejprve shromážděte výsledky a pak je upravte.
  • Pass a results array as a parameter → Předávejte pole výsledků jako parametr: tím se zabrání alokaci nového pole při každém rekurzivním volání a usnadní se sloučení výsledků podstromu.

Časté problémy

Symptom → PříznakCause → PříčinaFix → Oprava
childNodes has zero length on → má nulovou délku na rootNodeModel not loaded → Model není načtenEnsure → Zajistěte scene.open() completed without error before traversing → dokončeno bez chyby před procházením
node.entity instanceof Mesh never true → nikdy pravdaWrong → Špatně Mesh import path → cesta importuImport → Import Mesh from → z @aspose/3d/entities, not from → , ne z @aspose/3d kořen
Procházení opomíjí vnořené sítěNerekurzuje se do všech potomkůZajistěte, aby rekurzivní volání pokrývalo každý prvek v node.childNodes
mesh.controlPoints.length je 0Síť načtena, ale neobsahuje žádnou geometriiZkontrolujte zdroj OBJ na prázdné skupiny; použijte mesh.polygonCount jako sekundární kontrolu
Přetečení zásobníku u hlubokých hierarchiíVelmi hluboký strom scény (stovky úrovní)Nahraďte rekurzi explicitním zásobníkem pomocí Array.push / Array.pop

Často kladené otázky

Provádí scene.rootNode sám nese entitu? Ne. Kořenový uzel je kontejner vytvořený automaticky knihovnou. Nemá žádnou entitu. Vaše geometrie a další objekty scény jsou umístěny na podřízených uzlech o jednu nebo více úrovní níže rootNode.

Jaký je rozdíl mezi node.entity a node.entities? node.entity obsahuje jedinou primární entitu (běžný případ). Některé starší soubory FBX a COLLADA mohou vytvářet uzly s více připojenými entitami; v takovém případě node.entities (množné číslo) poskytuje úplný seznam.

Mohu procházet v pořadí šířky místo hloubky? Ano. Použijte frontu místo rekurzivního volání: push scene.rootNode do pole, poté posunout a zpracovat uzly při vkládání každého uzlu childNodes do konce fronty.

Je scene.open() synchronní? Ano. scene.open() a scene.openFromBuffer() obě blokují volající vlákno, dokud není soubor plně parsován. Zabalte je do pracovního vlákna, pokud potřebujete udržet smyčku událostí responzivní.

Jak získám pozice ve světovém prostoru z uzlu? Číst node.globalTransform; vrací jen pro čtení GlobalTransform s maticí ve světovém prostoru, složenou ze všech transformací předků. Pro explicitní maticové výpočty zavolejte node.evaluateGlobalTransform(false).

Jaké typy entit jsou možné kromě Mesh? Camera, Light, a vlastních entit kostry/kosti. Zkontrolujte node.entity.constructor.name nebo použijte instanceof s konkrétní třídou importovanou z @aspose/3d.

Viz také

 Čeština