Fill
The Fill module closes holes. It takes a mesh and the rims its boundary walks, states one patch per rim, and replays those patches into the filled mesh. The patch continues the surface around the hole where the surface allows it, and falls back to a flat lid or an exact minimal triangulation where it does not.
import * as tf from "@polydera/trueform";
Fill Holes
const rims = tf.boundaryRims(mesh);
const fill = tf.fillHoles(mesh, rims);
const filled = tf.filledMesh(mesh, fill);
// off the main thread
const fillAsync = await tf.async.fillHoles(mesh, rims, { splitRim: false });
const filledAsync = await tf.async.filledMesh(mesh, fillAsync);
| Parameter | Type | Description |
|---|---|---|
mesh | Mesh | The mesh the rims are read against |
rims | BoundaryRimsResult | The rims tf.boundaryRims returns |
opts.minQuality | number, default 0.3 | The floor a table patch is refined toward; 0 asks for none |
opts.fairing | boolean, default true | Whether a refined table patch is faired against its host |
opts.splitRim | boolean, default true | Whether a tier may split the host's original rim edges |
Each rim is one group, handed down three tiers: exhaustive enumeration for a rim of at most five vertices; then the chart, which reads the hole as a height field over the plane its rim spans and continues the surface around it — or, where it refuses and the rim lies exactly in one plane, the flat lid in that plane; and otherwise the table, the patch with the best smallest angle and least area over a tetrahedralization of the rim, refined and faired.
The chart decides on the mesh's own exact lattice, so a rim of only a few lattice units per edge gets the lid or a refusal rather than an invented surface.
With splitRim, a tier may split a rim edge; the split and the carrying face's new triangles are published in the result and replayed by filledMesh, so the filled mesh stays watertight. Without it no original edge is touched, and a rim the table could only fill by a split refuses as refusedRimSplit.
The Result
HoleFill is laid out per group: entry g of every per-group lane belongs to rim g; jagged lanes are rows beside [G + 1] offsets.
| Field | Type | Meaning |
|---|---|---|
triangleOffsets, triangles | NDArrayInt32 [G+1], [T, 3] | The patch triangles per group |
mintedOffsets, mintedPoints | NDArrayInt32, [M, 3] float | The points each patch minted |
status, charted, refined, faired | NDArrayInt8 [G] | Values of tf.HoleFillStatus, tf.HoleChartStatus, tf.HoleRefineStatus, tf.HoleFairStatus |
seamMaxAngle | float [G] | Largest angle across the seam in radians, -1 where not computed |
offending | NDArrayInt32 [G, 2] | The (rim, edge) a refusal names, (-1, -1) where none |
splits, splitParameters | NDArrayInt32 [S, 4], [S] | Split edges as (face, v0, v1, point), parameter over 64 from v0 |
planFaces, planOffsets, planTriangles | NDArrayInt32 | The carrying faces the splits reached, and what each becomes |
groupOffsets | NDArrayInt32 [G+1] | The run of rims each group fills |
const status = fill.status.data;
for (let g = 0; g < status.length; g++) {
if (status[g] === tf.HoleFillStatus.filled) continue;
const [rim, edge] = [fill.offending.data[2 * g], fill.offending.data[2 * g + 1]];
console.log(`rim ${rim} refused at edge ${edge}`);
}
tf.deleteHoleFill(fill); // releases every lane at once
tf.HoleFillStatus is filled, refusedOpen, refusedInvalidRim, refusedHostFace, refusedProtectionDepth, refusedTable, refusedResource or refusedRimSplit. tf.HoleChartStatus is admitted (the chart continues the host), rimPlane (the flat lid), notAttempted, or the reason the chart refused: noArea, steep, folded, collarArity, uncovered, unsampled, unsolved, forbiddenChord, resource, outOfLattice.
Filled Mesh
tf.filledMesh(mesh, fill) returns a new Mesh: the input with what the fill changed. A face no split reached is copied, a carrying face a split reached is replaced by its plan, and the patches follow. The points are the input's, then the minted ones.
HoleFill owns its WASM storage, so it outlives the mesh and rims it was computed from. Release it with tf.deleteHoleFill(fill) when done.