Simplify: Convert to Straight Lines keeps the tip of a path that turns back past its end

`rdp` measured each sample to the infinite line through the ends of the
line that would replace it. A sample beyond either end could be far from
that line segment and still be dropped: a line to (100, 0), a 0.2 pt
U-turn and a line back to (50, 0.2) became one line at a tolerance of
1 pt, leaving the tip 50 pt away. `rdp` now measures to the segment, so a
sample beyond an end is measured to that end, and a zero-length chord
measures to its point as before.

The test `a_path_that_turns_back_past_its_end_converted_to_straight_lines_keeps_its_tip`
covers that path.
This commit is contained in:
dotCooCoo
2026-11-08 11:22:30 +07:00
committed by bflatastic
parent 489552c14b
commit bc19dd64db
2 changed files with 21 additions and 3 deletions
+4 -3
View File
@@ -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<Point> {
if pts.len() < 3 {
return pts.to_vec();
@@ -498,10 +499,10 @@ fn rdp(pts: &[Point], tol: f64) -> Vec<Point> {
};
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);
}
+17
View File
@@ -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)] {