Робота з графом сцени

Увесь 3D‑контент у Aspose.3D FOSS для TypeScript розташований всередині Scene об’єкта, організованого у вигляді дерева Node об’єктів. Розуміння цієї ієрархії є основою для створення, завантаження та обробки будь‑якого 3D‑файлу.

Встановлення

Встановіть пакет з npm перед запуском будь‑якого коду в цьому посібнику:

npm install @aspose/3d

Переконайтеся, що ваш tsconfig.json включає "module": "commonjs" і "moduleResolution": "node" для правильного розв’язання імпорту підшляхів.

Концепції графа сцени

Граф сцени має три рівні:

РівеньКласРоль
СценаSceneКонтейнер верхнього рівня. Містить rootNode, animationClips, і assetInfo.
ВузолNodeНазваний вузол дерева. Може мати дочірні вузли, сутність, трансформацію та матеріали.
Сутність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 є основним класом геометрії. Додайте позиції вершин, додаючи 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 надає матрицю трансформації у світовому просторі (тільки для читання), обчислену шляхом конкатенації всіх трансформацій предків.

Крок 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 створює його без аргументу name.

Завжди захищайте доступ до сутності перевіркою на null перед приведенням до конкретного типу. Не кожен вузол містить сутність.

Крок 6: Збереження у glTF або GLB

Використовуйте GltfSaveOptions для керування форматом виводу. Встановіть binaryMode = true для створення одного самодостатнього .glb файлу; залиште його false для JSON .gltf + .bin пара сайдкару:

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() як аргумент формату, щоб бібліотека використовувала правильний encoder незалежно від розширення файлу.

Поради та кращі практики

  • Використовуйте createChildNode() замість створення Node безпосередньо: createChildNode() автоматично встановлює зв’язок батько‑дитина та реєструє node у дереві.
  • Перевірте node.entity перед доступом до властивостей сутності: багато вузлів (групові вузли, кістки, локатори) не містять сутності. Завжди захищайте їх перевіркою на null або instanceof тестом.
  • Встановити translation на дочірніх вузлах, а не на вершинах сітки: модифікація transform.translation не руйнівна і комбінується з трансформаціями батька.
  • Віддавайте перевагу binaryMode = true для GLB: один .glb файл легше розповсюджувати, завантажувати в браузерах і імпортувати в ігрові движки, ніж розділений .gltf + .bin формат.
  • Переміщатися через for...of через childNodes: уникайте числового індексування; використовуйте ітерований об’єкт безпосередньо для майбутньої сумісності.

Поширені проблеми

СимптомЙмовірна причинаВиправити
child.entity = mesh не впливає на експортСутність призначена до неправильного рівня вузлаПризначити entity до листового вузла, а не до групового вузла
node.entity завжди nullЛише перевірка rootNode самРекурсивно в node.childNodes; rootNode зазвичай не має сутності
Трансформація не відображається у переглядачі GLBglobalTransform не оновленоglobalTransform обчислюється під час збереження; встановити transform.translation перед викликом scene.save()
GLB створює окремий .bin sidecarbinaryMode за замовчуванням falseВстановити saveOpts.binaryMode = true

Див. також

 Українська