mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 18:54:57 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)] {
|
||||
|
||||
Reference in New Issue
Block a user