Wie man einen 3D‑Szenengraph in TypeScript durchläuft

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/3d installiert

Schritt-für-Schritt-Anleitung

Schritt 1: Installieren und Importieren

Paket installieren:

npm install @aspose/3d

Importieren 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.entity bevor Sie entitätsspezifische Eigenschaften zugreifen. Viele Knoten sind reine Gruppenknoten, die keine Entität tragen.
  • Verwenden Sie instanceof über constructor.name für Typprüfungen in Logikpfaden. instanceof ist refaktor-sicher; Zeichenkettenvergleich auf constructor.name bricht bei Minifizierung.
  • Durchlaufen über for...of über childNodes: 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

SymptomUrsacheBeheben
childNodes hat die Länge 0 bei rootNodeModell nicht geladenSicherstellen scene.open() abgeschlossen ohne Fehler, bevor traversiert wird
node.entity instanceof Mesh niemals wahrFalsch Mesh ImportpfadImportieren Mesh von @aspose/3d/entities, nicht von @aspose/3d Wurzel
Traversal verpasst verschachtelte MeshesNicht rekursiv in alle KinderSicherstellen, dass der rekursive Aufruf jedes Element in node.childNodes
mesh.controlPoints.length ist 0Mesh geladen, enthält aber keine GeometriePrüfen Sie die OBJ-Quelle auf leere Gruppen; verwenden Sie mesh.polygonCount als sekundäre Prüfung
Stapelüberlauf bei tiefen HierarchienSehr 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.

Siehe auch

 Deutsch