Jak przejść przez 3D Scene Graph w TypeScript

Jak przejść przez 3D Scene Graph w TypeScript

Grafika sceny w Aspose.3D FOSS dla TypeScript jest drzewem Node Obiekty z korzeniami w scene.rootNode.Przejazd jest recursywny: każdy węzły narażają na childNodes Niezależnie od tego, czy jest to opcjonalne entity Ten przewodnik pokazuje, jak chodzić przez cały drzewo, identyfikować typy podmiotów i zbierać statystyki z łupieżu.

Warunki

  • Node.js 18 lub później
  • TypScript 5.0 lub później
  • @aspose/3d Instalacja

Przewodnik krok po kroku

Krok 1: Instalacja i import

Instalacja pakietu:

npm install @aspose/3d

Importowanie klas używanych w tym przewodniku:

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

Scene i Mesh To są podstawowe klasy. ObjLoadOptions jest używany w przykładzie ładowania; zastępuje klasę opcji dopasowania dla innych formatów.


Krok 2: Pobierz scenę z pliku

Tworzenie A Scene i wezwanie scene.open() Detekcja formatu jest automatyczna z numerów magicznych binarnych, więc nie musisz określać formaty 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ż pobrać z A Buffer w pamięci wykorzystywanej scene.openFromBuffer(buffer, options);• przydatne w rurach bez serwera, gdzie dysk I/O nie jest dostępny.


Krok 3: Napisz funkcję recursywnej

Recursja nad childNodes Funkcja odwiedza każdy węzły głęboko-pierw:

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 jednym nazwą Cube,Wydajność będzie wyglądać jak:

[-] 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 grupy węzłów, kości i lokatorów. constructor.name sprawdzenie prac dla każdego rodzaju podmiotu: Mesh, Camera, Light,i tak dalej.


Krok 4: Dostęp do typu podmiotu na każdym przycisku

Aby podjąć działania w oparciu o typ jednostki, użyj instanceof Sprawdź po zerowej straży:

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 to najbezpieczniejszy sposób potwierdzenia, że jednostka jest poligonu przed dostępem do controlPoints, polygonCount,lub elementy vertex.


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

Aby zbierać tylko węzły noszące mysz bez drukowania pełnego drzewa, użyj recursywnej akumulatoru:

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 akceptuje opcjonalny results rozmieszczone tak, że wezwcy mogą go zapobiegać połączenia wyników w różnych poddrzewach.


Krok 6: Zbieraj wszystkie koszyki i drukuj konty vertekowe

Rozszerz kolekcjonera do drukowania statystyk per-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`);
}

Przykładowe wyniki dla sceny dwumiesięcznej:

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>

Porady i najlepsze praktyki

  • Zawsze zero-check node.entity Przed dostępem do właściwości specyficznych dla podmiotu. Wiele węzłów to czyste grupy wązły, które nie przynoszą żadnej jednostki.
  • Używanie instanceof nad constructor.name W przypadku kontrol typu w ścieżkach logicznych. instanceof jest bezpieczny; porównanie strun na constructor.name Zamieszka się z miniatyrą.
  • Przejście przez for...of nad childNodes: nieprzyjemne obsługuje wszystkie rozmiary array bezpiecznie. unikać indeksowania numerycznego dla kompatybilności z przodu.
  • Unikaj wymiany drzewa podczas przepływu:Nie dodaj ani nie usuwaj węzłów wewnątrz połączenia recursywnego. najpierw zbierz wyniki, a następnie modyfikuj.
  • Przeprowadź aretę wyników jako parametr:To unika przypisywania nowego rzędu na każdym powtarzającym się wezwaniu i ułatwia połączenie wyników poddrzew.

wspólne kwestie

objawyPrzyczynyFixy
childNodes ma zero długości rootNodeModel nie jest ładowanyBezpieczeństwo scene.open() bez błędu przed przechodzeniem
node.entity instanceof Mesh Nigdy prawdziweniewłaściwy Mesh Droga importuImportowanie Mesh od @aspose/3d/entities,Nie z @aspose/3d Root
Mieszkańcy przegapili zniszczone łodygiNie wchodząc do wszystkich dzieciUpewnij się, że powtarzający się połączenie obejmuje każdy element w node.childNodes
mesh.controlPoints.length Jest 0Mesh ładowany, ale nie zawiera geometriiSprawdź źródło OBJ dla pustej grupy; używaj mesh.polygonCount jako wtórny check
Złoty przepływ na głębokich hierarchiachBardzo głębokie drzewo sceniczne (stotki poziomów)Zastąpić odwrotność z wyjaśnionym stackem za pomocą Array.push / Array.pop

Często zadawane pytania

Czyli scene.rootNode Czy sam posiada podmiot? No. Krok jest kontenerem stworzonym automatycznie przez bibliotekę. nie ma podmiotu. geometry i inne obiekty sceny żyją na dziecięcych węzłów jeden lub więcej poziomów poniżej rootNode.

Jaka jest różnica między node.entity i node.entities? node.entity niektóre starsze pliki FBX i COLLADA mogą wytwarzać węzły z wieloma podłączonymi podmiotami; w tym przypadku node.entities (Plural) zawiera pełną listę.

Czy mogę przejść w szerokości pierwszej kolejności zamiast głębi pierwszej? Użyj cegły zamiast powtarzającego się połączenia: push scene.rootNode w kolejce, a następnie przełącz i przetwarzaj przyciski podczas naciśnięcia każdego przycisków childNodes Wchodzimy do tail.

Jest scene.open() Synchronizacja ? Tak i tak. scene.open() i scene.openFromBuffer() Obie blokują pasek wezwania, aż plik zostanie w pełni rozdrobniony. Włóż je do paska pracownika, jeśli potrzebujesz, aby przewód zdarzeń był reakcyjny.

Jak mogę uzyskać pozycje światowego przestrzeni z nodu? Czytaj node.globalTransform;Wróci do czytania tylko GlobalTransform z świat-space matrycy, składającej się ze wszystkich przodków transformacji. dla wyjaśnionej matrix matematyki, wezwanie node.evaluateGlobalTransform(false).

Jakie rodzaje podmiotów są możliwe oprócz Mesh? Camera, Light, i dostosowane podmioty skeletonowe / kości. sprawdź node.entity.constructor.name lub używać instanceof z klasą przywożoną od @aspose/3d.

Zobacz również

 Polski