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

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

Graf scény v Aspose.3D FOSS pro TypeScript je strom Node objektů s kořenem v scene.rootNode. Procházení je rekurzivní: každý uzel poskytuje childNodes iterable a volitelnou 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 průvodci:

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číst scénu 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é zadávat 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: Napište rekurzivní funkci pro průchod

Rekurze nad childNodes je standardní vzor. Funkce prochází každý uzel hloubkově:

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 bude výstup 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. Kontrola constructor.name funguje pro jakýkoli typ entity: Mesh, Camera, Light, atd.


Krok 4: Přístup k typu entity na každém uzlu

Pro provedení akce na základě typu entity použijte kontrolu instanceof po null guardu:

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 vrcholovým prvkům.


Krok 5: Filtrovat uzly podle typu entity

Chcete-li shromáždit pouze uzly nesoucí síť 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é pole results, aby volající mohli předem naplnit jej pro sloučení výsledků napříč více podstromy.


Krok 6: Shromáždit všechny sítě 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`);
}

Příklad výstupu pro scénu se dvěma sítěmi:

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 provádějte kontrolu null node.entity před přístupem k vlastnostem specifickým pro entitu. Mnoho uzlů jsou čisté skupinové uzly, které neobsahují žádnou entitu.
  • Používejte instanceof mí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ázejte pomocí for...of místo childNodes: iterovatelný objekt bezpečně zvládá všechny velikosti polí. 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, pak upravte.
  • 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ů podstromů.

Časté problémy

PříznakPříčinaOprava
childNodes má nulovou délku na rootNodeModel není načtenZajistěte, aby scene.open() byl dokončen bez chyby před procházením
node.entity instanceof Mesh nikdy není pravdaŠpatná cesta importu MeshImportujte Mesh z @aspose/3d/entities, ne z kořene @aspose/3d
Procházení opomíjí vnořené sítěNerekurzuje 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 pro prázdné skupiny; použijte mesh.polygonCount jako sekundární kontrolu
Přetečení zásobníku při hlubokých hierarchiíchVelmi hluboký strom scény (stovky úrovní)Nahraďte rekurzi explicitním zásobníkem pomocí Array.push / Array.pop

Často kladené otázky

Nese scene.rootNode samotný 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 než 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 šířkovém pořadí místo hloubkového?
Ano. Použijte frontu místo rekurzivního volání: vložte scene.rootNode do pole, poté posuňte a zpracovávejte uzly při vkládání childNodes každého uzlu do konce fronty.

Je scene.open() synchronní? Ano. scene.open() a scene.openFromBuffer() blokují volající vlákno, dokud není soubor plně zpracová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? Přečtěte 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í entity kostry/kosti. Zkontrolujte node.entity.constructor.name nebo použijte instanceof se specifickou třídou importovanou z @aspose/3d.

Viz také

 Čeština