Jak przeglądać graf sceny 3D w TypeScript

Jak przeglądać graf sceny 3D w TypeScript

Graf scen w Aspose.3D FOSS dla TypeScript jest drzewem obiektów Node, którego korzeniem jest scene.rootNode. Przeglądanie jest rekurencyjne: każdy węzeł udostępnia iterowalny childNodes oraz opcjonalną właściwość entity. Ten przewodnik pokazuje, jak przejść całe drzewo, zidentyfikować typy encji i zebrać statystyki siatek.

Wymagania wstępne

  • Node.js 18 lub nowszy
  • TypeScript 5.0 lub nowszy
  • @aspose/3d zainstalowany

Przewodnik krok po kroku

Krok 1: Instalacja i import

Zainstaluj pakiet:

npm install @aspose/3d

Zaimportuj klasy użyte w tym przewodniku:

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

Scene i Mesh są klasami podstawowymi. ObjLoadOptions jest używany w przykładzie ładowania; zamień na odpowiednią klasę opcji dla innych formatów.


Krok 2: Załaduj scenę z pliku

Utwórz Scene i wywołaj scene.open() z ścieżką do pliku. Wykrywanie formatu jest automatyczne na podstawie binarnych liczb magicznych, więc nie musisz podawać formatu dla plików GLB, STL lub 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}`);

Możesz również wczytać z Buffer w pamięci przy użyciu scene.openFromBuffer(buffer, options); przydatne w bezserwerowych pipeline’ach, w których nie jest dostępny disk I/O.


Krok 3: Napisz rekurencyjną funkcję przeglądania

Rekurencja nad childNodes jest standardowym wzorcem. Funkcja odwiedza każdy węzeł w kolejności przeglądania wgłąb:

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

Dla sceny z jedną siatką o nazwie Cube, wynik będzie wyglądał następująco:

[-] 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 jest null dla węzłów grupowych, kości i lokalizatorów. Sprawdzenie constructor.name działa dla dowolnego typu jednostki: Mesh, Camera, Light, itp.


Krok 4: Uzyskaj dostęp do typu jednostki na każdym węźle

Aby podjąć działanie w zależności od typu encji, użyj sprawdzenia instanceof po null guard:

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 jest najbezpieczniejszym sposobem potwierdzenia, że jednostka jest siatką wielokątową, przed dostępem do controlPoints, polygonCount lub elementów wierzchołków.


Krok 5: Filtruj węzły według typu encji

Aby zebrać tylko węzły zawierające siatkę bez drukowania całego drzewa, użyj rekurencyjnego akumulatora:

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

Funkcja przyjmuje opcjonalną tablicę results, aby wywołujący mógł ją wstępnie wypełnić w celu scalania wyników z wielu poddrzew.


Krok 6: Zbierz wszystkie siatki i wypisz liczbę wierzchołków

Rozszerz kolektor, aby wyświetlał statystyki dla każdej siatki:

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

Przykładowy wynik dla sceny z dwoma siatkami:

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>

Wskazówki i najlepsze praktyki

  • Zawsze sprawdzaj null node.entity przed dostępem do właściwości specyficznych dla encji. Wiele węzłów to czyste węzły grupowe, które nie zawierają encji.
  • Używaj instanceof zamiast constructor.name przy sprawdzaniu typów w ścieżkach logiki. instanceof jest bezpieczny przy refaktoryzacji; porównywanie łańcuchów na constructor.name psuje się po minifikacji.
  • Przechodź przez for...of zamiast childNodes: iterowalny obiekt obsługuje wszystkie rozmiary tablic bezpiecznie. Unikaj indeksowania numerycznego dla przyszłej kompatybilności.
  • Unikaj mutacji drzewa podczas przechodzenia: nie dodawaj ani nie usuwaj węzłów wewnątrz wywołania rekurencyjnego. Najpierw zbierz wyniki, a potem modyfikuj.
  • Przekazuj tablicę wyników jako parametr: to unika alokacji nowej tablicy przy każdym wywołaniu rekurencyjnym i ułatwia scalanie wyników poddrzewa.

Typowe problemy

ObjawPrzyczynaRozwiązanie
childNodes ma zerową długość na rootNodeModel nie został załadowanyUpewnij się, że scene.open() zakończyło się bez błędów przed przeglądaniem
node.entity instanceof Mesh nigdy nie jest prawdziweNieprawidłowa ścieżka importu MeshImportuj Mesh z @aspose/3d/entities, a nie z rootu @aspose/3d
Traversowanie pomija zagnieżdżone siatkiBrak rekurencji we wszystkich dzieciachUpewnij się, że wywołanie rekurencyjne obejmuje każdy element w node.childNodes
mesh.controlPoints.length wynosi 0Siatka załadowana, ale nie zawiera geometriiSprawdź źródło OBJ pod kątem pustych grup; użyj mesh.polygonCount jako dodatkowego sprawdzenia
Przepełnienie stosu przy głębokich hierarchiachBardzo głębokie drzewo sceny (setki poziomów)Zastąp rekurencję jawnym stosem przy użyciu Array.push / Array.pop

Najczęściej zadawane pytania

Czy scene.rootNode sam posiada encję?
Nie. Węzeł główny jest kontenerem tworzonym automatycznie przez bibliotekę. Nie posiada encji. Twoja geometria i inne obiekty sceny znajdują się w węzłach potomnych jeden lub kilka poziomów poniżej rootNode.

Jaka jest różnica między node.entity a node.entities?
node.entity przechowuje pojedynczy podmiot główny (typowy przypadek). Niektóre starsze pliki FBX i COLLADA mogą generować węzły z wieloma podłączonymi podmiotami; w takim przypadku node.entities (liczba mnoga) zapewnia pełną listę.

Czy mogę przeglądać w kolejności wszerz zamiast wgłąb?
Tak. Użyj kolejki zamiast wywołania rekurencyjnego: wstaw scene.rootNode do tablicy, a następnie przesuń i przetwarzaj węzły, jednocześnie wstawiając childNodes każdego węzła do ogona kolejki.

Czy scene.open() jest synchroniczne?
Tak. scene.open() i scene.openFromBuffer() blokują wątek wywołujący, dopóki plik nie zostanie w pełni przetworzony. Umieść je w wątku roboczym, jeśli musisz utrzymać responsywność pętli zdarzeń.

Jak uzyskać pozycje w przestrzeni świata z węzła? Odczytaj node.globalTransform; zwraca ona macierz w przestrzeni świata w trybie tylko do odczytu GlobalTransform, złożoną ze wszystkich transformacji przodków. Aby wykonać explicite obliczenia macierzy, wywołaj node.evaluateGlobalTransform(false).

Jakie typy encji są możliwe oprócz Mesh?
Camera, Light oraz własne encje szkieletu/kości. Sprawdź node.entity.constructor.name lub użyj instanceof z konkretną klasą zaimportowaną z @aspose/3d.

Zobacz także

 Polski