Skip to content

Fill planar holes in mesh space - #6739

Merged
Grantim merged 4 commits into
masterfrom
fill-contours-2d-mesh-space
Sep 1, 2026
Merged

Fill planar holes in mesh space#6739
Grantim merged 4 commits into
masterfrom
fill-contours-2d-mesh-space

Conversation

@Grantim

@Grantim Grantim commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What

Two commits:

  1. fillContours2D fills holes in mesh space. The hole loops go through triangulateDisjointContours( mesh, loops, normal, nullptr, &patchToMesh ) and the patch is stitched through the patch→mesh edge map, aligned to the input loops by identity. Deleted: the best-fit-plane projection round-trip (projectHoles/fillProjected), the HolesVertIds-driven vertex merge, and the border point snap-back — patch boundary vertices now carry the exact mesh coordinates by construction. validateAndFixPatch and the addMeshPart stitch are unchanged.
  2. SweepLineQueue: insert pinch-ring edges geometrically in the sweep frame. The copied ring order comes from the input surface, whose facing is independent of the patch normal the sweep frame follows; at a vertex the boundary visits twice the inherited order could pair the wrong sectors and the fill came out folded (correct net winding, faces of both senses — e.g. a two-lobe "bowtie" hole filled as the whole quadrilateral plus two reversed triangles). Both splices of a new patch edge are now deferred until its endpoints are known and anchored with findClosestToFront in projected coordinates (new overload taking the reference-ray base explicitly) — the same rule mergeSamePoints_ uses, so pinched fills match the contour-rebuild path exactly. This fixes the mesh-space triangulation itself, not only fillContours2D.

Verification

  • Differential test over 2770 planar-hole meshes (random simple polygons, quantized degenerate stars, sphere trims, annulus meshes with two loops filled jointly) vs the old projection path: every in-contract case agrees on all invariants (faces, verts, holes closed, multiple edges, filled area, orientation); the 2160 polygon cases produce byte-identical triangulations, including 27 pinched (self-touching) boundaries the pre-fix copy folded.
  • Six cases that crash the old fillContours2D with heap corruption (quantized self-touching boundaries; reproduced on clean master) return a clean error instead.
  • Pinch fix exercised across the configuration space: loop normal on all 6 signed axes, in-plane rotations 0–90°, tilted planes off every axis, pinch vertex displaced ±1e-6..±0.05, both surface orientations — all clean, patch always faces the passed normal.
  • End-to-end boolean digest (rotating spheres, 200 frames): byte-identical to master.
  • MRTest suite passes, with new pinched-boundary tests at both levels (fillContours2D and triangulateDisjointContours).
  • Perf (alternating-process A/B vs master, medians over 28 rounds, decimate control flat): 956 small planar holes in one call −6.1%, one big trim hole −7.9%, two-loop annulus −6.5%.

Follow-ups (separate PRs)

  1. Delete the then-unused machinery: outBoundaries mesh-overload parameter and its rebuild branch, 2D holeVertsIds/outBoundaries parameters, HolesVertIds types and findHoleVertIdsByHoleEdges, the identity-guard in mergeSamePoints_.
  2. cutMesh joint per-old-face hole fill on top.

🤖 Generated with Claude Code

Grantim and others added 3 commits August 14, 2026 15:06
…o a plane

The hole loops are triangulated in the mesh's own coordinates and the patch is
stitched through the patch->mesh edge map, so the projection round-trip, the
coordinate-based vertex merging and the border point fixup all go away. Loops
sharing a vertex are now merged by identity rather than by position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The copied ring order comes from the input surface, whose facing is
independent of the patch normal the sweep frame follows, so at a vertex the
boundary visits twice the inherited order could pair the wrong sectors and
the fill came out folded (correct net winding, faces of both senses). Now
both splices of a new edge are deferred until its endpoints are known and
the insertion position is found with findClosestToFront in projected
coordinates - the same rule mergeSamePoints_ uses, so pinched fills match
the contour-rebuild path exactly. Tests for both sweep-frame orientations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
return 0;
}

int findClosestToFront( const MeshTopology& tp, const SweepLinePredicates& predicates,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add descriptions to new functions

Review follow-up, comments only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Grantim
Grantim merged commit f8083fa into master Sep 1, 2026
53 checks passed
@Grantim
Grantim deleted the fill-contours-2d-mesh-space branch September 1, 2026 11:45
Fedr added a commit that referenced this pull request Sep 1, 2026
Merges master (bool-returning addPartByMask of #6744, mesh-space fill and
pinched tests of #6739) and builds on both:

MeshTopology::addPartByMask now simulates the future edge ring at every target
vertex the contours visit more than once, before anything is modified: walking
from each stitched edge through the patch and back, all stitched edges of the
vertex must fall in one cycle, otherwise the vertex would be left with several
disjoint edge rings and the call returns false keeping the target intact.
Inputs whose ring composition it cannot simulate (a gap in the from-ring fixed
by the near-stitch seam code) keep the old behavior.

fillContours2D on a pinched hole loop now fails cleanly instead of producing an
invalid mesh: the mirror patch is pinched at the same vertex and pairs each
lobe with itself, which no vertex duplication on the patch side can fix; the
plan-based fillContours2DPlan remains the way to fill such loops (disk-like).

Both formerly DISABLED_ tests are enabled and rewritten to the new behavior:
fillContours2DPinchedHoleValidity expects a clean failure with the mesh intact,
AddPartByMaskAndStitchPinched expects the pinched stitch rejected and also
checks that a fan patch reaching the pinch through two distinct vertices is
still accepted (5 verts, 6 faces, closed, valid). Master's
fillContours2DPinchedBoundary is updated to the same clean-failure expectation;
its triangulation-level sibling keeps checking the patch itself.
Grantim added a commit that referenced this pull request Sep 1, 2026
Since fillContours2D moved to mesh space (#6739) nothing constructs a
HolesVertIds or asks for outBoundaries, so the identity-merge and
rebuild-from-contours paths they fed are unreachable. Dropping them
leaves mergeSamePoints_ a pure positional weld and reduces the mesh
overload of triangulateDisjointContours to its single copy-the-
sub-topology path.

Public API break: TriangulationParameters::holeVertsIds and the
holeVertsIds/outBoundaries parameters of triangulateDisjointContours
are gone.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants