Skip to content

roof(): voronoi method roofs unions of touching pieces - #268

Merged
revarbat merged 1 commit into
mainfrom
fix/voronoi-roof-touching
Oct 8, 2026
Merged

revarbat merged 1 commit into
mainfrom
fix/voronoi-roof-touching

Conversation

@revarbat

@revarbat revarbat commented Oct 8, 2026

Copy link
Copy Markdown
Member

The default roof() (method="voronoi") gave no roof (and on some inputs allocated without bound) for unions of pieces that touch along edges. On a seeded 6,000-input corpus it failed on 384 before, 16 of them runaways (one reached 36 GB); it now fails on none.

Root causes

  1. Union outline breaks Boost.Polygon's input rule. Clipper returns edge-sharing pieces as separate outlines a hairline apart; after snapping, their segments overlap or cross. Boost's Voronoi builder needs segments that meet only at endpoints: overlaps gave a wrong diagram (no roof), crossings never terminated (the runaway). Fix: voronoi now runs the straight method's existing weldContours (weld, re-union, drop straight corners and spikes) first; an outline the weld leaves unchanged except for loop start/order is used as is, so its roof is identical.
  2. Boost misbuilds some clean outlines (near-equidistant sites after snapping). Fix: if the mesh is not a valid non-empty solid, retry on grids 2^28 down to 2^25. No previously working input needed a retry.
  3. Far unused vertices loosened Manifold's tolerance, so its cleanup collapsed whole roofs or detail (old volumes off by up to 25% vs OpenSCAD). Fix: RoofMesh::build drops unused vertices beyond 16× the mesh extent.

The weld's corner-vs-edge scan now only checks corners in each edge's x range.

Verification

  • Corpus (one process per input under a 20 s / 1 GB watchdog): 0 empty/invalid/runaway outputs across triangles, rectangles, rotated, stars, convex, holes, blobs, mixed unions.
  • vs OpenSCAD 2026.02.01: closer or tied almost everywhere it gives a roof; 18 cases where OpenSCAD roofs touching pieces separately now differ by 1–14%, and a numerical integral of distance-to-boundary agrees with ours.
  • Regressions: previously working inputs keep volume/height/bbox except where the weld changed the outline or the tolerance collapse is fixed; method="straight" is byte-identical on 1,600 inputs.
  • Timing: +2–10% on 1000/5000-vertex inputs (the weld's union pass, which straight already pays).
  • 5 new gtests in tests/test_extrude_roof.cpp, all failing on the old code. Note: the runaway test allocates without bound on the old code, so beware when bisecting across this change. Full suite: 1468 passed.

🤖 Generated with Claude Code

The default roof() gave nothing -- or ran away allocating -- for many
unions of triangles/rectangles that touch along edges. Three causes:

- Clipper returns pieces sharing an edge as outlines a hairline apart,
  which on the grid overlap or cross. Boost's Voronoi builder needs
  segments that meet only at their ends: the diagram came out wrong (no
  roof), or the builder never finished, growing without bound (16 of
  6,000 corpus inputs; tens of GB). The outline is now welded and
  re-unioned first, as method="straight" already does, keeping the
  original outline (and so the identical roof) when welding changes
  nothing.
- On a clean outline Boost still occasionally builds a wrong diagram
  where four sites are all but equidistant after rounding to the grid
  (a cell with no finite edges). The roof is retried on coarser grids
  (2^28..2^25) when the mesh does not come out a valid, non-empty solid.
- An unused vertex -- the far end of a Voronoi edge outside the outline,
  up to 10^12 away -- stretched the bounding box Manifold sizes its
  tolerance by; its cleanup then collapsed the roof to nothing, or
  quietly dropped detail (old volumes off by up to 25% vs OpenSCAD).
  Such far vertices are now left out of the mesh.

The weld's corner-vs-edge scan now only visits corners in each edge's
x range, so the extra pass costs 2-10% of roof time at 1000-5000
corners. method="straight" output is byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@revarbat
revarbat merged commit aa060a0 into main Oct 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant