Как программно построить 3D‑меш в TypeScript

Как программно построить 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

Общие проблемы

IssueCauseFix
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 узла.

См. также

 Русский