การทำงานกับ Scene Graph

การทำงานกับ 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.
EntityMesh, 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 โดยทั่วไปไม่มีเอนทิตี
การแปลงไม่แสดงในตัวดู GLBglobalTransform ไม่ได้อัปเดตglobalTransform คำนวณเมื่อบันทึก; ตั้งค่า transform.translation ก่อนเรียกใช้ scene.save()
GLB สร้างไฟล์แยก .bin sidecarbinaryMode ค่าเริ่มต้นเป็น falseตั้งค่า saveOpts.binaryMode = true

ดูเพิ่มเติม

 ภาษาไทย