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