如何在 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常见问题
| Issue | Cause | Fix |
|---|---|---|
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_POINT 和 MappingMode.POLYGON_VERTEX 有什么区别?CONTROL_POINT 为每个 唯一 顶点存储一个值。POLYGON_VERTEX 为每个多边形‑顶点对存储一个值,这允许在同一顶点属于多个多边形时拥有不同的法线(硬边)。
在保存为 STL 之前,我需要对网格进行三角化吗?
不。库在导出需要三角形的格式时会自动处理三角化。您可以在网格中定义四边形和 n 边形,并直接保存为 STL。
如何添加 UV 坐标?
使用 mesh.createElementUV(TextureMapping.Diffuse, MappingMode.CONTROL_POINT, ReferenceMode.DIRECT) 创建一个 VertexElementUV,然后调用 setData([...]) 并传入 FVector2 或 FVector3 值的数组——每个控制点一个。data 的 getter 返回一个副本;不要直接向其 push。
我可以在一个场景中构建多个网格吗?
是的。创建多个节点在 scene.rootNode 下,并为每个节点的 entity 属性分配一个单独的 Mesh。