Modules | TS

Geometry

Mesh generation, measurements, normals, curvature, smoothing, and alignment.

The Geometry module provides tools for computing geometric properties of meshes — normals, curvature, area, volume — along with mesh generation, triangulation, smoothing, and point cloud alignment.

Every function also has an async variant via tf.async for off-main-thread execution.

Mesh Generation

Generate common primitive meshes for testing or as building blocks.

import * as tf from "@polydera/trueform";

// UV sphere
const sphere = tf.sphereMesh(1.0, 20, 20);  // radius, stacks, segments

// Cylinder along z-axis
const cyl = tf.cylinderMesh(1.0, 2.0, 20);  // radius, height, segments

// Axis-aligned box
const box = tf.boxMesh(2, 1, 3);

// Subdivided box for deformation/simulation
const boxSub = tf.boxMesh(2, 1, 3, 4, 2, 6);  // + widthTicks, heightTicks, depthTicks

// Flat plane in XY
const plane = tf.planeMesh(10, 5);

// Subdivided plane
const planeSub = tf.planeMesh(10, 5, 20, 10);  // + widthTicks, heightTicks
FunctionParametersDescription
sphereMeshradius, stacks, segmentsUV sphere
cylinderMeshradius, height, segmentsCylinder along z-axis
boxMeshwidth, height, depth, [ticks]Axis-aligned box
planeMeshwidth, height, [ticks]Flat plane in XY
All generated meshes have consistent outward-facing normals (positive orientation).

Measurements

Area

Total surface area of a mesh.

const box = tf.boxMesh(2, 3, 4);
const a = tf.area(box);  // 52.0

Volume

Volume of a closed 3D mesh. signedVolume is positive when face normals point outward.

const box = tf.boxMesh(2, 3, 4);
tf.volume(box);        // 24.0
tf.signedVolume(box);  // 24.0

Edge Length

tf.meanEdgeLength(mesh);
tf.minEdgeLength(mesh);
tf.maxEdgeLength(mesh);
FunctionReturnsDescription
areanumberTotal surface area
volumenumberAbsolute volume (3D closed mesh)
signedVolumenumberSigned volume (positive = outward normals)
meanEdgeLengthnumberMean edge length across all faces
minEdgeLengthnumberMinimum edge length
maxEdgeLengthnumberMaximum edge length
All measurements respect mesh.transformation. When a mesh has a transformation, measurements are computed in the transformed coordinate space.

Normals

Face and vertex normals are lazily computed on first access and cached on the mesh.

const mesh = tf.mesh(faces, points);

// Face normals — one unit normal per face
const n = mesh.normals;       // NDArrayFloat32 [F, 3]

// Vertex normals — area-weighted average of adjacent face normals
const pn = mesh.pointNormals; // NDArrayFloat32 [V, 3]
PropertyReturnsDescription
mesh.normalsNDArrayFloat32 [F, 3]Unit face normals
mesh.pointNormalsNDArrayFloat32 [V, 3]Unit vertex normals

Curvature Analysis

Surface curvature is computed by fitting a quadric to the local k-ring neighborhood at each vertex.

Principal Curvatures

// Curvature values only
const { k0, k1 } = tf.principalCurvatures(mesh);
// k0: NDArrayFloat32 [V] — maximum principal curvature
// k1: NDArrayFloat32 [V] — minimum principal curvature

// With principal directions
const { k0, k1, d0, d1 } = tf.principalDirections(mesh);
// d0: NDArrayFloat32 [V, 3] — direction of max curvature
// d1: NDArrayFloat32 [V, 3] — direction of min curvature

// Custom k-ring size (default k=2)
const curv = tf.principalCurvatures(mesh, 3);

// Derived measures
const gaussian = k0.mul(k1);         // Gaussian curvature
const mean = k0.add(k1).mul(0.5);    // Mean curvature

Shape Index

Maps principal curvatures to -1, 1, characterizing local surface type.

const si = tf.shapeIndex(mesh);       // NDArrayFloat32 [V]
const si3 = tf.shapeIndex(mesh, 3);   // custom k-ring
Index RangeSurface Type
[-1, -5/8)Concave ellipsoid (cup)
[-5/8, -3/8)Concave cylinder (trough)
[-3/8, 3/8)Hyperboloid (saddle)
[3/8, 5/8)Convex cylinder (ridge)
5/8, 1Convex ellipsoid (cap)

Orientation

For closed 3D meshes, ensure faces are oriented so that normals point outward (positive signed volume).

// Orient mesh to have outward-facing normals
const oriented = tf.positivelyOriented(mesh);

// Skip consistency step if mesh is already consistently oriented
const oriented2 = tf.positivelyOriented(mesh, true);

This function:

  1. Orients faces consistently (unless isConsistent is true)
  2. Computes the signed volume
  3. Reverses all face windings if the signed volume is negative

Triangulation

Convert polygon meshes to triangle meshes using ear-cutting triangulation.

