Modules | C++

Cut

Mesh booleans and arrangements.

The cut module provides high-level operations for splitting meshes along curves and performing boolean operations. It builds on the intersect module to compute intersection curves, then embeds these curves into the mesh topology by splitting faces and creating new connectivity. It additionally provides fascilities for planar arrangements.

Include the module with:

#include <trueform/cut.hpp>

Overview

Cut operations take intersection curves and produce new meshes where:

  • Original faces are split along the curves
  • The curves become edges in the new mesh topology
  • Each resulting face region may receives a label indicating its classification

All cut operations support an optional tf::return_curves parameter that returns both the cut mesh and the explicit curve geometry.

Embedded Isocurves

Isocurves are level sets of a scalar field. Embedding them creates a mesh where the contour lines become edges:

tf::buffer<float> scalar_field;
scalar_field.allocate(mesh.points().size());
// Assign scalar values to vertices...

std::array<float, 3> cut_values = {0.0f, 0.5f, 1.0f};

// Returns: {mesh, per-face labels}
auto [result_mesh, labels] = tf::embedded_isocurves<int>(
    mesh.polygons(),
    scalar_field,
    tf::make_range(cut_values));

// With curves: returns {mesh, labels, curves_buffer}
auto [result_mesh, labels, curves] = tf::embedded_isocurves<int>(
    mesh.polygons(),
    scalar_field,
    tf::make_range(cut_values),
    tf::return_curves);

The labels buffer contains one integer per face in the result mesh, indicating which isoband (region between consecutive cut values) the face belongs to. Faces are labeled with indices 0, 1, 2, ... corresponding to regions (-∞, cut_values[0]), [cut_values[0], cut_values[1]), etc.

Technical Details

  • Cut values are automatically sorted internally
  • Scalar values are vertex-based (one per point)
  • Multiple cut values are processed efficiently in a single pass

Isobands

While embedded_isocurves creates a mesh with all regions between cut values, make_isobands extracts only the selected bands:

std::array<float, 4> cut_values = {0.0f, 0.25f, 0.5f, 0.75f};
std::array<int, 2> selected_bands = {1, 3}; // Extract 2nd and 4th bands

auto [result_mesh, labels] = tf::make_isobands<int>(
    mesh.polygons(),
    scalar_field,
    tf::make_range(cut_values),
    tf::make_range(selected_bands));

Isobands are useful for:

  • Extracting regions within specific value ranges
  • Creating multi-level terrain representations
  • Segmenting meshes by property values

Boolean Operations

Boolean operations combine two meshes using set operations.

Input Mesh Requirements

Boolean operations accept PWN (piecewise winding number) meshes—plus the non-manifold flaps and geometric inconsistencies that real-world pipelines accumulate. These defects are modeled as independent noise: locally across polygon regions, globally across manifold edge-connected components.

Intersections collapse to canonical form via reduction diagrams in the ε-topology. The result is commutative correctness: operations on non-ideal meshes behave as if applied to the intended geometry.

See Research Foundation for the conceptual model and Publications for formal definitions.

Boolean Operation Requirements

Boolean operations require forms with topology tags:

// Build topology structures
tf::aabb_tree<int, float, 3> tree;
tree.build(mesh.polygons(), tf::config_tree(4, 4));

tf::face_membership<int> fm;
fm.build(mesh.polygons());

tf::manifold_edge_link<int, 3> mel;
mel.build(mesh.polygons().faces(), fm);

// Create form with required tags
auto form = tf::make_form(tree,
                          mesh.polygons() | tf::tag(fm) | tf::tag(mel));

The topology tags enable:

  • face_membership: Determines edge-to-face connectivity
  • manifold_edge_link: Provides manifold neighborhood information for curve extraction

Basic Boolean: make_boolean

Computes a single boolean result from two input meshes:

// Union operation
auto [result_mesh, labels] = tf::make_boolean(
    form1, form2, tf::boolean_op::merge);

// With curves
auto [result_mesh, labels, curves] = tf::make_boolean(
    form1, form2, tf::boolean_op::merge, tf::return_curves);

Available operations:

OperationDescription
tf::boolean_op::mergeUnion: A ∪ B
tf::boolean_op::intersectionIntersection: A ∩ B
tf::boolean_op::left_differenceDifference: A \ B
tf::boolean_op::right_differenceDifference: B \ A

The labels buffer contains one integer per face, indicating which input mesh (0 or 1) the face originated from.

Boolean Pair: make_boolean_pair

Returns the result of the boolean operation for the left and right mesh separately.

