Modules | TS

Remesh

Decimation, simplification, and isotropic remeshing.

The Remesh module provides tools for modifying triangle mesh resolution: reducing face count through decimation (to a target count) or simplification (to an error budget), or redistributing vertices through isotropic remeshing.

All remesh functions require triangle meshes (3 vertices per face) and 3D coordinates. Use tf.triangulate from the Geometry module to convert polygon meshes first.
All remesh operations use parallel execution by default. Set parallel: false for sequential execution (e.g. when processing many meshes in parallel externally).
All edge length decisions (split thresholds, collapse thresholds, max edge length checks) respect the Meshtransformation. When a Mesh has a transformation, lengths are measured in the transformed coordinate space. This allows remeshing a scaled or rotated mesh without modifying vertex data.

Decimation

Reduce face count using quadric error metrics. The algorithm collapses edges in priority order, placing the new vertex at the position that minimizes geometric error.

Basic Usage

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

const mesh = tf.readStl("model.stl");

// Decimate to 10% of original faces
const decimated = tf.decimated(mesh, 0.1);

With Configuration

const decimated = tf.decimated(mesh, 0.1, {
  preserveBoundary: true,
  minQuality: 0.3,
  parallel: false,
});
ParameterTypeDefaultDescription
mMeshTriangle mesh
targetProportionnumberTarget face count as fraction of original (0.0–1.0)
opts.minQualitynumber-1Worst triangle quality allowed after a collapse, in 0,1 (1 = equilateral). Negative disables, 0 = never worsen, >0 = quality floor
opts.preserveBoundarybooleantrueIf true, boundary edges are never collapsed
opts.stabilizernumber1e-3Tikhonov stabilizer for quadric solve
opts.parallelbooleantrueUse parallel partitioned collapse
opts.featureAnglenumber-1Feature edge detection angle in degrees. Edges sharper than this are preserved. Negative disables
opts.featureWeightnumber100Penalty weight for feature edge preservation. Higher = stronger
opts.preserveRegionsInt32Array | number[] | NDArrayundefinedPer-face region labels (exactly one int per input face). When given, edges between differing labels are preserved as features and the call returns { mesh, regions } — the output mesh's per-face int32 labels — instead of a Mesh

Feature Edge Preservation

Preserve sharp creases and corners during decimation:

const decimated = tf.decimated(mesh, 0.1, { featureAngle: 30 });

Returns a new Mesh, or { mesh, regions } when preserveRegions is given.

Simplification

Like decimation, simplification reduces a triangle mesh by quadric-error edge collapse (Garland–Heckbert). The difference is the stopping criterion: decimation stops at a target face count, while simplification stops at a geometric error budget. Flat regions collapse to almost nothing while curved detail and feature edges survive — the right tool when you care about fidelity rather than a specific size, such as cleaning up the over-sampled output of a boolean or arrangement.

Basic Usage

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

const mesh = tf.readStl("model.stl");

// Simplify within the default budget (0.2% of the bounding-box diagonal)
const simplified = tf.simplified(mesh);

With Configuration

const simplified = tf.simplified(mesh, {
  errorRel: 0.005,          // allow more deviation -> fewer faces
  optimizeIterations: 3,
  featureAngle: 30,
});
ParameterTypeDefaultDescription
mMeshTriangle mesh
opts.errorRelnumber0.002Error allowed per collapse pass, as a fraction of the bounding-box diagonal: an edge is collapsed when its quadric error is <= errorRel * diagonal. With iterations > 1 it is re-applied each pass (against the current surface), so it caps per-pass error, not total deviation from the original
opts.optimizeIterationsnumber3Rounds of quality cleanup (min-angle edge flip + tangential relaxation) run after each collapse. 0 = pure error-budget collapse
opts.iterationsnumber1Outer collapse rounds. 1 = single collapse + cleanup; >1 re-collapses after each cleanup (iterated remesh), removing more at the cost of more deviation from the original
opts.relaxationItersnumber3Tangential relaxation passes per cleanup round
opts.lambdanumber0.5Damping factor for tangential relaxation in (0, 1]
opts.minQualitynumber0.3Worst triangle quality allowed after a collapse, in 0,1 (1 = equilateral). Negative disables, 0 = never worsen, >0 = quality floor
opts.preserveBoundarybooleantrueIf true, boundary edges are never collapsed
opts.stabilizernumber1e-3Tikhonov stabilizer for quadric solve
opts.parallelbooleantrueUse parallel partitioned collapse
opts.featureAnglenumber-1Feature edge detection angle in degrees. Edges sharper than this are preserved. Negative disables
opts.featureWeightnumber100Penalty weight for feature edge preservation. Higher = stronger
opts.preserveRegionsInt32Array | number[] | NDArrayundefinedPer-face region labels (exactly one int per input face). When given, edges between differing labels are preserved as features and the call returns { mesh, regions } — the output mesh's per-face int32 labels — instead of a Mesh

