Benchmarking Mesh Booleans: Iterated Dome Carve

A workpiece carved into a half-dome by up to 200,000 iterated capsule subtractions. Simple steps, but within the 1-hour budget only 1 of 19 implementations still finishes at 100k.

This is the fourth case in our public mesh boolean benchmark. The first one was a friendly iterated CSG chain, the second a long terrain carve with exact coplanar seams, and the third a grid-aligned fill-and-empty whose target was the empty set. This one pushes on a different axis: instead of a single fixed workload, we scale the same carve up and up and watch who keeps up.

The case is a kind of simplified milling simulation. We take a solid workpiece and subtract capsules from it, one at a time, following a path until a half-dome is carved out of the block. There are no sweeps, so no single step is exotic: each subtraction is one convex-ish tool against the running solid, and because nothing sweeps, no coplanar faces are forced on the classifier. Individually the steps are about as friendly as a boolean gets. The difficulty is entirely in the count (and the fact that you have to eat your own result as new input). We run the exact same carve at 10, 100, 250, 1000, 5000, 10000, 100000, and 200000 steps (each finer version approximates the same ideal dome with more, smaller capsules), give every runner a budget of about an hour, and see how far it gets while staying robust.

The video is the 1000-step carve run by Solidean, from the intact block down to the finished half-dome.

The field thins fast. At 10 cuts, 12 of the 19 runners reach the canonical dome. At 100 it is eight, at 250 seven, at 1000 four, at 5000 three, at 10000 two, and by 100000 only Solidean still finishes inside the hour. At 200000 even Solidean runs past the hour.

The runner programs that wrap each implementation are open source: github.com/Open-Boolean-Benchmark/boolean-benchmark-runners

The raw input meshes and per-operation data for this case, so you can reproduce it yourself, are at github.com/solidean/bench-blog-data. To keep the download reasonable we uploaded the versions up to 5000 steps (zipped). Contact us if you want a link to the higher-count ones.

Cost per cut

It is instructive to plot the time per cut vs. the number of steps. The workpiece complexity grows and grows but the cut itself is quite localized. Methods that want to stay fast thus have to make sure they spend the bulk of their time near the actual cut.

Cost per cut versus step count, log-log. Most methods grow linearly or superlinearly as the running mesh grows; Solidean stays flat-ish a lot longer. The 3600s per-run timeout is drawn as a slanted line.

The x-axis is the step count, the y-axis is the time spent per cut, both logarithmic, and the 3600-second per-run budget is drawn as a slanted line. For most methods the cost per cut climbs at least linearly as the run goes on, because the running mesh keeps growing and each new subtraction has more geometry to intersect, classify, and re-triangulate. It means they grow at least quadratically as we increase the step count: not just more steps but also more time per step because one of the inputs keeps growing.

This makes it difficult to "just throw more hardware at it".

Solidean doesn't stay completely "flat" but you can see that it definitely grows sub-linearly. This is an important property, which lets us carry the same carve two or three orders of magnitude further than anything else here. (We truncate runners that become grossly incorrect, because a method that silently drops most of the geometry also gets cheap, and its timings stop meaning anything.)

The wireframes

It's instructive to look at the wireframes (of the exported meshes) as well.

Solidean

One caveat on this video: it is the topology of the exported intermediate mesh, not Solidean's true internal wireframe. Internally Solidean carries convex polygons and only triangulates when it exports, so the triangulated fans you see here are an artifact of the export, not how the kernel stores the solid. Here are two stills of that exported topology around the 500-step mark, an overview and a close-up:

Exported Solidean topology around 500 steps, overview.

Exported Solidean topology around 500 steps, close-up of the carved region.

Trueform 0.7.0

As another example, this is Trueform 0.7.0 in wireframe, 1000 steps with float input. This illustrates nicely how challenging the topology in such cases can become. The top face starts as a quad but because we're carving away lots of small capsules, it creates some valence 100+ vertices. Solidean's triangulation looks more irregular but it actually helps keeping the cost down. We can create cleaned up triangulation on export, but during iteration, it would just be in the way.

In this example Trueform starts to fail around the 70% mark. Which is actually kind of impressive, because as far as we can tell, Trueform is the only algorithm in our lineup that works internally with 32-bit floats and still gets this far on a carve this long. It is also under active development. We tested the double-precision build (slower, but it did not fail at this step count) and current main (still fails here), and we are already eager to test their next version, which is promised to be both more stable and more performant. None of this is meant to dunk on it; getting this close in 32-bit float is a real achievement.

Results

