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>
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<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.
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:
Boolean operations combine two meshes using set 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 result is commutative correctness: operations on non-ideal meshes behave as if applied to the intended geometry.
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 connectivitymanifold_edge_link: Provides manifold neighborhood information for curve extractionmake_booleanComputes 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:
| 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.
make_boolean_pairReturns 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);
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, 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:
0 (came from first mesh) or 1 (came from second mesh)tf::strict_containment::inside - The region is inside the other meshtf::strict_containment::outside - The region is outside the other meshExample 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:
The arrangements approach is useful when you need:
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.
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:
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.
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());
}
}