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/3dInstalacja
Przewodnik krok po kroku
Krok 1: Instalacja i import
Instalacja pakietu:
npm install @aspose/3dImportowanie 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.entityPrzed 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
instanceofnadconstructor.nameW przypadku kontrol typu w ścieżkach logicznych.instanceofjest bezpieczny; porównanie strun naconstructor.nameZamieszka się z miniatyrą. - Przejście przez
for...ofnadchildNodes: 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
| objawy | Przyczyny | Fixy |
|---|---|---|
childNodes ma zero długości rootNode | Model nie jest ładowany | Bezpieczeństwo scene.open() bez błędu przed przechodzeniem |
node.entity instanceof Mesh Nigdy prawdziwe | niewłaściwy Mesh Droga importu | Importowanie Mesh od @aspose/3d/entities,Nie z @aspose/3d Root |
| Mieszkańcy przegapili zniszczone łodygi | Nie wchodząc do wszystkich dzieci | Upewnij się, że powtarzający się połączenie obejmuje każdy element w node.childNodes |
mesh.controlPoints.length Jest 0 | Mesh ładowany, ale nie zawiera geometrii | Sprawdź źródło OBJ dla pustej grupy; używaj mesh.polygonCount jako wtórny check |
| Złoty przepływ na głębokich hierarchiach | Bardzo 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.