Clean
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
| Geometry | Removes |
|---|---|
| Points | Duplicate vertices |
| Polygons | Duplicate 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,
});
Understanding Index Maps
When returnIndexMap: true is set, cleaning operations return IndexMap objects:
f—NDArrayInt32forward mapping of length N (original count).f[oldId]gives the new index for kept elements (< f.length). The sentinel valuef[oldId] === f.lengthmarks removed elements (degenerate, dropped duplicate, or unreferenced).keptIds—NDArrayInt32of 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
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,
});
