Как программно построить 3D‑меш в TypeScript
Aspose.3D FOSS for TypeScript позволяет создавать 3D‑геометрию полностью в коде без загрузки файлов. Вы определяете позиции вершин как контрольные точки, задаёте грани полигонов по индексам и при необходимости присоединяете элементы вершин, такие как нормали, UV‑координаты или цвета вершин. Полученный результат можно сохранить в любой записываемый формат: glTF, GLB, STL, FBX или COLLADA.
Требования
- Node.js 18 или новее
- TypeScript 5.0 или новее
@aspose/3dустановлен (см. Шаг 1)
Пошаговое руководство
Шаг 1: Установите @aspose/3d
npm install @aspose/3dНе требуются нативные аддоны или системные библиотеки. Пакет включает определения типов TypeScript.
Минимум tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true
}
}Шаг 2: Создать сцену и узел
Scene — это контейнер верхнего уровня. Вся геометрия должна быть прикреплена к Node внутри дерева сцены:
import { Scene } from '@aspose/3d';
const scene = new Scene();
const node = scene.rootNode.createChildNode('triangle');createChildNode(name) создает именованный узел и привязывает его как дочерний к текущему узлу. Возвращаемый объект Node — это место, где вы прикрепите сетку на Шаге 7.
Шаг 3: Создать объект Mesh
Mesh хранит позиции вершин и определения полигонов. Создайте один с необязательным именем:
import { Mesh } from '@aspose/3d/entities';
const mesh = new Mesh('triangle');Сетка начинается пустой: нет вершин и граней. Вы добавляете их в следующих шагах.
Шаг 4: Добавить контрольные точки (вершины)
Контрольные точки — это позиции вершин в локальном пространстве. Перенесите значения Vector4 в mesh.controlPoints. Четвёртый компонент (w) является 1 для позиций:
import { Vector4 } from '@aspose/3d/utilities';
mesh.controlPoints.push(new Vector4(0.0, 0.0, 0.0, 1.0)); // index 0
mesh.controlPoints.push(new Vector4(1.0, 0.0, 0.0, 1.0)); // index 1
mesh.controlPoints.push(new Vector4(0.5, 1.0, 0.0, 1.0)); // index 2
Вы ссылаетесь на эти позиции по их нулевому индексу при определении граней полигона.
Шаг 5: Создать полигональные грани
createPolygon() определяет грань, перечисляя индексы вершин в порядке. Три индекса образуют треугольник:
mesh.createPolygon(0, 1, 2);Вы также можете определять квадраты (четыре индекса) или произвольные полигоны для форматов, которые их поддерживают. Для glTF библиотека автоматически триангулирует квадраты и n‑гоны при экспорте.
Шаг 6: Добавить нормали вершин
Нормали улучшают качество рендеринга. Используйте mesh.createElement() для создания VertexElementNormal, собирайте векторы нормалей в массив, затем вызовите setData() для их сохранения. Геттер data возвращает защитную копию — добавление в неё не оказывает эффекта. Используйте FVector3 (число с одинарной точностью) для данных нормалей, а не Vector4.
import { VertexElementNormal } from '@aspose/3d/entities';
import { VertexElementType, MappingMode, ReferenceMode } from '@aspose/3d/entities';
import { FVector3 } from '@aspose/3d/utilities';
const normals = mesh.createElement(
VertexElementType.NORMAL,
MappingMode.CONTROL_POINT,
ReferenceMode.DIRECT
) as VertexElementNormal;
// Build the normal array, then call setData() — do NOT push to normals.data
normals.setData([
new FVector3(0, 0, 1), // normal for vertex 0 (pointing +Z)
new FVector3(0, 0, 1), // normal for vertex 1
new FVector3(0, 0, 1), // normal for vertex 2
]);MappingMode.CONTROL_POINT означает одну нормаль на вершину. ReferenceMode.DIRECT означает, что массив данных индексируется напрямую индексом вершины полигона.
Шаг 7: Присоединить сетку и сохранить в glTF
Назначьте сетку узлу через node.entity, затем сохраните сцену:
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';
node.entity = mesh;
const saveOpts = new GltfSaveOptions();
scene.save('triangle.gltf', GltfFormat.getInstance(), saveOpts);
console.log('Triangle mesh saved to triangle.gltf');Чтобы вместо этого создать один самодостаточный файл .glb, установите saveOpts.binaryMode = true и измените расширение выходного файла на .glb.
Полный пример
Ниже приведён полный скрипт, объединяющий все шаги выше:
import { Scene } from '@aspose/3d';
import { Mesh, VertexElementNormal } from '@aspose/3d/entities';
import { VertexElementType, MappingMode, ReferenceMode } from '@aspose/3d/entities';
import { Vector4, FVector3 } from '@aspose/3d/utilities';
import { GltfSaveOptions, GltfFormat } from '@aspose/3d/formats/gltf';
const scene = new Scene();
const node = scene.rootNode.createChildNode('triangle');
const mesh = new Mesh('triangle');
mesh.controlPoints.push(new Vector4(0.0, 0.0, 0.0, 1.0));
mesh.controlPoints.push(new Vector4(1.0, 0.0, 0.0, 1.0));
mesh.controlPoints.push(new Vector4(0.5, 1.0, 0.0, 1.0));
mesh.createPolygon(0, 1, 2);
const normals = mesh.createElement(
VertexElementType.NORMAL,
MappingMode.CONTROL_POINT,
ReferenceMode.DIRECT
) as VertexElementNormal;
// setData() is the correct API — normals.data returns a defensive copy; pushing to it has no effect
normals.setData([
new FVector3(0, 0, 1),
new FVector3(0, 0, 1),
new FVector3(0, 0, 1),
]);
node.entity = mesh;
const saveOpts = new GltfSaveOptions();
scene.save('triangle.gltf', GltfFormat.getInstance(), saveOpts);
console.log('Triangle mesh saved to triangle.gltf');Запустить с ts-node:
npx ts-node triangle.tsОбщие проблемы
| Issue | Cause | Fix |
|---|---|---|
mesh.controlPoints.length равно 0 после push | Сетка не привязана ни к одному узлу | Выполните push до назначения node.entity; порядок не важен, но проверьте ссылку |
| Экспорт создает пустую геометрию | node.entity не назначен | Убедитесь, что node.entity = mesh выполнен перед вызовом scene.save() |
| Несоответствие количества нормалей | Массив, переданный в setData(), короче, чем controlPoints | Добавьте одну запись FVector3 на каждую контрольную точку при использовании MappingMode.CONTROL_POINT |
| Просмотрщик glTF отображает черную сетку | Нормали направлены внутрь | Измените порядок обхода в createPolygon (например, 0, 2, 1) или инвертируйте векторы нормалей |
| TypeScript: свойство ’normals.data’ не найдено | Неправильный путь импорта | Импортируйте VertexElementNormal из @aspose/3d/entities, а не из корня @aspose/3d |
Часто задаваемые вопросы
Могу ли я создавать четырехугольники вместо треугольников?
Да. Передайте четыре индекса в createPolygon(0, 1, 2, 3). Библиотека триангулирует четырехугольники при экспорте в форматы, требующие треугольники (glTF, STL).
В чем разница между MappingMode.CONTROL_POINT и MappingMode.POLYGON_VERTEX?
CONTROL_POINT хранит одно значение на уникальную вершину. POLYGON_VERTEX хранит одно значение на пару полигон‑вершина, что позволяет иметь разные нормали в одной и той же вершине, когда она принадлежит нескольким полигонам (жёсткие грани).
Нужно ли триангулировать сетку перед сохранением в STL?
Нет. Библиотека автоматически выполняет триангуляцию при экспорте в форматы, требующие треугольников. Вы можете определять четырёхугольники и n‑угольники в сетке и сохранять напрямую в STL.
Как добавить UV‑координаты?
Use mesh.createElementUV(TextureMapping.Diffuse, MappingMode.CONTROL_POINT, ReferenceMode.DIRECT) to create a VertexElementUV, then call setData([...]) with an array of FVector2 or FVector3 values — one per control point. The data getter returns a copy; do not push to it directly.
Могу ли я построить несколько мешей в одной сцене?
Да. Создайте несколько узлов под scene.rootNode и назначьте отдельный Mesh каждому свойству entity узла.