การทำงานกับ Scene Graph
เนื้อหา 3D ทั้งหมดใน Aspose.3D FOSS สำหรับ TypeScript อยู่ภายใน a Scene อ็อบเจ็กต์ที่จัดระเบียบเป็นต้นไม้ของ Node อ็อบเจ็กต์ การทำความเข้าใจลำดับชั้นนี้เป็นพื้นฐานสำหรับการสร้าง, โหลด, และประมวลผลไฟล์ 3D ใด ๆ.
การติดตั้ง
ติดตั้งแพ็กเกจจาก npm ก่อนรันโค้ดใด ๆ ในคู่มือนี้:
npm install @aspose/3dตรวจสอบให้แน่ใจว่า tsconfig.json รวม "module": "commonjs" และ "moduleResolution": "node" เพื่อการแก้ไขการนำเข้าตำแหน่งย่อยอย่างถูกต้อง.
แนวคิดของ Scene Graph
Scene Graph มีสามระดับ:
| ระดับ | คลาส | บทบาท |
|---|---|---|
| ซีน | Scene | คอนเทนเนอร์ระดับบนสุด. เก็บ rootNode, animationClips, และ assetInfo. |
| โหนด | Node | โหนดต้นไม้ที่มีชื่อ. สามารถมีโหนดลูก, entity, transform, และ materials. |
| Entity | Mesh, Camera, Light, … | เนื้อหาที่แนบกับโหนด โหนดจะบรรจุได้ไม่เกินหนึ่งเอนทิตี้. |
โซ่การสืบทอดสำหรับบล็อกพื้นฐานหลักคือ:
A3DObject
└─ SceneObject
├─ Node (tree structure)
└─ Entity
└─ Geometry
└─ Mesh (polygon geometry)scene.rootNode ถูกสร้างโดยอัตโนมัติ คุณไม่ได้สร้างมันด้วยตนเอง; คุณสร้างโหนดลูกภายใต้มัน.
Step 1: Create a Scene
import { Scene } from '@aspose/3d';
const scene = new Scene();
console.log(scene.rootNode.name); // '' (empty string — the root node is created with no name)
ใหม่ Scene เริ่มต้นด้วยโหนดรากที่ว่างเปล่าและไม่มีคลิปแอนิเมชัน คุณสร้างเนื้อหาโดยการแนบโหนดลูก.
ขั้นตอนที่ 2: เพิ่มโหนดลูก
ใช้ createChildNode() เพื่อเพิ่มต้นไม้ วิธีนี้จะคืนค่าใหม่ Node, ดังนั้นคุณสามารถต่อเรียกเพิ่มเติมจากระดับใดก็ได้:
import { Scene } from '@aspose/3d';
const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');
console.log(scene.rootNode.childNodes.length); // 1 (parent)
console.log(parent.childNodes.length); // 1 (child)
ชื่อโหนดเป็นสตริงใดก็ได้ ชื่อไม่จำเป็นต้องเป็นเอกลักษณ์ แต่การใช้ชื่อที่มีความหมายทำให้โค้ดการเดินทางง่ายต่อการดีบัก.
ขั้นตอนที่ 3: สร้าง Mesh และกำหนดจุดเวอร์เท็กซ์
Mesh เป็นคลาส geometry หลัก. เพิ่มตำแหน่งเวอร์เท็กซ์โดยการผลัก Vector4 ค่าเข้า mesh.controlPoints, จากนั้นเรียก createPolygon() เพื่อกำหนดหน้าโดยดัชนีเวอร์เท็กซ์:
import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4 } from '@aspose/3d/utilities';
const scene = new Scene();
const child = scene.rootNode.createChildNode('parent').createChildNode('child');
const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3); // quad face using all four vertices
child.entity = mesh;
console.log(mesh.controlPoints.length); // 4
console.log(mesh.polygonCount); // 1
Vector4 ใช้พิกัดเชิงเนื้อเดียว: ส่วน w ส่วนคือ 1 สำหรับตำแหน่งและ 0 สำหรับเวกเตอร์ทิศทาง.
Step 4: Set Node Transforms
แต่ละโหนดมี transform คุณสมบัติที่มี translation, rotation, และ scaling. ตั้งค่า translation เพื่อย้ายโหนดสัมพันธ์กับพาเรนท์ของมัน:
import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';
const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');
const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;
child.transform.translation = new Vector3(2.0, 0.0, 0.0);globalTransform ให้ world-space transformation matrix (read-only) ที่คำนวณโดยการต่อเนื่องการแปลงของบรรพบุรุษทั้งหมด.
ขั้นตอนที่ 5: เดินสำรวจต้นไม้
เขียนฟังก์ชันแบบเรียกซ้ำเพื่อเยี่ยมชมทุกโหนด ตรวจสอบ node.entity และ node.childNodes ที่แต่ละระดับ:
function traverse(node: any, depth = 0): void {
const indent = ' '.repeat(depth);
const entityType = node.entity ? node.entity.constructor.name : 'none';
console.log(`${indent}${node.name} [${entityType}]`);
for (const child of node.childNodes) {
traverse(child, depth + 1);
}
}
traverse(scene.rootNode);สำหรับโครงสร้างลำดับชั้นที่สร้างขึ้นข้างต้น ผลลัพธ์จะเป็น:
[none]
parent [none]
child [Mesh]ชื่อโหนดรากเป็นสตริงว่างเพราะ Scene สร้างมันโดยไม่มีอาร์กิวเมนต์ชื่อ.
ควรตรวจสอบค่า null ก่อนเข้าถึงเอนทิตี้และทำการแคสท์เป็นประเภทเฉพาะเสมอ ไม่ใช่ทุกโหนดจะมีเอนทิตี้.
ขั้นตอนที่ 6: บันทึกเป็น glTF หรือ GLB
ใช้ GltfSaveOptions เพื่อควบคุมรูปแบบการส่งออก ตั้งค่า binaryMode = true เพื่อสร้างไฟล์เดียวที่เป็นอิสระ .glb ไฟล์; ปล่อยให้มัน false สำหรับ JSON .gltf + .bin คู่ sidecar:
import { Scene } from '@aspose/3d';
import { Mesh } from '@aspose/3d/entities';
import { Vector4, Vector3 } from '@aspose/3d/utilities';
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';
const scene = new Scene();
const parent = scene.rootNode.createChildNode('parent');
const child = parent.createChildNode('child');
const mesh = new Mesh('cube');
mesh.controlPoints.push(new Vector4(0, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 0, 0, 1));
mesh.controlPoints.push(new Vector4(1, 1, 0, 1));
mesh.controlPoints.push(new Vector4(0, 1, 0, 1));
mesh.createPolygon(0, 1, 2, 3);
child.entity = mesh;
child.transform.translation = new Vector3(2.0, 0.0, 0.0);
const saveOpts = new GltfSaveOptions();
saveOpts.binaryMode = true; // write a single .glb file
scene.save('scene.glb', GltfFormat.getInstance(), saveOpts);
console.log('Scene saved to scene.glb');ส่งผ่าน GltfFormat.getInstance() เป็นอาร์กิวเมนต์รูปแบบเพื่อให้ไลบรารีใช้ตัวเข้ารหัสที่ถูกต้องโดยไม่คำนึงถึงส่วนขยายของไฟล์.
เคล็ดลับและแนวปฏิบัติที่ดีที่สุด
- ใช้
createChildNode()แทนการสร้างNodeโดยตรง:createChildNode()เชื่อมความสัมพันธ์พาเรนท์-ชิลด์โดยอัตโนมัติและลงทะเบียนโหนดในต้นไม้. - ตรวจสอบ
node.entityก่อนเข้าถึงคุณสมบัติของเอนทิตี้: หลายโหนด (group nodes, bones, locators) ไม่มีเอนทิตี้. ควรตรวจสอบ null เสมอหรือinstanceofการทดสอบ. - ตั้งค่า
translationบนโหนดลูก, ไม่ใช่บน mesh vertices:การปรับเปลี่ยนtransform.translationเป็นแบบไม่ทำลายและสามารถผสานกับการแปลงของพาเรนต์ได้. - แนะนำให้ใช้
binaryMode = trueสำหรับ GLB: หนึ่งเดียว.glbไฟล์ง่ายต่อการแจกจ่าย โหลดในเบราว์เซอร์ และนำเข้าไปยังเอนจินเกม มากกว่าการแยก.gltf+.binรูปแบบ. - สำรวจผ่าน
for...ofบนchildNodes: หลีกเลี่ยงการใช้ดัชนีเชิงตัวเลข; ใช้ตัววนซ้ำโดยตรงเพื่อความเข้ากันได้ในอนาคต.
ปัญหาทั่วไป
| อาการ | สาเหตุที่เป็นไปได้ | แก้ไข |
|---|---|---|
child.entity = mesh ไม่มีผลต่อการส่งออก | เอนทิตี้ถูกกำหนดให้ระดับโหนดผิด | กำหนด entity ไปยัง leaf node, ไม่ใช่ group node |
node.entity เป็นเสมอ null | กำลังตรวจสอบเท่านั้น rootNode ตัวเอง | ทำการเรียกซ้ำเข้าไปใน node.childNodes; rootNode โดยทั่วไปไม่มีเอนทิตี |
| การแปลงไม่แสดงในตัวดู GLB | globalTransform ไม่ได้อัปเดต | globalTransform คำนวณเมื่อบันทึก; ตั้งค่า transform.translation ก่อนเรียกใช้ scene.save() |
GLB สร้างไฟล์แยก .bin sidecar | binaryMode ค่าเริ่มต้นเป็น false | ตั้งค่า saveOpts.binaryMode = true |
ดูเพิ่มเติม
- คุณลักษณะและฟังก์ชันการทำงาน: เอกสารอ้างอิง API เต็มรูปแบบสำหรับทุกพื้นที่ของคุณลักษณะ.
- การสนับสนุนรูปแบบ: รูปแบบ 3D ที่รองรับ, ความสามารถในการอ่าน/เขียน, และตัวเลือกรูปแบบ.
- วิธีสร้าง 3D Mesh อย่างเป็นโปรแกรม.