diff --git a/crates/geom/src/hit.rs b/crates/geom/src/hit.rs index 944ded3e..6115a96c 100644 --- a/crates/geom/src/hit.rs +++ b/crates/geom/src/hit.rs @@ -15,7 +15,7 @@ pub fn fill_contains(path: &BezPath, rule: FillRule, p: Point) -> bool { /// Distance from `p` to the nearest point on the path outline. pub fn distance_to_outline(path: &BezPath, p: Point) -> f64 { - path.segments().map(|s| s.nearest(p, 1e-6).distance_sq).fold(f64::INFINITY, f64::min).sqrt() + path.segments().map(|s| s.nearest(p, 1e-9).distance_sq).fold(f64::INFINITY, f64::min).sqrt() } /// Is `p` within `tol` of the path's stroke of width `width`? @@ -91,3 +91,26 @@ mod tests { assert!(intersects_rect(&l, Rect::new(4.0, 0.0, 6.0, 10.0))); } } + +#[cfg(test)] +mod regress_tests { + use super::*; + use kurbo::{CubicBez, ParamCurve}; + + /// A cubic whose handles sit on its anchors on one side (cusp-like): points on the curve are + /// at distance ~0 (proptest regression). + #[test] + fn nearest_on_degenerate_cubic() { + let c = CubicBez::new( + (131.69052599341336, 172.64518298885875), + (104.00879084385166, 172.64518298885875), + (145.71215277397286, 141.82358989609716), + (131.4188160942415, 162.38678549385997), + ); + let mut bp = BezPath::new(); + bp.move_to(c.p0); + bp.curve_to(c.p1, c.p2, c.p3); + let worst = (0..=200).map(|i| distance_to_outline(&bp, c.eval(i as f64 / 200.0))).fold(0.0, f64::max); + assert!(worst < 1e-6, "{worst}"); + } +}