Wie man einen 3D-Szenengraph in TypeScript durchläuft
Die Szene Grafik in Aspose.3D FOSS für TypeScript ist ein Baum von Node Objekte, die auf der scene.rootNode.Die Überfahrt ist rekursiv: Jeder Knoten zeigt eine childNodes Erweiterbar und optional entity Diese Anleitung zeigt, wie man den gesamten Baum durchläuft, Entitätstypen identifiziert und Netzstatistiken erhebt.
Voraussetzungen für die Durchführung der Maßnahmen
- Node.js 18 or later
- TypScript 5.0 oder neuer Version
@aspose/3dinstalliert
Schritt für Schritte
Schritt 1: Installieren und Importieren
Installieren Sie das Paket:
asposefoss/3d is not yet published — build from source until it ships. See the project README for build instructions.
Importieren der 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 Lastbeispiel verwendet; die Klasse “Matching Options” für andere Formate ersetzen.
Schritt 2: Laden Sie eine Szene aus einer Datei ein
Erstellen einer Scene und rufen Sie an. scene.open() Die Formaterkennung erfolgt automatisch aus binären Zauberzahlen, so dass 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 von einem Buffer in der Speicherverarbeitung mit Hilfe von scene.openFromBuffer(buffer, options); nützlich in serverlosen Pipelines, bei denen keine Festplatten-E/A verfügbar ist.
Schritt 3: Eine rekursive Durchlauffunktion schreiben
Rückfall über childNodes Die Funktion besucht jeden Knoten zuerst in der Tiefe:
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 Netz namens Cube, wird die Ausgabe wie folgt aussehen:
[-] 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 Die Daten werden für die Gruppe Knoten, Knochen und Lokatoren. constructor.name Prüfarbeiten für jeden Entitätstyp: Mesh, Camera, Light, usw.
Schritt 4: Zugriff auf den Entity-Typ in jedem Knoten
Um Maßnahmen auf der Grundlage des Entitätstyps zu ergreifen, verwenden Sie eine instanceof Nach dem Nullschutz überprüfen:
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 der sicherste Weg, um zu bestätigen, dass die Entität ein Polygonmesch vor dem Zugriff ist. controlPoints, polygonCount, oder Winkel-Elemente.
Schritt 5: Filterknoten nach Entitätstyp
Um nur Netzträgerknoten zu sammeln, ohne den gesamten Baum auszudrucken, wird ein rekursiver Akkumulator verwendet:
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 eine optionale results Array, damit Anrufer es vor-befüllen können, um Ergebnisse über mehrere Unterbäume hinweg zu verschmelzen.
Schritt 6: Sammeln Sie alle Meshes und Drucken Sie die Vertex-Zählungen
Erweitern Sie den Sammler, um die Statistiken pro Masche zu drucken:
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`);
}Beispiel für eine zweimeschnittene Szene:
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 beste Praktiken
- Immer null-check
node.entityViele Knoten sind reine Gruppenknoten, die keine Entität tragen. - Verwendung
instanceofÜberholtconstructor.namefür Typprüfungen in Logikpfaden.instanceofist refaktorsicher; Stringvergleich aufconstructor.namemit der Minifizierung bricht. - Durchschnittsweg
for...ofÜberholtchildNodes:Die Iteration erfolgt mit allen Arraygrößen. Vermeiden Sie die numerische Indexierung für eine nach vorne kompatible Lösung. - Vermeiden Sie die Mutation des Baumes während der Durchdringung.: keine Knoten in der rekursiven Aufforderung hinzufügen oder entfernen. Sammeln Sie zuerst die Ergebnisse, dann ändern Sie sie.
- Übertragen eines Ergebnis-Arrays als Parameter: so wird vermieden, bei jedem rekursiven Aufruf ein neues Array zuzuweisen und es ist einfach, die Ergebnisse des Unterbaums zusammenzuführen.
Häufige Probleme
| Symptom der | Ursache | Behebung |
|---|---|---|
childNodes hat eine Länge von 0 auf rootNode | Modell nicht geladen | Sicherstellen scene.open() vor der Überquerung fehlerfrei abgeschlossen. |
node.entity instanceof Mesh - Das stimmt nie . | Falsch . Mesh Importweg | Einfuhr Mesh von @aspose/3d/entities, nicht ab @aspose/3d Wurzel |
| Durchschnittsmaschen mit verschachtelten Maschen | Nicht bei allen Kindern wiederkehrend. | Sicherstellen, dass der rekursive Aufruf alle Elemente in den node.childNodes |
mesh.controlPoints.length ist 0 | Maschen mit Geometrie, jedoch ohne Bearbeitung | Überprüfen Sie die OBJ-Quelle auf leere Gruppen; verwenden mesh.polygonCount als Sekundärprüfung |
| Stacküberschuss bei tiefen Hierarchien | Sehr tiefe Szenenbaum (Hunderte von Ebenen) | Ersetzen Sie die Rekursion durch einen expliziten Stack, indem Sie Array.push / Array.pop |
Häufig gestellte Fragen
Ist es so ? scene.rootNode - Sie tragen eine Entität? Nein. Der Root-Knoten ist ein Container, der automatisch von der Bibliothek erstellt wird. Er hat keine Entität. Ihre Geometrie und andere Szenobjekte leben auf Kindernoden eine oder mehrere Ebenen unterhalb. rootNode.
Was ist der Unterschied zwischen node.entity und . node.entities? node.entity enthält die einzige primäre Entität (Common Case). Einige ältere FBX- und COLLADA-Dateien können Knoten mit mehreren angeschlossenen Entitäten erzeugen; in diesem Fall node.entities (Mehrzahl) enthält die vollständige Liste.
Kann ich in der Breite-ersten Ordnung statt tiefste erste durchqueren? Ja, benutze eine Warteschlange anstelle eines rekursiven Anrufs: Drücken. scene.rootNode in eine Array, dann verschieben und Prozessknoten während jeder Knoten drücken childNodes in die Schlange.
Ist . scene.open() Synchron? - Ja, das ist gut. scene.open() und . scene.openFromBuffer() Beide blockieren den Anrufer, bis die Datei vollständig analysiert ist. Wickeln Sie sie in einen Worker-Thread ein, wenn Sie die Ereignisschleife reaktionsfähig halten müssen.
Wie bekomme ich Weltraumpositionen von einem Knoten? Lesen Sie . node.globalTransform; es gibt einen Lesebericht zurück. GlobalTransform Für explizite Matrix-Mathematik, nennen Sie die Weltraummatrix. node.evaluateGlobalTransform(false).
Welche Entitätstypen sind möglich, außer: Mesh? Camera, Light, und benutzerdefinierte Skelett-/Knochenentitäten. node.entity.constructor.name oder Verwendung instanceof mit der spezifischen Klasse, die aus: @aspose/3d.