auto [result_mesh, labels] = tf::make_boolean_pair(
    form1, form2, tf::boolean_op::intersection);

Mesh Arrangements: make_mesh_arrangements

While boolean operations select specific regions to combine into a result, make_mesh_arrangements returns all subdivided regions created by the intersection of two meshes, each classified by origin and spatial relationship.

This is the complete decomposition of the intersection problem—every region is returned as a separate mesh with full classification:

auto [meshes, labels, containments] = tf::make_mesh_arrangements(form0, form1);

// meshes: std::vector<polygons_buffer> - one mesh per region
// labels: buffer<int8_t> - which input mesh (0 or 1) each region came from
// containments: buffer<strict_containment> - spatial classification (inside/outside)

Classification details:

  • Labels: Each region is marked with 0 (came from first mesh) or 1 (came from second mesh)
  • Containments: Each region is classified as:
    • tf::strict_containment::inside - The region is inside the other mesh
    • tf::strict_containment::outside - The region is outside the other mesh

Example usage:

// Get all subdivided regions
auto [meshes, labels, containments] = tf::make_mesh_arrangements(form0, form1);

// Process each region with full classification
for (const auto& [mesh, label, containment] : tf::zip(meshes, labels, containments)) {
    bool from_first = (label == 0);
    bool is_inside = (containment == tf::strict_containment::inside);

    if (from_first && is_inside) {
        // Region from first mesh that's inside the second
    } else if (from_first && !is_inside) {
        // Region from first mesh that's outside the second
    }
    // ... and so on for label == 1
}

Relationship to boolean operations:

Mesh arrangements provide the complete decomposition from which any boolean operation can be reconstructed:

  • Union: All outside regions from both meshes + intersection regions
  • Intersection: Inside regions from both meshes
  • A \ B: Outside regions from A that are not inside B

The arrangements approach is useful when you need:

  • Complete control over region selection
  • Multiple different boolean results from the same intersection
  • Per-region analysis with spatial classification
  • Visualization of all subdivided regions

Boolean with Transformations

Forms can include transformations, enabling boolean operations between transformed instances:

auto transform = tf::make_transformation_from_translation(
    tf::make_vector<float, 3>(5.0f, 0.0f, 0.0f));

auto frame = tf::make_frame(transform);

auto form1 = tf::make_form(frame, tree,
                           mesh.polygons() | tf::tag(fm) | tf::tag(mel));
auto form2 = tf::make_form(tree,
                           mesh.polygons() | tf::tag(fm) | tf::tag(mel));

// Computes boolean between original and translated mesh
auto [res_polygons, labels] = tf::make_boolean(
    form1, form2, tf::boolean_op::merge);

This pattern is used in the benchmarks to create random overlapping configurations.

Embedded Self-Intersection Curves

To resolve self-intersections within a single mesh:

auto mesh = load_mesh("self_intersecting.obj");

// Returns: new mesh where self-intersection curves are embedded as edges
// See Booleans for computation of fm and mel
auto result_mesh = tf::embedded_self_intersection_curves(
                mesh.polygons() | tf::tag(tree) | tf::tag(fm) | tf::tag(mel));

// With curves: returns {polygons_buffer, curves_buffer}
auto [result_mesh, curves] =
    tf::embedded_self_intersection_curves(
                mesh.polygons() | tf::tag(tree) | tf::tag(fm) | tf::tag(mel),
                tf::return_curves);

This operation:

  1. Computes all self-intersection curves using tf::intersections_within_polygons
  2. Splits faces along these curves
  3. Returns a new mesh where the intersection curves are edges

The input mesh remains unchanged. The output mesh has faces split such that no face contains a self-intersection curve in its interior.

Planar Arrangements

Planar arrangements compute the subdivision of a 2D plane induced by a set of line segments:

tf::buffer<float> segment_coords; // x0,y0, x1,y1, x2,y2, ...
tf::buffer<int> segment_edges;    // 0,1, 2,3, 4,5, ...

auto segments = tf::make_segments(
    tf::make_blocked_range<2>(segment_edges),
    tf::make_points<2>(segment_coords));

// Build planar arrangement
tf::planar_arrangements<int, float> pa;
pa.build(segments);

for (auto [face, hole_ids] :
    tf::zip(pa.faces(), pa.holes_for_faces())){
    auto polygon = tf::make_polygon(face, pa.points());
    for (auto hole : tf::make_indirect_range(hole_ids, pa.holes())) {
      auto hole_polygon = tf::make_polygon(hole, pa.points());
    }
}