Cara Melintasi Graf Adegan 3D dalam TypeScript
Graf adegan dalam Aspose.3D FOSS untuk TypeScript adalah pohon objek Node yang berakar pada scene.rootNode. Penelusuran bersifat rekursif: setiap node memperlihatkan iterable childNodes dan properti opsional entity. Panduan ini menunjukkan cara melintasi seluruh pohon, mengidentifikasi jenis entitas, dan mengumpulkan statistik mesh.
Prasyarat
- Node.js 18 atau yang lebih baru
- TypeScript 5.0 atau yang lebih baru
@aspose/3dterpasang
Panduan Langkah demi Langkah
Langkah 1: Pasang dan Impor
Pasang paket:
npm install @aspose/3dImpor kelas yang digunakan dalam panduan ini:
import { Scene } from '@aspose/3d';
import { ObjLoadOptions } from '@aspose/3d/formats/obj';
import { Mesh } from '@aspose/3d/entities';Scene dan Mesh adalah kelas inti. ObjLoadOptions digunakan dalam contoh pemuatan; gantikan kelas opsi yang sesuai untuk format lain.
Langkah 2: Muat Adegan dari File
Buat Scene dan panggil scene.open() dengan jalur file. Deteksi format dilakukan secara otomatis dari angka ajaib biner, sehingga Anda tidak perlu menentukan format untuk file GLB, STL, atau 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}`);Anda juga dapat memuat dari Buffer di memori menggunakan scene.openFromBuffer(buffer, options); berguna dalam pipeline tanpa server di mana disk I/O tidak tersedia.
Langkah 3: Tulis Fungsi Penelusuran Rekursif
Rekursi atas childNodes adalah pola standar. Fungsi ini mengunjungi setiap node secara depth-first:
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);Untuk adegan dengan satu mesh bernama Cube, output akan terlihat seperti:
[-] 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 adalah null untuk node grup, tulang, dan locator. Pemeriksaan constructor.name berfungsi untuk jenis entitas apa pun: Mesh, Camera, Light, dan lainnya.
Langkah 4: Akses Jenis Entitas pada Setiap Node
Untuk mengambil tindakan berdasarkan jenis entitas, gunakan pemeriksaan instanceof setelah penjaga 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 adalah cara paling aman untuk memastikan entitas adalah mesh poligon sebelum mengakses controlPoints, polygonCount, atau elemen vertex.
Langkah 5: Filter Node berdasarkan Jenis Entitas
Untuk mengumpulkan hanya node yang memiliki mesh tanpa mencetak seluruh pohon, gunakan akumulator rekursif:
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)`);Fungsi ini menerima array results opsional sehingga pemanggil dapat mengisinya terlebih dahulu untuk menggabungkan hasil dari beberapa subpohon.
Langkah 6: Kumpulkan Semua Mesh dan Cetak Jumlah Vertex
Perluas pengumpul untuk mencetak statistik per mesh:
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`);
}Contoh output untuk adegan dengan dua mesh:
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>
Tips dan Praktik Terbaik
- Selalu periksa null
node.entitysebelum mengakses properti khusus entitas. Banyak node adalah node grup murni yang tidak membawa entitas. - Gunakan
instanceofdaripadaconstructor.nameuntuk pemeriksaan tipe dalam jalur logika.instanceofaman untuk refaktor; perbandingan string padaconstructor.nameakan rusak dengan minifikasi. - Lintasi melalui
for...ofpadachildNodes: iterable menangani semua ukuran array dengan aman. Hindari pengindeksan numerik untuk kompatibilitas ke depan. - Hindari mengubah pohon selama penelusuran: jangan menambahkan atau menghapus node di dalam panggilan rekursif. Kumpulkan hasil terlebih dahulu, kemudian modifikasi.
- Berikan array hasil sebagai parameter: ini menghindari alokasi array baru di setiap panggilan rekursif dan memudahkan penggabungan hasil subpohon.
Masalah Umum
| Gejala | Penyebab | Solusi |
|---|---|---|
childNodes memiliki panjang nol pada rootNode | Model tidak dimuat | Pastikan scene.open() selesai tanpa kesalahan sebelum melintasi |
node.entity instanceof Mesh tidak pernah benar | Jalur impor Mesh yang salah | Impor Mesh dari @aspose/3d/entities, bukan dari akar @aspose/3d |
| Penelusuran melewatkan mesh bersarang | Tidak merekursi ke semua anak | Pastikan panggilan rekursif mencakup setiap elemen dalam node.childNodes |
mesh.controlPoints.length adalah 0 | Mesh dimuat tetapi tidak mengandung geometri | Periksa sumber OBJ untuk grup kosong; gunakan mesh.polygonCount sebagai pemeriksaan sekunder |
| Stack overflow pada hierarki yang dalam | Pohon adegan yang sangat dalam (ratusan level) | Ganti rekursi dengan stack eksplisit menggunakan Array.push / Array.pop |
Pertanyaan Umum
Apakah scene.rootNode sendiri membawa entitas?
Tidak. Node root adalah wadah yang dibuat secara otomatis oleh pustaka. Node ini tidak memiliki entitas. Geometri Anda dan objek adegan lainnya berada pada node anak satu atau lebih level di bawah rootNode.
Apa perbedaan antara node.entity dan node.entities?
node.entity menyimpan entitas utama tunggal (kasus umum). Beberapa file FBX dan COLLADA lama mungkin menghasilkan node dengan beberapa entitas yang dilampirkan; dalam kasus tersebut node.entities (jamak) menyediakan daftar lengkap.
Bisakah saya melintasi dalam urutan luas-pertama daripada kedalaman-pertama?
Ya. Gunakan antrian daripada panggilan rekursif: push scene.rootNode ke dalam array, kemudian shift dan proses node sambil push setiap childNodes node ke ekor antrian.
Apakah scene.open() sinkron?
Ya. scene.open() dan scene.openFromBuffer() keduanya memblokir thread pemanggil hingga file sepenuhnya diparsing. Bungkus keduanya dalam worker thread jika Anda perlu menjaga event loop tetap responsif.
Bagaimana cara mendapatkan posisi ruang dunia dari sebuah node?
Baca node.globalTransform; ini mengembalikan GlobalTransform hanya-baca dengan matriks ruang dunia, yang terdiri dari semua transformasi ancestor. Untuk matematika matriks eksplisit, panggil node.evaluateGlobalTransform(false).
Jenis entitas apa yang mungkin selain Mesh?
Camera, Light, dan entitas skeleton/tulang kustom. Periksa node.entity.constructor.name atau gunakan instanceof dengan kelas spesifik yang diimpor dari @aspose/3d.