Πώς να διασχίσετε ένα 3D γράφημα σκηνής σε TypeScript
Το γράφημα σκηνής στο Aspose.3D FOSS for TypeScript είναι ένα δέντρο αντικειμένων Node με ρίζα στο scene.rootNode. Η διάσχιση είναι αναδρομική: κάθε κόμβος εκθέτει έναν επαναλήψιμο childNodes και μια προαιρετική ιδιότητα entity. Αυτός ο οδηγός δείχνει πώς να περιηγηθείτε σε ολόκληρο το δέντρο, να εντοπίσετε τύπους οντοτήτων και να συλλέξετε στατιστικά πλέγματος.
Προαπαιτούμενα
- Node.js 18 ή νεότερο
- TypeScript 5.0 ή νεότερο
@aspose/3dεγκατεστημένο
Οδηγός βήμα προς βήμα
Βήμα 1: Εγκατάσταση και Εισαγωγή
Εγκαταστήστε το πακέτο:
npm install @aspose/3dΕισαγάγετε τις κλάσεις που χρησιμοποιούνται σε αυτόν τον οδηγό:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene και Mesh είναι οι βασικές κλάσεις. ObjLoadOptions χρησιμοποιείται στο παράδειγμα φόρτωσης· αντικαταστήστε την αντίστοιχη κλάση επιλογών για άλλες μορφές.
Βήμα 2: Φόρτωση μιας σκηνής από αρχείο
Δημιουργήστε ένα Scene και καλέστε το scene.open() με διαδρομή αρχείου. Η ανίχνευση μορφής είναι αυτόματη από τους δυαδικούς αριθμούς μαγικού, έτσι δεν χρειάζεται να καθορίσετε τη μορφή για αρχεία GLB, STL ή 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}`);Μπορείτε επίσης να φορτώσετε από ένα Buffer στη μνήμη χρησιμοποιώντας scene.openFromBuffer(buffer, options); χρήσιμο σε serverless pipelines όπου δεν είναι διαθέσιμη η πρόσβαση σε δίσκο.
Βήμα 3: Γράψτε μια Αναδρομική Συνάρτηση Διάσχισης
Η επανάληψη πάνω στο childNodes είναι το τυπικό μοτίβο. Η συνάρτηση επισκέπτεται κάθε κόμβο βάθος‑πρώτα:
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);Για μια σκηνή με ένα πλέγμα με όνομα Cube, η έξοδος θα φαίνεται ως εξής:
[-] 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 είναι null για κόμβους ομάδας, οστά και εντοπιστές. Ο έλεγχος constructor.name λειτουργεί για οποιονδήποτε τύπο οντότητας: Mesh, Camera, Light, κ.λπ.
Βήμα 4: Πρόσβαση στον τύπο οντότητας σε κάθε κόμβο
Για να εκτελέσετε ενέργεια βάσει του τύπου οντότητας, χρησιμοποιήστε έναν έλεγχο instanceof μετά το null guard:
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 είναι ο πιο ασφαλής τρόπος να επιβεβαιώσετε ότι η οντότητα είναι ένα πολυγωνικό πλέγμα πριν αποκτήσετε πρόσβαση σε controlPoints, polygonCount ή στοιχεία κορυφής.
Βήμα 5: Φιλτράρισμα Κόμβων ανά Τύπο Οντότητας
Για τη συλλογή μόνο των κόμβων που περιέχουν πλέγμα χωρίς την εκτύπωση του πλήρους δέντρου, χρησιμοποιήστε έναν αναδρομικό συσσωρευτή:
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)`);Η συνάρτηση δέχεται έναν προαιρετικό results πίνακα ώστε οι καλούντες να μπορούν να τον προσυμπληρώσουν για τη συγχώνευση αποτελεσμάτων σε πολλαπλά υποδέντρα.
Βήμα 6: Συλλογή όλων των πλέγματων και εκτύπωση του αριθμού των κορυφών
Επεκτείνετε τον συλλέκτη ώστε να εκτυπώνει στατιστικά ανά πλέγμα:
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`);
}Παράδειγμα εξόδου για μια σκηνή με δύο πλέγματα:
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>
Συμβουλές και βέλτιστες πρακτικές
- Πάντα ελέγχετε για null
node.entityπριν αποκτήσετε πρόσβαση σε ιδιότητες συγκεκριμένες για οντότητα. Πολλοί κόμβοι είναι καθαροί κόμβοι ομάδας που δεν φέρουν οντότητα. - Χρησιμοποιήστε
instanceofαντί γιαconstructor.nameγια ελέγχους τύπου σε λογικές διαδρομές.instanceofείναι ασφαλές για ανασχεδιασμό· η σύγκριση συμβολοσειρών στοconstructor.nameσπάει με τη μινιφικέση. - Διασχίστε μέσω
for...ofαντί γιαchildNodes: η επαναληπτική δομή διαχειρίζεται όλα τα μεγέθη πίνακα με ασφάλεια. Αποφύγετε την αριθμητική δεικτοδότηση για μελλοντική συμβατότητα. - Αποφύγετε την τροποποίηση του δέντρου κατά τη διάρκεια της διαδρομής: μην προσθέτετε ή αφαιρείτε κόμβους μέσα στην αναδρομική κλήση. Συλλέξτε πρώτα τα αποτελέσματα, έπειτα τροποποιήστε.
- Περάστε έναν πίνακα αποτελεσμάτων ως παράμετρο: αυτό αποτρέπει την εκχώρηση νέου πίνακα σε κάθε αναδρομική κλήση και καθιστά εύκολη τη συγχώνευση των αποτελεσμάτων των υποδέντρων.
Κοινά προβλήματα
| Σύμπτωμα | Αιτία | Διόρθωση |
|---|---|---|
childNodes έχει μηδενικό μήκος στο rootNode | Το μοντέλο δεν έχει φορτωθεί | Βεβαιωθείτε ότι το scene.open() ολοκληρώθηκε χωρίς σφάλμα πριν από την διέλευση |
node.entity instanceof Mesh ποτέ δεν είναι αληθές | Λάθος διαδρομή εισαγωγής Mesh | Εισάγετε το Mesh από το @aspose/3d/entities, όχι από τη ρίζα @aspose/3d |
| Η διέλευση παραλείπει ενσωματωμένα πλέγματα | Δεν γίνεται επανάληψη σε όλα τα παιδιά | Βεβαιωθείτε ότι η αναδρομική κλήση καλύπτει κάθε στοιχείο στο node.childNodes |
mesh.controlPoints.length είναι 0 | Το πλέγμα φορτώθηκε αλλά δεν περιέχει γεωμετρία | Ελέγξτε την πηγή OBJ για κενές ομάδες· χρησιμοποιήστε το mesh.polygonCount ως δευτερεύουσα επαλήθευση |
| Υπέρβαση στοίβας σε βαθιές ιεραρχίες | Πολύ βαθύ δέντρο σκηνής (εκατοντάδες επίπεδα) | Αντικαταστήστε την επανάληψη με μια ρητή στοίβα χρησιμοποιώντας Array.push / Array.pop |
Συχνές Ερωτήσεις
Φέρει το scene.rootNode από μόνο του μια οντότητα;
Όχι. Ο ριζικός κόμβος είναι ένας κοντέινερ που δημιουργείται αυτόματα από τη βιβλιοθήκη. Δεν έχει οντότητα. Η γεωμετρία σας και άλλα αντικείμενα σκηνής ζουν σε θυγατρικούς κόμβους ένα ή περισσότερα επίπεδα κάτω από το rootNode.
Ποια είναι η διαφορά μεταξύ node.entity και node.entities;node.entity περιέχει τη μοναδική κύρια οντότητα (η κοινή περίπτωση). Ορισμένα παλαιότερα αρχεία FBX και COLLADA μπορεί να δημιουργούν κόμβους με πολλαπλές συνημμένες οντότητες· σε αυτήν την περίπτωση node.entities (πληθυντικός) παρέχει τη πλήρη λίστα.
Μπορώ να διασχίσω με σειρά κατά πλάτος αντί για βάθος;
Ναι. Χρησιμοποιήστε μια queue αντί για μια recursive call: push scene.rootNode σε ένα array, μετά shift και process nodes ενώ push το childNodes του κάθε node στην queue tail.
Είναι το scene.open() συγχρονισμένο;
Ναι. Τα scene.open() και scene.openFromBuffer() αποκλείουν το νήμα κλήσης μέχρι να αναλυθεί πλήρως το αρχείο. Τυλίξτε τα σε νήμα εργασίας εάν χρειάζεται να διατηρήσετε το βρόχο γεγονότων ανταποκρινόμενο.
Πώς να λάβω θέσεις σε παγκόσμιο χώρο από έναν κόμβο;
Διαβάστε node.globalTransform; επιστρέφει ένα μόνο για ανάγνωση GlobalTransform με τη μητρώα σε παγκόσμιο χώρο, συντιθέμενη από όλους τους μετασχηματισμούς προγόνων. Για ρητή μαθηματική επεξεργασία μητρώας, καλέστε node.evaluateGlobalTransform(false).
Τι τύποι οντοτήτων είναι δυνατοί εκτός από Mesh;Camera, Light, και προσαρμοσμένες οντότητες σκελετού/οστών. Ελέγξτε node.entity.constructor.name ή χρησιμοποιήστε instanceof με την συγκεκριμένη κλάση που εισάγεται από @aspose/3d.