Each runner executes the same carve at a given step count and reports per-operation time. "Time" is the sum of operation time and import time (input mesh to internal representation), the same measure we used in the earlier posts. Each step count is a distinct, finer target with its own reference area and volume.

The tables below are a survivor bracket. Each one lists only the runners that were still canonical and inside the 1-hour budget at the previous, coarser step count, and shows how they did at this one. Read straight down the series and you can see exactly who drops out and when.

The "Rel." column is runtime relative to Solidean, and we only report it for runs that reached the canonical result. Rows marked with * exceeded the 1-hour budget (they timed out mid-carve, or, for Solidean at 200000 steps, ran past the hour to completion), so their raw time is not comparable to runs that finished inside the budget. "Peak mem" is the peak resident memory of the run.

100 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.173 ms1x46 MiB64106.5450274099.8661🟢 canonical
Trueform 0.7.0673 ms9x23 MiB64106.5450274099.8661🟢 canonical
MeshLib 3.1.1.211957 ms13x37 MiB64106.5450274099.8661🟢 canonical
Manifold 3.4.01.1 s15x21 MiB64106.5450274099.8661🟢 canonical
CGAL (corefine) 6.1.13.3 s45x36 MiB64106.5450274099.8661🟢 canonical
Mesh Arrangements 2.6.047 s644x203 MiB64106.5450274099.8661🟢 canonical
CGAL (nef) 6.1.1262 s3576x270 MiB64106.5450274099.8661🟢 canonical
CGAL (nef, regularized) 6.1.1302 s4123x338 MiB64106.5450274099.8661🟢 canonical
Blender (exact) 5.1747 ms223 MiB0.00000.0000🔴 empty result
Blender (fast) 5.13.2 s372 MiB66778.1624259137.2519🔴 wrong
Geogram 1.10.054 MiB🔴 no result
Geogram 1.9.885 MiB🔴 no result

250 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.1247 ms1x90 MiB25505.1990197234.4605🟢 canonical
MeshLib 3.1.1.2114.6 s19x81 MiB25505.1990197234.4605🟢 canonical
Manifold 3.4.05.5 s22x32 MiB25505.1990197234.4605🟢 canonical
CGAL (corefine) 6.1.113.6 s55x45 MiB25505.1990197234.4605🟢 canonical
Mesh Arrangements 2.6.0223 s902x291 MiB25505.1990197234.4605🟢 canonical
CGAL (nef) 6.1.1~25 min6137x495 MiB25505.1990197234.4605🟢 canonical
CGAL (nef, regularized) 6.1.1~30 min7297x626 MiB25505.1990197234.4605🟢 canonical
Trueform 0.7.01.9 s35 MiB26290.3720190514.2855🔴 wrong

1000 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.11.3 s1x226 MiB23790.0451192212.0330🟢 canonical
Manifold 3.4.034 s27x55 MiB23790.0451192212.0330🟢 canonical
CGAL (corefine) 6.1.1157 s125x107 MiB23790.0451192212.0330🟢 canonical
MeshLib 3.1.1.211219 s175x774 MiB23790.0451192212.0330🟢 canonical
Mesh Arrangements 2.6.0~60 min*1.4 GiB🔴 timeout (op 858)
CGAL (nef) 6.1.1~60 min*509 MiB🔴 timeout (op 624)
CGAL (nef, regularized) 6.1.1~60 min*578 MiB🔴 timeout (op 566)

5000 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.112.4 s1x819 MiB23041.8864189335.5688🟢 canonical
Manifold 3.4.0442 s36x138 MiB23041.8864189335.5688🟢 canonical
CGAL (corefine) 6.1.1~44 min213x445 MiB23041.8864189335.5688🟢 canonical
MeshLib 3.1.1.211~60 min*14.5 GiB🔴 timeout (op 4855)

10000 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.126 s1x1.1 GiB22924.6567188862.0125🟢 canonical
Manifold 3.4.0~20 min46x203 MiB22924.6567188862.0125🟢 canonical
CGAL (corefine) 6.1.1~60 min*432 MiB🔴 timeout (op 6033)

100000 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.1~23 min1x7.9 GiB22854.9905188246.8921🟢 canonical
Manifold 3.4.0~60 min*452 MiB🔴 timeout (op 17257)

200000 steps

RunnerTimeRel.Peak memAreaVolumeVerdict
Solidean 2026.1~97 min*1x16.2 GiB22852.6263188189.0818🔴 past 1h

Well, seems like we have our next goal: Ensure that Solidean stays flat even for extremely long chains so it can finish the 200k steps within an hour. Also, we have quite a bit of peak memory right now, something to optimize in the future as well.

What makes this hard

The individual steps here are easy. So the difficulty is not any single operation, it is what happens when you do a lot of them in a row.

