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 facilities for planar arrangements.
Include the module with:
#include <trueform/cut.hpp>
Cut operations take intersection curves and produce new meshes where:
All cut operations support an optional tf::return_curves parameter that returns both the cut mesh and the explicit curve geometry.
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(
mesh.polygons(),
scalar_field,
tf::make_range(cut_values));
// With curves: returns {mesh, labels, curves_buffer}
auto [result_mesh, labels, curves] = tf::embedded_isocurves(
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.
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(
mesh.polygons(),
scalar_field,
tf::make_range(cut_values),
tf::make_range(selected_bands));
Isobands are useful for:
Boolean operations combine two meshes using set operations. Functions can be called directly on polygons without precomputed structures, or with precomputed and tagged structures for interactive applications where the same mesh is reused across multiple operations.
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 goal is commutative correctness: operations that commute with mesh idealization.
make_booleanComputes a single boolean result from two input meshes:
// Union operation
auto [result_mesh, labels] = tf::make_boolean(
mesh1.polygons(), mesh2.polygons(), tf::boolean_op::merge);
// With curves
auto [result_mesh, labels, curves] = tf::make_boolean(
mesh1.polygons(), mesh2.polygons(), tf::boolean_op::merge, tf::return_curves);
Available operations:
| Operation | Description |
|---|---|
tf::boolean_op::merge | Union: A ∪ B |
tf::boolean_op::intersection | Intersection: A ∩ B |
tf::boolean_op::left_difference | Difference: A \ B |
tf::boolean_op::right_difference | Difference: B \ A |
The labels buffer contains one integer per face, indicating which input mesh (0 or 1) the face originated from.
With precomputed structures:
// Build structures once, reuse across operations
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);
auto tagged = mesh.polygons() | tf::tag(tree) | tf::tag(fm) | tf::tag(mel);
auto [result, labels] = tf::make_boolean(
tagged, other.polygons(), tf::boolean_op::merge);
make_boolean_pairReturns the two halves of the boolean result, split along the intersection curve. Each half contains the faces from one input mesh that survive the operation:
auto [left_result, right_result] = tf::make_boolean_pair(
mesh1.polygons(), mesh2.polygons(), tf::boolean_op::left_difference);
// With curves
auto [left_result, right_result, curves] = tf::make_boolean_pair(
mesh1.polygons(), mesh2.polygons(), tf::boolean_op::left_difference, tf::return_curves);
Both results have open boundaries at the intersection curve. Concatenating and cleaning them produces the same watertight mesh as make_boolean.
With precomputed structures:
auto tagged1 = mesh1.polygons() | tf::tag(tree1) | tf::tag(fm1) | tf::tag(mel1);
auto tagged2 = mesh2.polygons() | tf::tag(tree2) | tf::tag(fm2) | tf::tag(mel2);
auto [left_result, right_result] = tf::make_boolean_pair(
tagged1, tagged2, tf::boolean_op::left_difference);
make_mesh_arrangementsWhile 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, classes] = tf::make_mesh_arrangements(
mesh1.polygons(), mesh2.polygons());
// meshes: std::vector<polygons_buffer> - one mesh per region
// labels: buffer<int8_t> - which input mesh (0 or 1) each region came from
// classes: buffer<arrangement_class> - spatial classification
Classification details:
0 (came from first mesh) or 1 (came from second mesh)tf::arrangement_class::inside - The region is inside the other meshtf::arrangement_class::outside - The region is outside the other meshtf::arrangement_class::aligned_boundary - Coplanar faces with same normal directiontf::arrangement_class::opposing_boundary - Coplanar faces with opposite normal directionExample usage:
auto [meshes, labels, classes] = tf::make_mesh_arrangements(
mesh1.polygons(), mesh2.polygons());
for (const auto& [mesh, label, cls] : tf::zip(meshes, labels, classes)) {
bool from_first = (label == 0);
if (cls & tf::arrangement_class::inside) {
// Region is inside the other mesh
} else if (cls & tf::arrangement_class::outside) {
// Region is outside the other mesh
} else if (cls & tf::arrangement_class::aligned_boundary) {
// Coplanar region, normals aligned
} else if (cls & tf::arrangement_class::opposing_boundary) {
// Coplanar region, normals opposing
}
}
Relationship to boolean operations:
Mesh arrangements provide the complete decomposition from which any boolean operation can be reconstructed:
The arrangements approach is useful when you need:
With precomputed structures:
auto tagged1 = mesh1.polygons() | tf::tag(tree1) | tf::tag(fm1) | tf::tag(mel1);
auto tagged2 = mesh2.polygons() | tf::tag(tree2) | tf::tag(fm2) | tf::tag(mel2);
auto [meshes, labels, classes] = tf::make_mesh_arrangements(tagged1, tagged2);
Forms can include transformations, enabling boolean operations between transformed instances:
auto transform = tf::make_transformation_from_translation(
tf::make_vector(5.0f, 0.0f, 0.0f));
// Computes boolean between original and translated mesh
auto [res_polygons, labels] = tf::make_boolean(
polygons, polygons | tf::tag(transform), tf::boolean_op::merge);
This pattern is used in the benchmarks to create random overlapping configurations.
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
auto result_mesh = tf::embedded_self_intersection_curves(mesh.polygons());
// With curves: returns {polygons_buffer, curves_buffer}
auto [result_mesh, curves] =
tf::embedded_self_intersection_curves(mesh.polygons(), tf::return_curves);
This operation:
tf::intersections_within_polygonsThe input mesh remains unchanged. The output mesh has faces split such that no face contains a self-intersection curve in its interior.
With precomputed 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);
auto tagged = mesh.polygons() | tf::tag(tree) | tf::tag(fm) | tf::tag(mel);
auto result_mesh = tf::embedded_self_intersection_curves(tagged);
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());
}
}