Shape Builder: a merge walks the arrangement once per batch of faces

A merge found the contours of the faces it unites with one contour pass
over the whole arrangement for each coverage among them, and the search
for slivers next to them did the same for each neighboring coverage.
Each in-place remnant of a merge is such a merge too, so a click on
many overlapping shapes made one pass over the whole arrangement for
each coverage, for the merged faces and again for each remnant. Both
now use the batching that Divide's regions use: one pass for each batch
of coverages whose faces share no vertex. The batching looks a face's
coverage up by a hash of the inputs that cover it, computed as it walks
them. The search for slivers looks up the merged faces' coverages the
same way.

Merging a frame no longer takes in an island of the same coverage in
its hole: each face is its outer contour and the holes directly in it,
as the regions are.
This commit is contained in:
dotCooCoo
2026-10-29 10:01:22 +07:00
committed by bflatastic
parent 8b4ebf50be
commit 32601a6bc3
2 changed files with 190 additions and 45 deletions
+9 -1
View File
@@ -14,6 +14,12 @@ use vectorcraft_geom::{Anchor, FillRule, PathData, SubPath};
use crate::PathOpsError;
use crate::fit::{end_tangent, fit_single, is_straight, sample, start_tangent};
#[cfg(test)]
thread_local! {
/// The contour passes made over an arrangement on this thread, each of which walks all of it.
pub(crate) static CONTOUR_PASSES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
/// Default curve-refit tolerance (document points) used to merge split curve pieces back together.
/// Similar in spirit to Illustrator's Pathfinder "Precision" option (0.028 pt).
pub const DEFAULT_PRECISION: f64 = 0.01;
@@ -314,11 +320,13 @@ impl Arrangement {
/// [`Arrangement::contours`] with the test on the windings themselves (see [`Self::covered`]).
pub(crate) fn contours_where(&self, pred: impl Fn(&Multi) -> bool) -> Contours {
#[cfg(test)]
CONTOUR_PASSES.with(|n| n.set(n.get() + 1));
self.top.contours(pred)
}
pub(crate) fn contours(&self, pred: impl Fn(&[bool]) -> bool) -> Contours {
self.top.contours(|w| pred(&self.mask(w)))
self.contours_where(|w| pred(&self.mask(w)))
}
/// All distinct non-empty coverage masks that occur next to some edge, sorted.