// Triangulate a polygon
const poly = tf.polygon(new Float32Array([
  0,0,0, 1,0,0, 1,1,0, 0.5,1.5,0, 0,1,0
]));
const triMesh = tf.triangulate(poly);

// Triangulate a quad mesh
const quadMesh = { faces: quadFaces, points: pts };
const triMesh2 = tf.triangulate(quadMesh);

// Triangulate a dynamic (variable n-gon) mesh
const dynMesh = { faces: offsetBlockedFaces, points: pts };
const triMesh3 = tf.triangulate(dynMesh);

// Pass a Mesh directly — returned as-is if already triangulated
const same = tf.triangulate(existingTriMesh);
Uses the ear-cutting algorithm. For polygons with more than 80 vertices, z-order curve indexing is used.

Smoothing

Laplacian Smoothing

Iteratively moves vertices toward their neighbors' centroid.

// 100 iterations, lambda=0.5 (default)
const smoothed = tf.laplacianSmoothed(mesh, 100);

// Custom lambda
const smoothed2 = tf.laplacianSmoothed(mesh, 100, 0.3);
ParameterTypeDefaultDescription
mMeshInput mesh
iterationsnumberNumber of smoothing passes
lambdanumber0.5Movement factor in 0, 1
Laplacian smoothing shrinks the mesh. For volume-preserving smoothing, use Taubin smoothing.

Taubin Smoothing

Volume-preserving smoothing by alternating shrink and inflate passes.

// 50 iterations
const smoothed = tf.taubinSmoothed(mesh, 50);

// Custom parameters
const smoothed2 = tf.taubinSmoothed(mesh, 50, 0.5, 0.1);
ParameterTypeDefaultDescription
mMeshInput mesh
iterationsnumberNumber of passes (each = shrink + inflate)
lambdanumber0.5Shrink factor in (0, 1]
kpbnumber0.1Pass-band frequency in (0, 1)
Unlike Laplacian smoothing, Taubin smoothing preserves mesh volume by alternating contraction and expansion.

Point Cloud Alignment

Compute rigid transformations to align point sets. All functions return a 4x4 delta transformation matrix (source world → target world).

ScenarioFunctionNotes
Known correspondencesfitRigidAlignmentOptimal closed-form solution
No correspondences, need initializationfitObbAlignmentCoarse alignment via OBBs
No correspondences, need refinementfitIcpAlignmentFull ICP loop
// Typical pipeline: OBB → ICP
const T_coarse = tf.fitObbAlignment(source, target);
source.transformation = T_coarse;

const T_fine = tf.fitIcpAlignment(source, target, { maxIterations: 50 });

// Compose for total transformation
// T_fine is a delta from source's current world position

Rigid Alignment

Kabsch/Procrustes algorithm for optimal rotation + translation. Requires 1:1 correspondence (source and target must have the same number of points).

const T = tf.fitRigidAlignment(source, target);  // NDArrayFloat32 [4, 4]

Coarse Alignment

Align using oriented bounding boxes (OBBs) for initialization.

const T = tf.fitObbAlignment(source, target);

// Custom sample size for orientation disambiguation
const T2 = tf.fitObbAlignment(source, target, { sampleSize: 200 });
OBB alignment is inherently ambiguous up to 180° rotations about each axis. The function tests all orientations and selects the one with lowest chamfer distance using sampleSize points.

Iterative Refinement

Refine an initial alignment using Iterative Closest Point (ICP).

source.transformation = T_init;

const T = tf.fitIcpAlignment(source, target, {
  maxIterations: 50,
  nSamples: 1000,
});
OptionTypeDefaultDescription
maxIterationsnumber100Maximum iterations
nSamplesnumber1000Subsample size per iteration (0 = all)
knumber1Nearest neighbors (k=1 is classic ICP)
sigmanumber-1Gaussian width (-1 = adaptive)
outlierProportionnumber0Outlier rejection ratio (0-1)
minRelativeImprovementnumber1e-6Convergence threshold
emaAlphanumber0.3EMA smoothing factor

Chamfer Error

One-way Chamfer distance — mean nearest-neighbor distance from source to target.

const error = tf.chamferError(source, target);

// With outlier rejection
const error2 = tf.chamferError(source, target, { outlierProportion: 0.05 });

// Symmetric chamfer distance
const sym = (tf.chamferError(a, b) + tf.chamferError(b, a)) / 2;
Chamfer error is asymmetric. For alignment quality assessment, compute both directions.

Async

All functions are available as tf.async.area(), tf.async.principalCurvatures(), tf.async.fitIcpAlignment(), etc. Signatures are identical — they return Promise<T> instead of T.

const a = await tf.async.area(mesh);
const { k0, k1 } = await tf.async.principalCurvatures(mesh);
const T = await tf.async.fitIcpAlignment(source, target, { maxIterations: 50 });
Use async variants for expensive operations like ICP alignment or curvature analysis to keep the UI thread responsive.