Робота з графом сцени
Увесь 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 зазвичай не має сутності |
| Трансформація не відображається у переглядачі GLB | globalTransform не оновлено | globalTransform обчислюється під час збереження; встановити transform.translation перед викликом scene.save() |
GLB створює окремий .bin sidecar | binaryMode за замовчуванням false | Встановити saveOpts.binaryMode = true |
Див. також
- Функції та можливості: повна довідка API для всіх областей функцій.
- Підтримка форматів: підтримувані 3D формати, можливість читання/запису та параметри форматів.
- Як програмно створити 3D Mesh.