How to Build a 3D Mesh Programmatically in TypeScript

How to Build a 3D Mesh Programmatically in TypeScript

Aspose.3D FOSS for TypeScript lets you build 3D geometry entirely in code without loading any file. You define vertex positions as control points, specify polygon faces by index, and attach optional vertex elements such as normals, UVs, or vertex colors. The result can be saved to any writable format: glTF, GLB, STL, FBX, or COLLADA.

Prerequisites

  • Node.js 16 or later
  • TypeScript 5.0 or later
  • @aspose/3d installed (see Step 1)

Step-by-Step Guide

Step 1: Install @aspose/3d

npm install @aspose/3d

No native addons or system libraries are required. The package includes TypeScript type definitions.

Minimum tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true
  }
}

Step 2: Create a Scene and Node

A Scene is the top-level container. All geometry must be attached to a Node inside the scene tree:

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

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

createChildNode(name) creates a named node and wires it as a child of the current node. The returned Node object is where you will attach the mesh in Step 7.


Step 3: Create a Mesh Object

Mesh holds vertex positions and polygon definitions. Construct one with an optional name:

import { Mesh } from '@aspose/3d/entities';

const mesh = new Mesh('triangle');

The mesh starts empty: no vertices and no faces. You add them in the next steps.


Step 4: Add Control Points (Vertices)

Control points are the vertex positions in local space. Push Vector4 values into mesh.controlPoints. The fourth component (w) is 1 for positions:

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

You reference these positions by their zero-based index when defining polygon faces.


Step 5: Create Polygon Faces

createPolygon() defines a face by listing the vertex indices in order. Three indices make a triangle:

mesh.createPolygon(0, 1, 2);

You can also define quads (four indices) or arbitrary polygons for formats that support them. For glTF, the library will automatically triangulate quads and n-gons on export.


Step 6: Add Vertex Normals

Normals improve rendering quality. Use mesh.createElement() to create a VertexElementNormal and fill its data array with one normal per control point:

import { VertexElementNormal } from '@aspose/3d/entities';
import { VertexElementType, MappingMode, ReferenceMode } from '@aspose/3d/entities';

const normals = mesh.createElement(
    VertexElementType.Normal,
    MappingMode.ControlPoint,
    ReferenceMode.Direct
) as VertexElementNormal;

normals.data.push(new Vector4(0, 0, 1, 0)); // normal for vertex 0 (pointing +Z)
normals.data.push(new Vector4(0, 0, 1, 0)); // normal for vertex 1
normals.data.push(new Vector4(0, 0, 1, 0)); // normal for vertex 2

MappingMode.ControlPoint means one normal per vertex. ReferenceMode.Direct means the data array is indexed directly by the polygon vertex index.


Step 7: Attach the Mesh and Save to glTF

Assign the mesh to the node via node.entity, then save the scene:

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');

To produce a single self-contained .glb file instead, set saveOpts.binaryMode = true and change the output file extension to .glb.

Complete Example

The following is the full script combining all steps above:

import { Scene } from '@aspose/3d';
import { Mesh, VertexElementNormal } from '@aspose/3d/entities';
import { VertexElementType, MappingMode, ReferenceMode } from '@aspose/3d/entities';
import { Vector4 } 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.ControlPoint,
    ReferenceMode.Direct
) as VertexElementNormal;
normals.data.push(new Vector4(0, 0, 1, 0));
normals.data.push(new Vector4(0, 0, 1, 0));
normals.data.push(new Vector4(0, 0, 1, 0));

node.entity = mesh;

const saveOpts = new GltfSaveOptions();
scene.save('triangle.gltf', GltfFormat.getInstance(), saveOpts);
console.log('Triangle mesh saved to triangle.gltf');

Run with ts-node:

npx ts-node triangle.ts

Common Issues

IssueCauseFix
mesh.controlPoints.length is 0 after pushMesh not referenced by any nodePush before assigning node.entity; order does not matter, but verify the reference
Export produces empty geometrynode.entity not assignedEnsure node.entity = mesh before calling scene.save()
Normal count mismatchdata array shorter than controlPointsAdd one normal entry per control point when using MappingMode.ControlPoint
glTF viewer shows black meshNormals pointing inwardReverse winding order in createPolygon (e.g., 0, 2, 1) or negate normal vectors
TypeScript: ’normals.data’ property not foundWrong import pathImport VertexElementNormal from @aspose/3d/entities, not from @aspose/3d root

Frequently Asked Questions

Can I create quads instead of triangles? Yes. Pass four indices to createPolygon(0, 1, 2, 3). The library triangulates quads during export to formats that require triangles (glTF, STL).

What is the difference between MappingMode.ControlPoint and MappingMode.ByPolygonVertex? ControlPoint stores one value per unique vertex. ByPolygonVertex stores one value per polygon-vertex pair, which allows different normals at the same vertex when it belongs to multiple polygons (hard edges).

Do I need to triangulate the mesh before saving to STL? No. The library handles triangulation automatically when exporting to formats that require triangles. You can define quads and n-gons in the mesh and save to STL directly.

How do I add UV coordinates? Use mesh.createElementUV(TextureMapping.Diffuse, MappingMode.ControlPoint, ReferenceMode.Direct) to create a VertexElementUV, then push Vector4 values into its data array, one per control point.

Can I build multiple meshes in one scene? Yes. Create multiple nodes under scene.rootNode and assign a separate Mesh to each node’s entity property.

See Also