Algorithms
· Updated
booleanscsgoverview

Mesh booleans

Union, intersection and difference of 3D meshes, tested on the inputs that break booleans: identical operands, flush faces, non-manifold edges, meshes that cross themselves. Then the speed, in C++, Python and JavaScript.

Žiga Sajovic, Polydera

Subtract the Stanford dragon from itself and the answer should be nothing. It is a hard test. Every face of one operand lies on a face of the other, so every decision lands exactly on zero. This page puts trueform through that test and three more that break booleans. Then it says what trueform asks of your meshes and how fast it runs.

A mesh boolean returns the union (A ∪ B), the intersection (A ∩ B) or the difference (A ∖ B) of solids stored as surface meshes. It also goes by CSG, mesh subtraction and 3D boolean operations. Below, the dragon is A and a sphere is B. Every output face is coloured by the operand it came from.

InputA, the dragon · B, a sphere
UnionA ∪ B
IntersectionA ∩ B
DifferenceA ∖ B

Each output is one closed mesh. The orange faces are pieces of the sphere: in the union they are its outside, in the intersection and the difference they wall the cut. Run booleans on a dragon in your browser in the live example. Each language has a full tutorial.

Four inputs that break booleans

A boolean is easy when two surfaces cross cleanly. It is hard where they touch, coincide or cross themselves. The four tests below aim at exactly that. We ran them with trueform 0.10.8 from pip, in Python. The data is the public Stanford dragon, or shapes made from it. Run your own hard cases too; pip install trueform is one line.

The same mesh twice

The dragon has 244,026 triangles. Use it as both operands. Every face now sits on a face of the other mesh, edge on edge and corner on corner.

  • A ∪ A returns the dragon: 244,026 triangles, closed.
  • A ∩ A returns the dragon: 244,026 triangles, closed.
  • A ∖ A returns an empty mesh.

Nothing is nudged to make the case generic. Each contact is classified for what it is, and coincident faces count as coincident.

Flush faces everywhere

We turned the dragon into voxels: a closed mesh of 46,268 triangles, every vertex on a unit grid. Then we moved a copy by one voxel along each axis. Every face of one operand now lies in a plane of the other. Edges run along edges and corners land on corners. The voxel surface also has 45 non-manifold edges, where two voxels meet along an edge and nothing more.

This input has a known answer. Count the voxels. The volume of each output mesh must equal the count exactly.

OperationVoxels (expected)Volume of the outputClosedUnion55,74355,743.000yesIntersection42,47342,473.000yesDifference6,6356,635.000yes

The picture is the difference. The teal faces are what is left of the first dragon. The orange faces are walls of the moved copy, now cut into the first. The two colours meet only along shared edges and shared planes.

A mesh that crosses itself

Real files often hold two overlapping shells as one mesh. Inside and outside are then ambiguous, and a boolean expression needs them. There are two ways through, both in the CSG module.

Repair, then combine. outer_shell keeps only the faces that bound the outside and drops everything buried in the overlap. The shell is a valid operand for any boolean.

shell = tf.outer_shell(mesh)   # C++: tf::make_outer_shell, JavaScript: tf.outerShell
result = tf.boolean_difference(shell, other)

Two dragons in one mesh: 488,052 triangles that cross themselves along the pink curves. The shell has 444,856 triangles. It is closed and crosses itself nowhere.

Keep the overlap as a region. Build a CSG graph with within on. Each operand is then also cut against itself, and domains() returns the overlap pocket as its own closed cell. Pick the cells you need from there. Boolean expressions on the graph still read each operand as one solid, so for an expression, repair first. CSG for engineering works through both.

An open surface

A plane, a terrain or a scanned patch bounds no volume. Declare it a sheet and it becomes a valid operand. It cuts a solid, and difference and intersection return the two halves, each capped and closed. A unit box cut by a plane gives two halves of volume 0.5.

(faces, points), labels, face_labels = tf.boolean_difference(box, plane, sheets=[1])

Many sheets cutting many solids at once are the subject of CSG for engineering.

Why these cases hold

Exact decisions. Every geometric decision runs in integer arithmetic. Float coordinates go onto an int32 lattice, double coordinates onto int64. Each predicate is evaluated in integers wide enough to be exact. A sign test returns minus, zero or plus. Zero means touching, not "close to touching". The method is written up in Fast and Exact Mesh Booleans and Arrangements.

Five kinds of contact. Two triangles can meet vertex to vertex, vertex to edge, edge to edge, vertex to face or edge to face. The default mode states each contact as one of the five. It does not perturb them away. That is why the identical and voxel tests come out exact. Coincident faces are classified as facing the same way or opposite ways.

One arrangement, read once. A boolean is the two-mesh case of trueform's CSG graph. It splits both meshes along the curves where they cross, classifies every piece against the other mesh, and keeps the pieces the operation asks for.

How other libraries handle a touch

The hard cases above all land on exact contact. Libraries take different roads there, and each road has a price. The descriptions come from each library's own documentation.