Friendly steps, brutal accumulation

Because there are no sweeps, no step forces coplanar faces or partial overlaps on the classifier, which was the extra stressor in the terrain case. That makes this close to a pure test of iteration and scale. The catch is that iteration has no self-healing. Each cut takes the previous result as its input, so a single face that gets misclassified early does not stay a local glitch: it becomes a hole or a flipped patch that every later cut carves into, and the error grows instead of washing out. The wrong results in the raw data are exactly this failure mode. QuickCSG classifies per triangle, and one bad decision seeds holes and stray geometry that end up making the solid unusable. Carve keeps the right volume but leaves doubled-up, double-sided faces behind, so its surface area comes out too high. Neither of those is recoverable later in the chain, because the geometry a fix would need is already gone.

The cost of a cut must not grow too much

The other half of the case is performance, and the cost-per-cut graph demonstrates this beautifully. As the carve proceeds, the running mesh gets larger and the emerging dome develops a big, finely tessellated surface. If your per-operation cost scales with the size of the running mesh, then the later cuts get more and more expensive and the total cost grows faster than linearly in the number of steps. Most methods are actually at least linear in both inputs, which makes them at least quadratic on this test.

Why Solidean stays flat

Staying flat is an explicit design goal, and it is one of the main things that separates Solidean from the original EMBER work it grew out of. In an iterated scenario like this one, we aim for the cost of a step to grow sub-linearly, rather than track the size of the running mesh.

We get there with a more involved internal data structure than a plain triangle mesh. Internally Solidean keeps convex polygons and only triangulates on export, and the exact arithmetic underneath is one important ingredient: every coordinate has an exact representation with a bounded bit width, so a face does not accumulate ever-longer numbers as it is cut again and again, and the per-cut work does not creep up just from accumulated history. But exact arithmetic on its own is not enough. We also organize the geometry internally so that each new cut can home in on the small region where it actually interacts with the running solid as fast as possible, instead of paying for the whole mesh every time.

Toward a public benchmark

This is case four. The earlier posts covered an easy chain, deep iteration with coplanar seams, and a grid-aligned fill-and-empty; this one adds the scaling axis, the same simple workload pushed from ten cuts to two hundred thousand. More cases are coming, with different geometries, operation types, and stress patterns.

The runner infrastructure and build scripts are open source under the Open Boolean Benchmark org, at github.com/Open-Boolean-Benchmark/boolean-benchmark-runners. Each runner is a small standalone program that reads a JSON task description, calls the library, and writes back timings and result meshes. Adding a library means writing one wrapper.

Notes

(This is "boring data" meant for reproducibility)

The case is a single solid workpiece carved into a half-dome by iterated capsule subtractions, no sweeps. The running result of each operation is the input to the next, so the chain cannot be parallelized and errors accumulate. The same carve is run at 10, 100, 250, 1000, 5000, 10000, 100000, and 200000 steps; a finer version approximates the same ideal dome with more, smaller capsules, so the volume converges as the step count grows.

The canonical area and volume at each step count are the values the exact-construction methods agree on (not Solidean by decree). Through 250 steps that is Solidean, CGAL corefine, and both CGAL Nef variants; CGAL corefine still agrees through 5000 steps and Manifold through 10000. At larger counts the exact cross-checks no longer finish within the hour, so those targets rest on the smaller verified scales, the converging volume, and Solidean's exact arithmetic. Correctness is checked to a relative tolerance of 1e-4.

The reference values, per step count (area, volume):

  • 100: 64106.5450, 274099.8661
  • 250: 25505.1990, 197234.4605
  • 1000: 23790.0451, 192212.0330
  • 5000: 23041.8864, 189335.5688
  • 10000: 22924.6567, 188862.0125
  • 100000: 22854.9905, 188246.8921
  • 200000: 22852.6263, 188189.0818

The Solidean baseline (2026.1, operation plus import) is 247 ms at 250 steps, 1.3 s at 1000, 12.4 s at 5000, 26 s at 10000, about 23 minutes at 100000, and about 97 minutes at 200000 (over the 1-hour budget). As before, "Time" covers only the import (indexed triangle mesh to internal structure) and the boolean operations, no file IO. The measurement definition, system specs, library versions, and licensing are unchanged from the first benchmark post; see its Notes section for the full details.

The 1-hour figure is the per-run timeout we set for this case. Runs that hit it are marked as timeouts at the operation they reached; runs that returned finished-but-wrong geometry are in the raw data but omitted from the survivor tables above.

This is a single test case with specific library versions and default configurations. Results may differ for other geometries, operation types, iteration counts, or library settings.