วิธีการเดินทางผ่านกราฟฉาก 3D ใน TypeScript
กราฟฉากใน Aspose.3D FOSS สำหรับ TypeScript เป็นต้นไม้ของ Node อ็อบเจ็กต์ที่มีรากที่ scene.rootNode. การท่องผ่านเป็นแบบเรียกซ้ำ: แต่ละโหนดเปิดเผย childNodes อ็อบเจ็กต์ที่สามารถวนซ้ำได้และตัวเลือก entity property. คู่มือนี้แสดงวิธีการเดินผ่านต้นไม้ทั้งหมด, ระบุประเภทเอนทิตี้, และรวบรวมสถิติเมช.
Prerequisites
- 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 ใช้ในตัวอย่างการโหลด; แทนที่ด้วยคลาส options ที่ตรงกันสำหรับฟอร์แมตอื่น.
ขั้นตอนที่ 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); มีประโยชน์ใน pipeline แบบ serverless ที่ไม่มีการเข้าถึง I/O ของดิสก์.
ขั้นตอนที่ 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 สำหรับโหนดกลุ่ม, กระดูก, และตำแหน่ง. The constructor.name การตรวจสอบทำงานสำหรับประเภทเอนทิตีใดก็ได้: Mesh, Camera, Light, เป็นต้น.
ขั้นตอนที่ 4: เข้าถึงประเภทเอนทิตี้บนแต่ละโหนด
เพื่อดำเนินการตามประเภทเอนทิตี, ใช้ an instanceof ตรวจสอบหลังจากการตรวจสอบค่า 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 เป็นวิธีที่ปลอดภัยที่สุดในการยืนยันว่าเอนทิตีเป็นโพลิกอนเมชก่อนเข้าถึง 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 อาร์เรย์เพื่อให้ผู้เรียกสามารถเติมค่าไว้ล่วงหน้าเพื่อรวมผลลัพธ์ข้ามหลาย subtree.
ขั้นตอนที่ 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จะพังเมื่อทำการย่อขนาด (minification). - ท่องผ่าน
for...ofบนchildNodes: ตัววนซ้ำจัดการขนาดอาร์เรย์ทั้งหมดอย่างปลอดภัย. หลีกเลี่ยงการใช้ดัชนีเชิงตัวเลขเพื่อความเข้ากันได้ในอนาคต. - หลีกเลี่ยงการเปลี่ยนแปลงต้นไม้ระหว่างการท่องผ่าน: อย่าเพิ่มหรือลบโหนดภายในการเรียกแบบเรียกซ้ำ. รวบรวมผลลัพธ์ก่อน, แล้วจึงทำการแก้ไข.
- ส่งอาร์เรย์ผลลัพธ์เป็นพารามิเตอร์: สิ่งนี้ช่วยหลีกเลี่ยงการจัดสรรอาร์เรย์ใหม่ในแต่ละการเรียกแบบเรียกซ้ำและทำให้การรวมผลลัพธ์ของโครงย่อยเป็นเรื่องง่าย.
ปัญหาทั่วไป
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
childNodes มีความยาวเป็นศูนย์บน rootNode | โมเดลไม่ได้โหลด | ตรวจสอบให้แน่ใจ scene.open() เสร็จสมบูรณ์โดยไม่มีข้อผิดพลาดก่อนการเดินผ่าน |
node.entity instanceof Mesh ไม่เคยเป็นจริง | ผิด Mesh เส้นทางการนำเข้า | นำเข้า Mesh จาก @aspose/3d/entities, ไม่ใช่จาก @aspose/3d รูท |
| การเดินผ่านพลาดเมชที่ซ้อนกัน | ไม่ได้ทำการเรียกซ้ำไปยังลูกทั้งหมด | ตรวจสอบให้แน่ใจว่าการเรียกแบบเรียกซ้ำครอบคลุมทุกองค์ประกอบใน node.childNodes |
mesh.controlPoints.length เป็น 0 | Mesh ถูกโหลดแล้วแต่ไม่มีเรขาคณิต | ตรวจสอบแหล่ง OBJ สำหรับกลุ่มที่ว่างเปล่า; ใช้ mesh.polygonCount เป็นการตรวจสอบรอง |
| Stack overflow บนลำดับชั้นที่ลึก | ต้นไม้ซีนลึกมาก (หลายร้อยระดับ) | แทนที่การเรียกซ้ำด้วยสแตกแบบชัดเจนโดยใช้ Array.push / Array.pop |
คำถามที่พบบ่อย
ทำ scene.rootNode ตัวมันเองมีเอนทิตี้หรือไม่? ไม่. โหนดรากเป็นคอนเทนเนอร์ที่สร้างโดยอัตโนมัติโดยไลบรารี. มันไม่มีเอนทิตี้. เรขาคณิตและวัตถุซีนอื่น ๆ ของคุณอยู่บนโหนดลูกหนึ่งระดับหรือหลายระดับด้านล่าง rootNode.
ความแตกต่างระหว่าง node.entity และ node.entities? node.entity ถือเอนทิตีหลักเดียว (กรณีทั่วไป) ไฟล์ FBX และ COLLADA รุ่นเก่าบางไฟล์อาจสร้างโหนดที่มีเอนทิตีแนบหลายตัว; ในกรณีนั้น node.entities (พหูพจน์) ให้รายการเต็ม.
ฉันสามารถเดินทางแบบกว้างก่อน (breadth‑first) แทนแบบลึกก่อน (depth‑first) ได้ไหม? ได้เลย ใช้คิวแทนการเรียกแบบเรียกซ้ำ: ผลัก scene.rootNode เข้าไปในอาร์เรย์ จากนั้นชิฟท์และประมวลผลโหนดขณะผลักแต่ละโหนดของ childNodes เข้าไปที่ท้ายคิว.
เป็น scene.open() synchronous หรือไม่? ใช่. scene.open() และ scene.openFromBuffer() ทั้งสองจะบล็อกเธรดที่เรียกจนกว่าไฟล์จะถูกแยกวิเคราะห์อย่างสมบูรณ์ หากคุณต้องการให้ event loop ตอบสนองได้ ให้ห่อหุ้มพวกมันใน worker thread.
ฉันจะดึงตำแหน่งใน world-space จากโหนดได้อย่างไร? อ่าน node.globalTransform; มันจะคืนค่าแบบอ่าน‑อย่างเดียว GlobalTransform พร้อมเมทริกซ์เวิลด์สเปซที่ประกอบจากการแปลงของบรรพบุรุษทั้งหมด สำหรับการคำนวณเมทริกซ์โดยตรง ให้เรียก node.evaluateGlobalTransform(false).
ประเภทเอนทิตีที่เป็นไปได้นอกจาก Mesh? Camera, Light, และเอนทิตีโครงกระดูก/กระดูกที่กำหนดเอง ตรวจสอบ node.entity.constructor.name หรือใช้ instanceof พร้อมคลาสเฉพาะที่นำเข้าจาก @aspose/3d.