Comment parcourir un graphe de scène 3D en TypeScript

Comment parcourir un graphe de scène 3D en TypeScript

Le graphe de scène dans Aspose.3D FOSS pour TypeScript est un arbre d’objets Node enraciné à scene.rootNode. Le parcours est récursif : chaque nœud expose un itérable childNodes et une propriété optionnelle entity. Ce guide montre comment parcourir l’arbre complet, identifier les types d’entités et collecter les statistiques des maillages.

Prérequis

  • Node.js 18 ou version ultérieure
  • TypeScript 5.0 ou version ultérieure
  • @aspose/3d installé

Guide étape par étape

Étape 1 : Installer et importer

Installez le package :

npm install @aspose/3d

Importez les classes utilisées dans ce guide :

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

Scene et Mesh sont les classes principales. ObjLoadOptions est utilisé dans l’exemple de chargement ; remplacez la classe d’options correspondante pour les autres formats.


Étape 2 : Charger une scène à partir d’un fichier

Créez un Scene et appelez scene.open() avec un chemin de fichier. La détection du format est automatique à partir des nombres magiques binaires, vous n’avez donc pas besoin de spécifier le format pour les fichiers GLB, STL ou 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}`);

Vous pouvez également charger depuis un Buffer en mémoire en utilisant scene.openFromBuffer(buffer, options) ; utile dans les pipelines serverless où l’E/S disque n’est pas disponible.


Étape 3: Écrire une fonction de traversée récursive

La récursivité sur childNodes est le modèle standard. La fonction visite chaque nœud en profondeur d’abord :

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);

Pour une scène contenant un seul maillage nommé Cube, la sortie sera la suivante :

[-] 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 est null pour les nœuds de groupe, les os et les localisateurs. La vérification constructor.name fonctionne pour tout type d’entité : Mesh, Camera, Light, etc.


Étape 4 : Accéder au type d’entité sur chaque nœud

Pour agir en fonction du type d’entité, utilisez une vérification instanceof après la garde contre null :

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 est la façon la plus sûre de confirmer que l’entité est un maillage polygonal avant d’accéder à controlPoints, polygonCount ou aux éléments de sommet.


Étape 5 : Filtrer les nœuds par type d’entité

Pour collecter uniquement les nœuds contenant des maillages sans imprimer l’arbre complet, utilisez un accumulateur récursif :

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)`);

La fonction accepte un tableau results optionnel afin que les appelants puissent le préremplir pour fusionner les résultats à travers plusieurs sous‑arbres.


Étape 6 : Collecter tous les maillages et afficher le nombre de sommets

Étendre le collecteur pour afficher les statistiques par maillage :

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`);
}

Exemple de sortie pour une scène à deux maillages :

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>

Conseils et meilleures pratiques

  • Toujours vérifier la nullité de node.entity avant d’accéder aux propriétés spécifiques à l’entité. De nombreux nœuds sont de purs nœuds de groupe qui ne contiennent aucune entité.
  • Utilisez instanceof plutôt que constructor.name pour les vérifications de type dans les chemins logiques. instanceof est sûr lors de refactorisation ; la comparaison de chaînes sur constructor.name se casse avec la minification.
  • Parcourez via for...of plutôt que childNodes : l’itérable gère toutes les tailles de tableau en toute sécurité. Évitez l’indexation numérique pour une compatibilité future.
  • Évitez de muter l’arbre pendant le parcours : n’ajoutez ni ne supprimez de nœuds à l’intérieur de l’appel récursif. Collectez d’abord les résultats, puis modifiez.
  • Passez un tableau de résultats en paramètre : cela évite d’allouer un nouveau tableau à chaque appel récursif et facilite la fusion des résultats des sous‑arbres.

Problèmes courants

SymptomCauseFix
childNodes a une longueur nulle sur rootNodeModèle non chargéAssurez‑vous que scene.open() s’est terminé sans erreur avant le parcours
node.entity instanceof Mesh n’est jamais vraiMauvais chemin d’importation MeshImportez Mesh depuis @aspose/3d/entities, pas depuis la racine @aspose/3d
Le parcours ne trouve pas les maillages imbriquésPas de récursion sur tous les enfantsAssurez‑vous que l’appel récursif couvre chaque élément de node.childNodes
mesh.controlPoints.length vaut 0Maillage chargé mais ne contient aucune géométrieVérifiez la source OBJ pour les groupes vides ; utilisez mesh.polygonCount comme vérification secondaire
Débordement de pile sur des hiérarchies profondesArbre de scène très profond (des centaines de niveaux)Remplacez la récursion par une pile explicite en utilisant Array.push / Array.pop

Foire aux questions

scene.rootNode porte-t-il lui‑même une entité ?
Non. Le nœud racine est un conteneur créé automatiquement par la bibliothèque. Il n’a aucune entité. Votre géométrie et les autres objets de scène résident sur des nœuds enfants un ou plusieurs niveaux en dessous de rootNode.

Quelle est la différence entre node.entity et node.entities ?
node.entity contient l’entité principale unique (le cas le plus courant). Certains fichiers FBX et COLLADA plus anciens peuvent produire des nœuds avec plusieurs entités attachées ; dans ce cas, node.entities (pluriel) fournit la liste complète.

Puis-je parcourir en ordre largeur plutôt qu’en profondeur ?
Oui. Utilisez une file d’attente au lieu d’un appel récursif : poussez scene.rootNode dans un tableau, puis décalez et traitez les nœuds tout en poussant le childNodes de chaque nœud dans le queue tail.

scene.open() est‑il synchrone ?
Oui. scene.open() et scene.openFromBuffer() bloquent tous deux le thread appelant jusqu’à ce que le fichier soit entièrement analysé. Enveloppez‑les dans un thread de travail si vous devez garder la boucle d’événements réactive.

Comment obtenir les positions en espace mondial à partir d’un nœud ?
Lisez node.globalTransform ; il renvoie un GlobalTransform en lecture seule avec la matrice en espace mondial, composée de toutes les transformations des ancêtres. Pour des calculs de matrice explicites, appelez node.evaluateGlobalTransform(false).

Quels types d’entité sont possibles en plus de Mesh ?
Camera, Light et des entités personnalisées de squelette/os. Consultez node.entity.constructor.name ou utilisez instanceof avec la classe spécifique importée de @aspose/3d.

Voir aussi

 Français