ApproachExampleAt an exact touchFloating point with symbolic perturbationManifoldTies are broken by a fixed nudge that depends on the operation, so touching cubes merge and a mesh minus itself is empty. Its stated aim is manifold output, with geometry valid to within a small ε.Corefinement with an exact kernelCGALContacts are resolved exactly. Inputs must bound a volume and not cross themselves, and the output must be manifold: two cubes that share only an edge cannot be united this way.Floating point with Simulation of SimplicityMeshLib, mesh modeDegenerate contacts are resolved by a symbolic perturbation: an exact touch is decided as if one surface had moved by an infinitesimal amount, so it reads as a crossing or a gap, not as a touch. This is the mode in the speed comparison below.VoxelsMeshLib, voxel modeBoth solids become a grid, the operation runs on the grid, and a surface is extracted. Detail is bounded by the voxel size.Integer lattice, contact stated as istrueformCoordinates go onto an integer lattice once and every predicate is exact. A touch is classified as one of five contacts and kept, not nudged, so coincident faces stay coincident.

Sources: Manifold's library notes, CGAL's Boolean Operations on Meshes manual and MeshLib's mesh boolean page, which describes both of its modes. Speed is a separate question, answered below.

trueform also takes the voxel road when it fits. Its volume module samples closed meshes into signed distance fields and combines them by union, intersection and difference. A field boolean is bounded by the grid's spacing; use it for offsets, blends and inputs that must be resampled. See fields on the CSG page.

Preparing operands

Each condition below has a call that handles it, so a pipeline can run on files as they arrive.

  • Faces that disagree on their winding. An operand must say where its inside is. Orient it first with orient_faces_consistently.
  • An operand that overlaps itself. Repair it with outer_shell, or keep the overlap as a region with a within CSG graph, as above.
  • An open surface. List it in sheets and it cuts as a sheet. An open mesh not listed is read as a volume.
  • Coordinates far apart. Pass double coordinates, or ask for tf::exact::int64 in C++. Decisions then run on the int64 lattice. The API is the same.
  • CAD solids. B-rep and NURBS go in tessellated, as triangle meshes.
  • A long chain of booleans. The points created where two surfaces cross are written back in your input's type, float in, float out, and the next boolean reads them as fresh input. Build one CSG graph instead: one arrangement over every operand, every result read from it.

How fast

The same corpus runs in every language: 1,000 pairs of solid Thingi10K meshes, 200K to 1.5M polygons per pair, each pair randomly rotated and overlapped. Arrays in, arrays out. Apple M4 Max, 16 threads, trueform 0.9.8.

Languagetrueform medianCompared againstFastest onC++ (native)15.7 msMeshLib 5.5×, Manifold 7.5×, Solidean 9.2×, CGAL 31×, Geogram 105× (geomean)1000 / 1000Python18.0 msMeshLib 4.9×, Manifold 6.9× (geomean)1000 / 1000Browser (WebAssembly)21.9 msManifold WASM 12.6×, three-bvh-csg 41.2× (geomean)1000 / 1000
Language
C++up to 105× faster
Mesh Booleans benchmark, C++
Eight libraries on the Thingi10K corpus at 100K–1M triangles per operand. Fastest on every pair — 5.5× MeshLib, 31× CGAL, 105× Geogram at the geometric mean. Full benchmark

Repeated booleans on the same mesh go faster still. Hold the meshes and move the operand with a transformation. The first boolean builds the spatial tree and the topology; every later one reuses them.

dragon = tf.Mesh(*tf.read_stl("dragon.stl"))
tool = tf.Mesh(*tf.read_stl("tool.stl"))

for T in poses:                  # 4×4 transformations
    tool.transformation = T      # no copy, no rebuild
    (faces, points), labels, face_labels = tf.boolean_difference(dragon, tool)

The C++ tutorial runs ten booleans on two 500K-polygon dragons this way, with the tree and topology built once.

The protocols, corpus IDs and per-library notes are in the three comparisons:

Every trueform benchmark, by module and language, is on the trueform benchmarks page.

The call

Load a mesh, offset a copy, take the union. The copy shares the original's data and carries only a transformation. Intersection and difference are one word away.

#include <trueform/trueform.hpp>

auto dragon_buffer = tf::read_stl("stanford_dragon.stl");
auto dragon = dragon_buffer.polygons();

auto T = tf::make_transformation_from_translation(
    tf::make_vector(0.f, 1.f, 0.f));
auto translated = dragon | tf::tag(T);

// tf::boolean_op::intersection, tf::boolean_op::left_difference
auto [result_buffer, labels, face_labels] = tf::make_boolean(
    dragon, translated, tf::boolean_op::merge);

Every call returns three things. The first is the output mesh. The second, labels, says which operand each output face came from: 0 or 1. The third maps each output face to its source face, so colours, materials and other attributes follow the geometry. Ask for curves too, and the call also returns the polylines where the two surfaces cross. Install with pip install trueform, npm install @polydera/trueform, or CMake, conan or nuget for C++.

Where to go next

Booleans are one module of trueform. The same engine does spatial queries, topology, remeshing, isocontours, geodesics and more. See trueform.

trueform GitHub Documentation Try it live
Cite as
@article{polydera:mesh-booleans,
  title={Mesh booleans},
  author={Sajovic, {\v{Z}}iga, Polydera},
  year={2026},
  url={https://polydera.com/algorithms/mesh-booleans},
  organization={Polydera}
}