Modules | C++

Intersect

Exact mesh intersections, self-intersections, and scalar field isocontours.

The Intersect module computes geometric intersections between polygons, segments, and scalar fields. All intersection computations are geometrically and topologically exact, using exact arithmetic.

Include the module with:

#include <trueform/intersect.hpp>

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
  • Scalar field intersections: Contour curves at threshold values

Curves are returned as tf::curves_buffer objects for analysis, visualization, or further processing.

To embed intersection curves into mesh topology (splitting faces along curves), use the Cut module.

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 two or more curves meet at a point, crossings can be resolved by splitting curves at the crossing point. This is controlled via tf::intersect_mode flags — see Intersection Modes.
Contour crossing resolution is configured per function with sensible defaults. For detecting where a single mesh intersects itself, use tf::make_self_intersection_curves.

Exact Arithmetic

All mesh and segment intersection computations use exact integer arithmetic. Input coordinates are scaled to an integer range, and all geometric predicates (orientation tests, intersection point computation) are performed with exact integer arithmetic.

By default, coordinates are scaled to int32. For increased precision, all functions accept an optional Int template parameter:

// Default: int32 coordinates (~2.1 billion resolution)
auto curves = tf::make_intersection_curves(mesh1.polygons(), mesh2.polygons());

// Higher precision: int64 coordinates (~9.2e18 resolution)
auto curves = tf::make_intersection_curves<tf::exact::int64>(
    mesh1.polygons(), mesh2.polygons());

The Int parameter controls the entire arithmetic chain via tf::exact::meta<Int>:

IntCoordinatesDifferencesDeterminants
tf::exact::int32 (default)int32int64int128
tf::exact::int64int64int128int256

Intersection Modes

tf::intersect_mode is a bitmask that controls intersection computation and contour crossing resolution. Flags are combined with |.

Base modes (choose one):

FlagDescription
tf::intersect_mode::sosSoS (Simulation of Simplicity) perturbation. All intersections are edge-face — fast, no degenerate cases.
tf::intersect_mode::primitivesFull 5-type classification (VV, VE, EE, VF, EF). Handles shared edges, shared vertices, and coplanar faces.

Contour crossing resolution (optional, combine with base mode):

FlagDescription
resolve_crossing_contoursResolve crossings between different contours on the same face. A contour is defined by a mesh pair — e.g. contour (A,B) and contour (A,C) can cross on a face of mesh A.
resolve_self_crossing_contoursResolve self-crossings within a single contour. Needed when edges from different face pairs of the same contour cross on a face.
resolve_contoursBoth flags combined.

Each function sets appropriate defaults — see the individual function documentation in Intersect and Cut modules.

// Explicit mode with crossing resolution
auto curves = tf::make_intersection_curves(
    tf::make_range(forms, forms + 3),
    tf::intersect_mode::primitives | tf::intersect_mode::resolve_crossing_contours);

Intersection Curves

The simplest way to work with intersections is through curves — connected paths of intersection points.

For repeated computation on moving geometry, build spatial and topological structures once and tag them onto your polygons. See With Precomputed Structures and With Transformations.

Between Two Meshes

Extract intersection curves where two meshes intersect:

mesh_intersection.cpp
auto curves = tf::make_intersection_curves(mesh1.polygons(), mesh2.polygons());

Default mode: sos. With two meshes only one contour exists, so crossing resolution flags have no effect.

mesh_intersection_primitives.cpp
auto curves = tf::make_intersection_curves(
    mesh1.polygons(), mesh2.polygons(), tf::intersect_mode::primitives);

N-Mesh Intersection Curves

Extract all pairwise intersection curves from a range of meshes:

n_mesh_intersection.cpp
decltype(mesh0.polygons()) forms[] = {
    mesh0.polygons() | tf::tag(f0),
    mesh1.polygons() | tf::tag(f1),
    mesh2.polygons() | tf::tag(f2)};

auto curves = tf::make_intersection_curves(tf::make_range(forms, forms + 3));

Default mode: sos | resolve_crossing_contours. With 3+ meshes, contours from different mesh pairs can cross on a shared face — crossings are resolved by default.

Self-Intersection Curves

Find where a mesh intersects itself:

self_intersection.cpp
auto self_curves = tf::make_self_intersection_curves(mesh.polygons());

Default mode: sos | resolve_contours. Both cross-contour and self-crossing resolution are enabled, since different face pairs of the same mesh can produce crossing contours.

To embed self-intersection curves into mesh topology, use tf::embedded_self_intersection_curves from the Cut module.

With Precomputed Structures

All intersection functions accept plain polygons or forms with precomputed structures. When structures are pre-tagged, the function skips building them:

intersection_precomputed.cpp
tf::aabb_tree<int, float, 3> tree1, tree2;
tree1.build(mesh1.polygons(), tf::config_tree(4, 4));
tree2.build(mesh2.polygons(), tf::config_tree(4, 4));

tf::face_membership<int> fm1, fm2;
fm1.build(mesh1.polygons());
fm2.build(mesh2.polygons());