Quality Cleanup

A pure error-budget collapse leaves whatever triangulation the collapses produce, which can include thin slivers in flattened regions. Set optimizeIterations to run that many rounds of min-angle edge flip plus tangential relaxation after the collapse — feature- and boundary-aware — to even out the result:

const simplified = tf.simplified(mesh, { optimizeIterations: 5 });

Iterated Remesh

A single collapse stops at the edges the quality guard blocked. Set iterations > 1 to re-collapse after each cleanup round: the flip pass repairs the slivers a collapse leaves behind, which unblocks collapses the previous round refused, so more is removed. This turns simplification into an iterated error-budget remesh — the error sibling of isotropic remeshing. It trades fidelity for coarseness, since each round measures error against the already-collapsed surface rather than the original.

const simplified = tf.simplified(mesh, { iterations: 3 });

Region Preservation

To keep the boundaries between labelled regions (e.g. geological domains, material groups) intact, pass preserveRegions with a per-face label array (exactly one int per input face). Edges between differently-labelled faces are treated as features and never crossed, and the call returns the output mesh's per-face labels (int32, one per output face) alongside the mesh. This works the same way for decimated, simplified, and isotropicRemeshed (sync and async).

const { mesh, regions } = tf.simplified(inputMesh, { preserveRegions: regionLabels });
// regions: NDArray of int32, one label per face of `mesh`

A wrong-sized array (including an empty one) throws — omit preserveRegions when there are no regions to carry.

Returns a new Mesh, or { mesh, regions } when preserveRegions is given.

Isotropic Remeshing

Redistribute vertices to achieve uniform edge lengths. Each iteration splits long edges, collapses short edges, flips edges to improve valence, and relaxes vertex positions tangentially.

Basic Usage

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

const mesh = tf.readStl("model.stl");

// Remesh to target edge length
const mel = tf.meanEdgeLength(mesh);
const remeshed = tf.isotropicRemeshed(mesh, 2.0 * mel);

With Configuration

const remeshed = tf.isotropicRemeshed(mesh, 0.02, {
  iterations: 5,
  relaxationIters: 5,
  preserveBoundary: true,
  useQuadric: true,
});
ParameterTypeDefaultDescription
mMeshTriangle mesh
targetLengthnumberTarget edge length. Longer edges are split, shorter are collapsed
opts.iterationsnumber3Number of outer iterations (split + collapse + flip + relax)
opts.relaxationItersnumber3Tangential relaxation iterations per outer iteration
opts.minQualitynumber0.3Worst triangle quality allowed after a collapse, in 0,1 (1 = equilateral). Negative disables, 0 = never worsen, >0 = quality floor
opts.lambdanumber0.5Damping factor for tangential relaxation in (0, 1]
opts.preserveBoundarybooleantrueIf true, boundary edges are never split or collapsed
opts.useQuadricbooleanfalseUse quadric error metric for collapse vertex placement
opts.parallelbooleantrueUse parallel execution
opts.featureAnglenumber-1Feature edge detection angle in degrees. Edges sharper than this are preserved. Negative disables
opts.featureWeightnumber100Penalty weight for feature edge preservation. Higher = stronger
opts.preserveRegionsInt32Array | number[] | NDArrayundefinedPer-face region labels (exactly one int per input face). When given, edges between differing labels are preserved as features and the call returns { mesh, regions } — the output mesh's per-face int32 labels — instead of a Mesh

Returns a new Mesh, or { mesh, regions } when preserveRegions is given.

Typical Pipeline

A common workflow is to decimate first, then isotropic remesh to improve triangle quality:

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

const mesh = tf.readStl("model.stl");

// Decimate to 5%
const dec = tf.decimated(mesh, 0.05);

// Isotropic remesh to mean edge length of decimated result
const mel = tf.meanEdgeLength(dec);
const result = tf.isotropicRemeshed(dec, mel, { useQuadric: true });
Use tf.meanEdgeLength to compute a natural target length from the current mesh. This is a good default for isotropic remeshing after decimation.

Async

All remesh functions are available as async variants via tf.async for off-main-thread execution:

const dec = await tf.async.decimated(mesh, 0.1);
const sim = await tf.async.simplified(mesh, { errorRel: 0.005 });
const rem = await tf.async.isotropicRemeshed(mesh, 0.02);

// preserveRegions works the same as sync, returning { mesh, regions }:
const { mesh: out, regions } = await tf.async.decimated(mesh, 0.1, {
  preserveRegions: regionLabels,
});