Intersect
The Intersect module computes geometric intersections between meshes. All intersection computations are geometrically and topologically exact, using exact arithmetic.
import * as tf from "@polydera/trueform";
Overview
The Intersect module computes intersection geometry without modifying the input meshes:
- Mesh-mesh intersections: Curves where two or more meshes intersect
- Self-intersections: Curves where a mesh intersects itself
Curves are returned as Curves objects with paths (an OffsetBlockedBuffer of index sequences) and points (an NDArrayFloat32 of coordinates).
Supported Input
Intersection computation supports a wide range of input geometry:
- Open and closed meshes — boundaries are handled correctly
- Non-manifold edges — edges shared by 3 or more faces
- Coplanar faces — overlapping faces from the same or different meshes
- Self-intersecting geometry — meshes that intersect themselves are detected and curves are extracted
- Crossing intersection curves — where curves from different mesh pairs meet on a face, crossings can be resolved. Configured via the
IntersectOptsinterface — see Intersection Configuration.
tf.selfIntersectionCurves; for whether it does at all, tf.hasSelfIntersections.Intersection Configuration
The IntersectOpts interface controls intersection computation. It maps to the C++ tf::intersect_config {mode, tolerance}; the within option is composed into the mode bitmask as primitives | within.
interface IntersectOpts {
mode?: "sos" | "primitives";
tolerance?: number;
within?: boolean;
}
| Field | Description |
|---|---|
mode | The classifier ("primitives" or "sos"); within composes with whichever is selected into the one mode that crosses. "primitives" (default) handles shared edges/vertices/coplanar faces; "sos" (Simulation of Simplicity) is fast with no degenerate cases, but no contact is ever coplanar — coplanar walls then do not pool and the regions they would have separated stay joined. |
tolerance | World-coordinate distance an input vertex may move to reach the lattice (default 0, exact). |
within | Also intersect each mesh with itself — its self records and the resolution of crossings within one contour class are one request, never separable. Required when a mesh can self-overlap, e.g. meshes concatenated into one input. Honored by every entry taking these options, intersectionCurves included; a one-mesh entry implies it. Default false. |
Contour crossings are resolved unconditionally, not on request: between contour classes whenever a third mesh exists to make such a pair, and within one class whenever the run is within.
Tolerance
A non-zero tolerance is a statement about the INPUT, not about a predicate, and it promises exactly two things. ALMOST-COPLANAR IS COPLANAR: faces whose directions agree pool onto one committed plane, and a face that another form's vertex stands within the tolerance of commits a plane together with that vertex's own faces lying within the tolerance of it. ALMOST-ON-PLANE IS ON-PLANE: a vertex within the tolerance of a committed plane is dropped exactly onto it — where the plane settled, not where its face began. First the planes pull: a face commits to a plane only if its own corners move within the tolerance onto it. Then the vertices drop, bounded by the same tolerance. Everywhere the tolerance does not act it is zero: a vertex nothing pulled is not moved, and the output states it exactly as you gave it. What the pipeline arranges is that moved mesh, EXACTLY: every predicate below the placement runs at zero, so the tolerance only ever adds contacts the input states within it, and never removes one it states exactly. A plane its corners cannot follow, and a vertex no committed plane can hold within the tolerance, keep their own positions — the tolerance never invents one.
const { mesh, tagLabels, faceLabels } = tf.meshArrangements(
[mesh0, mesh1], { tolerance: 1e-6 });
A tolerance of 0 is the identity: nothing is pooled, nothing moves, no placement table is built, and the result is the exact arrangement of the input as given.
Each function sets appropriate defaults — see individual function documentation below and in Arrangement.
Self-Intersection Test
Whether a mesh meets itself — true exactly when the self-intersection build would state a record.
if (tf.hasSelfIntersections(mesh)) {
const curves = tf.selfIntersectionCurves(mesh);
}
The discovery runs in exact arithmetic and stops at the first contact, so a mesh that meets itself is answered without the rest of it being seen. A shared vertex or edge is not itself a contact; any other touching — between neighbouring faces too — is one.
The tree, faceMembership and manifoldEdgeLink the call needs are completed into the mesh's cache, so a later query — a second test, a curve extraction, a boolean — pays for none of them. 3D meshes only.
Intersection Curves
Between Two Meshes
const curves = tf.intersectionCurves(mesh0, mesh1);
const curves = tf.intersectionCurves(mesh0, mesh1, { mode: "primitives" });
Default: mode: "primitives", tolerance: 0 (exact). With two meshes every contour is of the one class (A,B); crossings among them — e.g. from disjoint components of one mesh — are resolved when the run is within, which the arrangement entries take.
N-Mesh Intersection Curves
Compute all pairwise intersection curves from an array of meshes:
const curves = tf.intersectionCurves([mesh0, mesh1, mesh2]);
const curves = tf.intersectionCurves([mesh0, mesh1, mesh2], { mode: "sos" });
Default: mode: "primitives", tolerance: 0 (exact). With 3+ meshes the arity makes a cross-class contour pair possible, so those crossings are resolved.
Self-Intersection Curves
Find where a mesh intersects itself:
const curves = tf.selfIntersectionCurves(mesh);
const curves = tf.selfIntersectionCurves(mesh, { mode: "primitives" });
Default: mode: "primitives", tolerance: 0 (exact). A one-mesh entry implies within, so the mesh's own self records and its self-crossing contours are both in.
tf.polygonArrangements from the Arrangement module.With Transformations
mesh1.transformation = tf.makeTranslation([5, 0, 0]);
const curves = tf.intersectionCurves(mesh0, mesh1);
Using Curves
for (let i = 0; i < curves.length; i++) {
const pathIds = curves.paths.at(i);
// Access points: curves.points
}
Async
All intersect functions are available as async variants via tf.async for off-main-thread execution:
const curves = await tf.async.intersectionCurves(mesh0, mesh1);
const self = await tf.async.selfIntersectionCurves(mesh);
const meets = await tf.async.hasSelfIntersections(mesh);
