如何在 TypeScript 中以编程方式构建 3D 网格

如何在 TypeScript 中以编程方式构建 3D 网格

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:创建场景和节点

A Scene 是顶层容器。所有几何体必须附加到场景树中的 Node

import { Scene } from '@aspose/3d';

const scene = new Scene();
const node = scene.rootNode.createChildNode('triangle');

createChildNode(name) 创建一个具名节点,并将其作为当前节点的子节点进行连接。返回的 Node 对象就是在第7步中附加网格的地方。


步骤 3:创建网格对象

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 的 getter 返回防御性副本——对其进行 push 没有任何效果。对法线数据使用 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网格未被任何节点引用在分配 node.entity 之前进行推送;顺序无关紧要,但请验证引用
导出产生空几何体node.entity 未分配在调用 scene.save() 之前确保 node.entity = mesh
法线计数不匹配传递给 setData() 的数组短于 controlPoints使用 MappingMode.CONTROL_POINT 时,每个控制点添加一个 FVector3 条目
glTF 查看器显示黑色网格法线指向内部createPolygon 中反转绕序(例如 0, 2, 1)或取法线向量的相反数
TypeScript:未找到 ’normals.data’ 属性导入路径错误@aspose/3d/entities 导入 VertexElementNormal,而不是从 @aspose/3d 根目录导入

常见问题

我可以创建四边形而不是三角形吗? 是的。向 createPolygon(0, 1, 2, 3) 传递四个索引。库在导出到需要三角形的格式(glTF、STL)时会对四边形进行三角化。

MappingMode.CONTROL_POINTMappingMode.POLYGON_VERTEX 有什么区别?
CONTROL_POINT 为每个 唯一 顶点存储一个值。POLYGON_VERTEX 为每个多边形‑顶点对存储一个值,这允许在同一顶点属于多个多边形时拥有不同的法线(硬边)。

在保存为 STL 之前,我需要对网格进行三角化吗?
不。库在导出需要三角形的格式时会自动处理三角化。您可以在网格中定义四边形和 n 边形,并直接保存为 STL。

如何添加 UV 坐标?
使用 mesh.createElementUV(TextureMapping.Diffuse, MappingMode.CONTROL_POINT, ReferenceMode.DIRECT) 创建一个 VertexElementUV,然后调用 setData([...]) 并传入 FVector2FVector3 值的数组——每个控制点一个。data 的 getter 返回一个副本;不要直接向其 push。

我可以在一个场景中构建多个网格吗? 是的。创建多个节点在 scene.rootNode 下,并为每个节点的 entity 属性分配一个单独的 Mesh

另请参阅

 中文