diff --git a/crates/pathops/src/edit.rs b/crates/pathops/src/edit.rs index 338900041..80f0deffd 100644 --- a/crates/pathops/src/edit.rs +++ b/crates/pathops/src/edit.rs @@ -476,7 +476,8 @@ fn with_handle(a: Anchor, out: bool, h: Point) -> Anchor { Anchor { p: a.p, h_in, h_out, kind } } -/// Ramer–Douglas–Peucker polyline reduction. +/// Ramer–Douglas–Peucker polyline reduction. Each point dropped is within `tol` of the line segment +/// that replaces it: a point beyond either end of the segment is measured to that end. fn rdp(pts: &[Point], tol: f64) -> Vec { if pts.len() < 3 { return pts.to_vec(); @@ -498,10 +499,10 @@ fn rdp(pts: &[Point], tol: f64) -> Vec { }; let (pa, pb) = (pts[a], pts[b]); let chord = pb - pa; - let len = chord.hypot(); + let len2 = chord.hypot2(); let mut best = (0.0, a); for (i, &p) in pts.iter().enumerate().take(b).skip(a + 1) { - let d = if len < 1e-12 { p.distance(pa) } else { (p - pa).cross(chord).abs() / len }; + let d = if len2 < 1e-24 { p.distance(pa) } else { p.distance(pa + chord * ((p - pa).dot(chord) / len2).clamp(0.0, 1.0)) }; if d > best.0 { best = (d, i); } diff --git a/crates/pathops/src/fit.rs b/crates/pathops/src/fit.rs index 4443e5b74..6b1f4eb0c 100644 --- a/crates/pathops/src/fit.rs +++ b/crates/pathops/src/fit.rs @@ -465,6 +465,23 @@ mod tests { } } + #[test] + fn a_path_that_turns_back_past_its_end_converted_to_straight_lines_keeps_its_tip() { + // A line to (100, 0), a smooth 0.2 pt U-turn and a line back to (50, 0.2): every point is + // within 0.4 pt of the line through the ends, but the tip is 50 pt past the end. + let k = 0.2 * 2.0 / 3.0; + let (a, b) = (Point::new(100.0, 0.0), Point::new(100.0, 0.2)); + let anchors = vec![ + Anchor::corner(Point::new(0.0, 0.0)), + Anchor::with_handles(a, a, Point::new(100.0 + k, 0.0)), + Anchor::with_handles(b, Point::new(100.0 + k, 0.2), b), + Anchor::corner(Point::new(50.0, 0.2)), + ]; + let path = PathData::single(SubPath::new(anchors, false)); + let out = crate::simplify_with(&path, &crate::SimplifyOptions { tolerance: 1.0, straight_lines: true, ..Default::default() }); + assert_within(&cubics(&path), &cubics(&out), 1.0, "a U-turn past the end"); + } + #[test] fn long_and_short_segments_stay_within_the_tolerance() { for (r, tol) in [(50_000.0, 2.0), (50_000.0, 1.0 / 32.0), (8_000.0, 0.01), (500.0, 0.5)] {