Plan the filling of several coplanar holes at once - #6560
Draft
Grantim wants to merge 2 commits into
Draft
Conversation
fillContours2DPlan gets an overload taking all hole edges lying in one plane: they are triangulated together as fillContours2D does, and the patch is converted into a single HoleFillPlan whose new edges may connect different holes, like the ring between two nested borders. The single-hole overload now shares that conversion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The position ring and one code per patch edge carry all the peeling state: the boundary-to-mesh map, the planned mask and the boundary mask collapse into that code, and the chord count follows from 3F = n + 2C instead of a scan over every patch edge. The produced plans are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fillContours2DPlanplans one hole at a time, so a caller holding several holes that lie in one plane can only plan and execute them one by one. That cannot express a filling whose triangles connect two different holes — the ring between two nested borders being the obvious case — and it is exactly what a caller filling the cut faces of a boolean needs, since one original face can be left with several holes at once.This adds an overload taking all the hole edges of one plane. They are tracked into loops, triangulated together by the same mesh-space
triangulateDisjointContoursthe single-hole path uses since #6555 (one sweep for the whole group, sharing the Newell normal and the hairline-edge guard), and the resulting patch is converted into a singleHoleFillPlan. The single-hole overload becomes a wrapper over it, so there is one patch-to-plan conversion instead of two.Why the conversion had to be generalized
executeHoleFillPlanresolves a negative edge code to the forward direction of an earlier plan edge (EdgeId( plan.items[-(code+1)].edgeCode1 )); nothing can name itssym. A plan therefore cannot just replay the order in which the sweep spliced its own edges, because the sweep anchors on syms (holeLoop[prev] = newE.sym()).The existing conversion sidesteps that by peeling ears off the boundary ring and addressing positions by patch vertex id (
i1 = int( pTp.dest( np[i0] ) )), which holds only while vertex ids coincide with positions around a single loop. With several loops in one patch, and vertices shared between them, it no longer does.The generalized conversion keeps the same ear discipline but tracks the polygon explicitly: for each boundary position, the patch edge currently sitting there, its plan code, and the ring successor. Rings are then plain successor cycles, so several coexist and a vertex shared by two loops is unambiguous. Each ear records its closing edge as a chord between the polygon edges at its two ends — precisely what
makeNewEdgesplices right after them — and every later chord at the same vertex lands closer to its anchor, which reproduces the patch's angular order.The peeling state is two buffers (second commit): one entry per boundary position holding its current patch edge and its ring successor, and one code per patch edge saying how the plan can name it — an absolute mesh edge on the borders, a plan edge once a chord for it is planned, nothing while it cannot be named yet. That single code subsumes what would otherwise be a boundary map plus two masks, and the number of chords to plan follows from
3F = n + 2Crather than a scan over every patch edge.Two rings are joined by a bridge chord. A bridge is expressible only when it is the first new edge after the current polygon edge at both of its ends, and the face left of its reverse must be clipped before it, so that the bridge's
symends up between the anchor and that clip. Whatever the encoding cannot express is refused rather than mis-encoded.Holes that need no new edges
A triangular hole is filled by one face and no chord, and
executeHoleFillPlanonly creates faces adjacent to edges the plan creates, so such a hole cannot ride inside a shared plan. They are reported throughoutSingleFaceHolesfor the caller to fill one by one; passing no output makes such a group an error rather than a silently unfilled hole.Verification
MRMesh.fillContours2DPlanMultipleHolescovers the case the single-hole overload cannot express: trimming a hollow sphere leaves an outer and an inner coplanar border, and one plan executed from a single edge closes both, reuses every border vertex in place, adds exactlynumTrisfaces, and produces no degenerate ones.FillHoleItem. So for one hole the shared conversion reproduces the old one exactly; only the multi-hole entry point is new behaviour.The same corpus also pins the second commit: the hash is unchanged by it, so shrinking the peeling state is not a behaviour change.
🤖 Generated with Claude Code