Wie man einen 3D‑Szenengraph in TypeScript durchläuft
Der Szenengraph in Aspose.3D FOSS für TypeScript ist ein Baum von Node Objekten, die bei scene.rootNode. Traversierung ist rekursiv: Jeder Knoten stellt ein childNodes iterierbares Objekt und ein optionales entity Property. Dieser Leitfaden zeigt, wie man den gesamten Baum durchläuft, Entitätstypen identifiziert und Mesh-Statistiken sammelt.
Voraussetzungen
- Node.js 18 oder neuer
- TypeScript 5.0 oder neuer
@aspose/3dinstalliert
Schritt-für-Schritt-Anleitung
Schritt 1: Installieren und Importieren
Paket installieren:
npm install @aspose/3dImportieren Sie die in diesem Leitfaden verwendeten Klassen:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene und Mesh sind die Kernklassen. ObjLoadOptions wird im Ladebeispiel verwendet; ersetze die passende Optionsklasse für andere Formate.
Schritt 2: Laden einer Szene aus einer Datei
Erstelle ein Scene und rufe scene.open() mit einem Dateipfad. Die Format-Erkennung erfolgt automatisch anhand von binären Magic Numbers, sodass Sie das Format für GLB-, STL- oder 3MF-Dateien nicht angeben müssen:
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}`);Sie können auch aus einem Buffer im Speicher mit scene.openFromBuffer(buffer, options); nützlich in serverlosen Pipelines, in denen kein Festplatten‑E/A verfügbar ist.
Schritt 3: Schreiben einer rekursiven Traversierungsfunktion
Rekursion über childNodes ist das Standardmuster. Die Funktion besucht jeden Knoten tiefen zuerst:
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);Für eine Szene mit einem Mesh namens Cube, die Ausgabe sieht folgendermaßen aus:
[-] 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=“Code kopieren”
<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 ist null für Gruppenknoten, Knochen und Locator. Der constructor.name Prüfung funktioniert für jeden Entitätstyp: Mesh, Camera, Light, usw.
Schritt 4: Zugriff auf den Entitätstyp jedes Knotens
Um basierend auf dem Entitätstyp zu handeln, verwenden Sie ein instanceof Prüfung nach der Nullprüfung:
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 ist die sicherste Methode, um zu bestätigen, dass die Entität ein Polygonnetz ist, bevor man darauf zugreift controlPoints, polygonCount, oder Vertex-Elemente.
Schritt 5: Knoten nach Entitätstyp filtern
Um nur Knoten mit Meshes zu sammeln, ohne den gesamten Baum auszugeben, verwende einen rekursiven Akkumulator:
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)`);Die Funktion akzeptiert ein optionales results Array, damit Aufrufer es vorab füllen können, um Ergebnisse über mehrere Teilbäume hinweg zu zusammenzuführen.
Schritt 6: Alle Meshes sammeln und Vertex‑Anzahlen ausgeben
Erweitere den Sammler, um pro Mesh Statistiken auszugeben:
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`);
}Beispielausgabe für eine Szene mit zwei Meshes:
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=“Code kopieren”
<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>
Tipps und bewährte Vorgehensweisen
- Immer Null prüfen
node.entitybevor Sie entitätsspezifische Eigenschaften zugreifen. Viele Knoten sind reine Gruppenknoten, die keine Entität tragen. - Verwenden Sie
instanceofüberconstructor.namefür Typprüfungen in Logikpfaden.instanceofist refaktor-sicher; Zeichenkettenvergleich aufconstructor.namebricht bei Minifizierung. - Durchlaufen über
for...ofüberchildNodes: das Iterable verarbeitet alle Array-Größen sicher. Vermeiden Sie numerische Indizierung für zukünftige Kompatibilität. - Vermeiden Sie das Mutieren des Baums während der Traversierung: Fügen Sie innerhalb des rekursiven Aufrufs keine Knoten hinzu oder entfernen Sie sie. Sammeln Sie zuerst die Ergebnisse, dann ändern Sie sie.
- Übergeben Sie ein Ergebnis-Array als Parameter: Dies verhindert die Allokation eines neuen Arrays bei jedem rekursiven Aufruf und erleichtert das Zusammenführen von Teilbaum-Ergebnissen.
Häufige Probleme
| Symptom | Ursache | Beheben |
|---|---|---|
childNodes hat die Länge 0 bei rootNode | Modell nicht geladen | Sicherstellen scene.open() abgeschlossen ohne Fehler, bevor traversiert wird |
node.entity instanceof Mesh niemals wahr | Falsch Mesh Importpfad | Importieren Mesh von @aspose/3d/entities, nicht von @aspose/3d Wurzel |
| Traversal verpasst verschachtelte Meshes | Nicht rekursiv in alle Kinder | Sicherstellen, dass der rekursive Aufruf jedes Element in node.childNodes |
mesh.controlPoints.length ist 0 | Mesh geladen, enthält aber keine Geometrie | Prüfen Sie die OBJ-Quelle auf leere Gruppen; verwenden Sie mesh.polygonCount als sekundäre Prüfung |
| Stapelüberlauf bei tiefen Hierarchien | Sehr tiefer Szenenbaum (Hunderte von Ebenen) | Ersetzen Sie Rekursion durch einen expliziten Stack, indem Sie verwenden Array.push / Array.pop |
Häufig gestellte Fragen
Hat scene.rootNode trägt es selbst eine Entität? Nein. Der Wurzelknoten ist ein Container, der von der Bibliothek automatisch erstellt wird. Er hat keine Entität. Ihre Geometrie und andere Szenenobjekte befinden sich auf Kindknoten eine oder mehrere Ebenen darunter. rootNode.
Was ist der Unterschied zwischen node.entity und node.entities? node.entity enthält die einzelne primäre Entität (der übliche Fall). Einige ältere FBX- und COLLADA-Dateien können Knoten mit mehreren angehängten Entitäten erzeugen; in diesem Fall node.entities (Plural) liefert die vollständige Liste.
Kann ich in Breitensuche statt in Tiefensuche traversieren? Ja. Verwenden Sie eine Warteschlange anstelle eines rekursiven Aufrufs: push scene.rootNode in ein Array, dann shift und verarbeite Knoten, während du jedes Knoten’s childNodes in das Ende der Warteschlange.
Ist scene.open() synchron? Ja. scene.open() und scene.openFromBuffer() blockieren beide den aufrufenden Thread, bis die Datei vollständig geparst ist. Packen Sie sie in einen Worker‑Thread, wenn Sie die Ereignisschleife reaktionsfähig halten müssen.
Wie erhalte ich Welt‑Raum‑Positionen von einem Knoten? Lesen node.globalTransform; es gibt ein schreibgeschütztes GlobalTransform mit der Welt‑Raum‑Matrix, zusammengesetzt aus allen übergeordneten Transformationen. Für explizite Matrixberechnungen rufen Sie node.evaluateGlobalTransform(false).
Welche Entitätstypen sind neben Mesh? Camera, Light, und benutzerdefinierte Skelett-/Knochen-Entitäten. Prüfen node.entity.constructor.name oder verwenden instanceof mit der spezifischen Klasse importiert aus @aspose/3d.