tf::manifold_edge_link<int, 3> mel1, mel2;
mel1.build(mesh1.faces(), fm1);
mel2.build(mesh2.faces(), fm2);

auto form1 = mesh1.polygons() | tf::tag(tree1) | tf::tag(fm1) | tf::tag(mel1);
auto form2 = mesh2.polygons() | tf::tag(tree2) | tf::tag(fm2) | tf::tag(mel2);

auto curves = tf::make_intersection_curves(form1, form2);

With Transformations

Tagged transformations enable intersection in transformed space without copying geometry:

intersection_transformed.cpp
auto T = tf::make_transformation_from_translation(
    tf::make_vector(5.0f, 0.0f, 0.0f));

auto curves = tf::make_intersection_curves(
    mesh.polygons(),
    mesh.polygons() | tf::tag(T));

Using Curves

use_curves.cpp
auto paths = curves.paths();   // Ranges of vertex indices
auto points = curves.points(); // Intersection point coordinates

for (auto path : paths) {
    bool is_closed = path.front() == path.back();
    for (auto vertex_id : path) {
        auto pt = points[vertex_id];
    }
}

Isosurface Curves (Isocontours)

Extract curves where a scalar field crosses threshold values.

To embed isocontours into mesh topology, use tf::embedded_isocurves or tf::make_isobands from the Cut module.

Single Isocontour

isocontour.cpp
std::vector<float> scalar_field(mesh.points().size());
// Assign scalar values to vertices...

float threshold = 0.5f;
auto contour = tf::make_isocontours(mesh.polygons(), tf::make_range(scalar_field), threshold);

Multiple Isocontours

multi_isocontours.cpp
std::vector<float> levels = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
auto contours = tf::make_isocontours(mesh.polygons(), tf::make_range(scalar_field), tf::make_range(levels));

Low-Level Intersection Access

For advanced use cases, direct access to intersection data structures.

Low-level classes store intersection points as integer coordinates internally (int32 by default, configurable via Int template parameter). Use .converter().deconvert(pt) to convert back to floating-point coordinates.

Between Meshes

low_level_mesh.cpp
tf::intersections_between_polygons<int, float> ibp; // int32 (default)
ibp.build(form1, form2, tf::intersect_mode::primitives);

// With int64 precision
tf::intersections_between_polygons<int, float, tf::exact::int64> ibp64;
ibp64.build(form1, form2, tf::intersect_mode::primitives);

// Access intersection points (int32)
auto points = ibp.intersection_points();

// Deconvert to float
auto &conv = ibp.converter();
for (auto pt : points) {
    auto fpt = conv.deconvert(pt);  // tf::point<float, 3>
}

// Access structured intersections grouped by (tag, face)
for (auto group : ibp.intersections()) {
    for (auto intersection : group) {
        intersection.tag;            // 0 or 1 (which mesh)
        intersection.tag_other;      // The other mesh
        intersection.object;         // Face ID
        intersection.object_other;   // Other face ID
        intersection.id;             // Point ID in intersection_points()
        intersection.target.label;   // tf::topo_type: vertex/edge/face
        intersection.target.id;      // Local index on face
    }
}

N-mesh overload:

low_level_n_mesh.cpp
tf::intersections_between_polygons<int, float> ibp;
ibp.build(tf::make_range(forms, forms + 3), tf::intersect_mode::primitives);

Self-Intersections

low_level_self.cpp
tf::intersections_within_polygons<int, float> iwp;
iwp.build(form, tf::intersect_mode::primitives);

for (auto group : iwp.intersections()) {
    for (auto intersection : group) {
        intersection.object;         // Face ID
        intersection.object_other;   // Other intersecting face ID
        intersection.id;             // Point ID
        intersection.target.label;   // Topological type
        intersection.target.id;      // Local index
    }
}

Segment Intersections

For 2D or 3D segment collections. Points are stored as int32 internally:

segment_intersections.cpp
tf::intersections_within_segments<int, float, 2> iws;
iws.build(segments | tf::tag(tree) | tf::tag(edge_membership));

// Access int32 intersection points
auto points = iws.intersection_points();

// Deconvert
auto &conv = iws.converter();
for (auto pt : points) {
    auto fpt = conv.deconvert(pt);
}

// Structured intersections grouped by edge
for (auto group : iws.intersections()) {
    for (auto intersection : group) {
        intersection.object;         // Edge ID
        intersection.object_other;   // Other edge ID
        intersection.id;             // Point ID
        intersection.target.label;   // vertex or edge
        intersection.target.id;      // Local index (0 or 1 for vertex)
    }
}

Scalar Field Intersections

Low-level access to isosurface intersection data:

scalar_field_low_level.cpp
tf::scalar_field_intersections<int, float, 3> sfi;
sfi.build(mesh.polygons(), scalar_field, threshold);

auto points = sfi.intersection_points();

for (auto group : sfi.intersections()) {
    for (auto intersection : group) {
        intersection.object;        // Face ID
        intersection.id;            // Point ID
        intersection.target.label;  // vertex/edge where crossing occurs
        intersection.target.id;     // Local index
    }
}

// Multiple thresholds
sfi.build_many(mesh.polygons(), scalar_field, thresholds);