M4: round_corners: each contour's own size sets its weld tolerance (#2222)

The tolerance that drops the sweep's slivers came from the whole path's bounding box, so a
contour a million units wide welded away the real half-unit edges of a separate small contour.
Each closed subpath now sets its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
This commit is contained in:
Trevor BrownandClaude Opus 5.5 committed 2026-11-10 15:39:09 -05:00
1 parent ec105d0f56
commit 4c91f9084b
2 files changed
+22 -2

No files matched your search

+4 -2
View File
@@ -114,10 +114,12 @@ pub fn round_corners(path: &BezPath, radius: f64, smoothing: f64) -> BezPath {
return path.clone();
}
let smoothing = if smoothing.is_finite() { smoothing.clamp(0.0, 1.0) } else { 0.0 };
let bb = path.bounding_box();
let weld = (bb.width().max(bb.height()) * 1e-6).max(1e-9);
let mut out = BezPath::new();
for sub in figcraft_geom::split_subpaths(path) {
// Each contour's own size sets its cleanup tolerance, so a large contour can't weld away
// the real short edges of a small island or hole beside it.
let bb = sub.bounding_box();
let weld = (bb.width().max(bb.height()) * 1e-6).max(1e-9);
let closed = matches!(sub.elements().last(), Some(PathEl::ClosePath));
match closed.then(|| round_subpath(&sub, radius, smoothing, weld)).flatten() {
Some(p) => out.extend(p.elements().iter().copied()),
+18
View File
@@ -236,3 +236,21 @@ fn junk_never_panics() {
zig.close_path();
assert!(all_finite(&round_corners(&zig, 2.0, 0.0)));
}
/// The cleanup tolerance is each contour's own: a contour a million units wide doesn't weld away
/// the half-unit notch of a separate small contour in the same path.
#[test]
fn a_large_contour_does_not_weld_a_small_ones_short_edges() {
let mut p = rect(0.0, 0.0, 1e6, 1e6);
let x = 2e6;
for (i, (dx, y)) in [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (5.5, 10.0), (5.5, 9.5), (5.0, 9.5), (5.0, 10.0), (0.0, 10.0)].into_iter().enumerate()
{
if i == 0 { p.move_to((x + dx, y)) } else { p.line_to((x + dx, y)) }
}
p.close_path();
let out = round_corners(&p, 0.001, 0.0);
let reaches = |q: Point| {
out.elements().iter().any(|el| matches!(el, PathEl::MoveTo(e) | PathEl::LineTo(e) | PathEl::CurveTo(_, _, e) if (*e - q).hypot() < 0.01))
};
assert!(reaches(pt(x + 5.0, 9.5)) && reaches(pt(x + 5.5, 9.5)), "the notch's floor is kept");
}