Modules | TS

Clean

Remove duplicates, degenerates, and unreferenced elements.

The Clean module removes duplicate, degenerate, and unreferenced elements from geometric data. It operates on meshes, point batches, and triangle soups, maintains referential integrity, and provides optional index maps for reindexing associated data.

What Gets Removed

GeometryRemoves
PointsDuplicate vertices
PolygonsDuplicate vertices, duplicate faces, degenerate faces, unreferenced vertices

Definitions:

  • Duplicate vertices: Points at the same location (exact or within tolerance)
  • Duplicate faces: Faces with the same vertex set (any cyclic rotation, either winding)
  • Degenerate faces: Faces with fewer than 3 unique vertices (zero area)
  • Unreferenced vertices: Vertices not used by any face

When duplicates are removed, the element with the smallest index is kept.

Meshes

Clean a mesh by removing duplicate/degenerate vertices and faces:

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

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

// Exact duplicate removal
const clean = tf.cleaned(mesh);

// Tolerance-based: merge vertices within distance
const clean2 = tf.cleaned(mesh, 1e-6);

With Index Maps

Get index maps for reindexing associated data:

const { mesh: clean, faceMap, pointMap } = tf.cleaned(mesh, {
  returnIndexMap: true,
  tolerance: 1e-6,
});

// Reindex per-face attributes using keptIds
// faceMap.keptIds: original face indices that survived
// pointMap.keptIds: original vertex indices that survived

Points

Remove duplicate points from point batches:

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

const pts = tf.point([[0, 0, 0], [1, 0, 0], [0, 0, 0]]);

// Exact duplicate removal
const clean = tf.cleaned(pts);

// Tolerance-based
const clean2 = tf.cleaned(pts, 1e-6);

// With index map
const { points: clean3, pointMap } = tf.cleaned(pts, {
  returnIndexMap: true,
  tolerance: 1e-6,
});

Triangle Soups

Triangle soups (batch primitives with unindexed geometry) can be cleaned, converting them to an indexed Mesh with shared vertices:

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

// Triangle batch: each triangle has 3 vertices
const soup = tf.triangle(
  [[0, 0, 0], [1, 0, 0], [2, 0, 0]],  // v0s
  [[1, 0, 0], [2, 0, 0], [3, 0, 0]],  // v1s
  [[0.5, 1, 0], [1.5, 1, 0], [2.5, 1, 0]],  // v2s
);

// Clean converts to indexed Mesh with shared vertices
const mesh = tf.cleaned(soup);

// With tolerance
const mesh2 = tf.cleaned(soup, 1e-6);

Configuration

The opts object passed to tf.cleaned accepts two additional flags for fine-grained control over which optional passes run. Vertex deduplication and degenerate-primitive removal always run; the flags only gate the two optional passes:

interface CleanOpts {
  tolerance?: number;
  returnIndexMap?: boolean;
  removeDuplicatePrimitives?: boolean;   // default true
  removeUnreferencedPoints?: boolean;    // default true
}

Examples:

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

// Keep duplicate faces but still merge vertices and drop unreferenced ones.
const clean = tf.cleaned(mesh, { removeDuplicatePrimitives: false });

// Tolerance + skip the unreferenced-point prune.
const clean2 = tf.cleaned(mesh, {
  tolerance: 1e-6,
  removeUnreferencedPoints: false,
});

// Same with index maps.
const { mesh: clean3, faceMap, pointMap } = tf.cleaned(mesh, {
  returnIndexMap: true,
  removeDuplicatePrimitives: false,
});
The flags apply to mesh cleaning. For point cleaning and triangle-soup cleaning they're accepted for API uniformity but have no effect (neither concept applies).

Understanding Index Maps

When returnIndexMap: true is set, cleaning operations return IndexMap objects:

  • fNDArrayInt32 forward mapping of length N (original count). f[oldId] gives the new index for kept elements (< f.length). The sentinel value f[oldId] === f.length marks removed elements (degenerate, dropped duplicate, or unreferenced).
  • keptIdsNDArrayInt32 of original indices that were kept, length M (new count).

The sentinel convention is uniform across the trueform clean module: any input whose f[i] === f.length was removed; all survivors (including duplicates folded into a representative) satisfy f[i] < f.length.

const { mesh: clean, faceMap, pointMap } = tf.cleaned(mesh, {
  returnIndexMap: true,
});

// faceMap.f[oldFaceId] → new face index (or sentinel if removed)
// faceMap.keptIds → original face indices that survived
// pointMap.f[oldVertexId] → new vertex index (or sentinel if removed)
// pointMap.keptIds → original vertex indices that survived
See the Reindex module for more ways to use index maps.
All cleaning operations maintain referential integrity: after removing elements, all face indices are correctly updated to reference the new vertex positions.

Async

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

const clean = await tf.async.cleaned(mesh, 1e-6);
const { mesh: m, faceMap, pointMap } = await tf.async.cleaned(mesh, {
  returnIndexMap: true,
});
For implementation details, see the C++ Clean documentation.