diff --git a/crates/geom/src/hit.rs b/crates/geom/src/hit.rs index 98242d918..35eddd35b 100644 --- a/crates/geom/src/hit.rs +++ b/crates/geom/src/hit.rs @@ -28,9 +28,11 @@ pub fn polygon_covers_rect(path: &BezPath, r: Rect, tol: f64) -> bool { // Convex: every turn goes the same way (straight runs allowed). let n = pts.len(); let mut sign = 0.0f64; + let mut turning = 0.0f64; for i in 0..n { let (a, b, c) = (pts[i], pts[(i + 1) % n], pts[(i + 2) % n]); let cross = (b - a).cross(c - b); + turning += cross.atan2((b - a).dot(c - b)); if cross.abs() <= 1e-9 { continue; } @@ -40,6 +42,10 @@ pub fn polygon_covers_rect(path: &BezPath, r: Rect, tol: f64) -> bool { return false; } } + // A simple convex polygon turns through exactly one full revolution; a star winds more. + if (turning.abs() - std::f64::consts::TAU).abs() > 1e-6 { + return false; + } let inner = r.inflate(-tol, -tol); sign != 0.0 && [Point::new(inner.x0, inner.y0), Point::new(inner.x1, inner.y0), Point::new(inner.x1, inner.y1), Point::new(inner.x0, inner.y1)] @@ -244,4 +250,20 @@ mod regress_tests { 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}"); } + + #[test] + fn polygon_covers_rect_rejects_self_intersecting_star() { + let mut b = BezPath::new(); + for k in 0..5 { + let a = (-90.0 + 144.0 * k as f64).to_radians(); + let p = Point::new(100.0 * a.cos(), 100.0 * a.sin()); + if k == 0 { + b.move_to(p); + } else { + b.line_to(p); + } + } + b.close_path(); + assert!(!polygon_covers_rect(&b, Rect::new(-55.0, -20.0, 55.0, 78.0), 0.01)); + } }