mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 22:21:59 +00:00
Simplify: the join at the start of a smoothed loop turns by at most 0.5°
A smoothed loop with no corner is refitted along one direction across its start. The first and the last fitted curve were each compared with that direction, so when both became lines, the two lines could turn by up to 1° where they meet at the loop's start. `refit_part` now compares the last curve of such a loop with the direction the loop's first segment leaves along. When the line would turn by more than 0.5° from it, the last piece stays a flat curve. Test: `simplify_keeps_the_join_at_the_start_of_a_smoothed_loop_smooth` (`crates/pathops/tests/it/ops.rs`).
This commit is contained in:
@@ -329,12 +329,18 @@ fn refit_part(part: &[(&Seg, usize)], opts: &SimplifyOptions, tol: f64, ends: Op
|
||||
// The fit may be `tol * 0.98` from the lines between the samples, those lines may be
|
||||
// `SAMPLE_GAP` of `tol` from the path, and the curve the test below turns into a line moves by up
|
||||
// to 1.5% of `tol`: 3/4 of how far its handles may be from the line. The fitted curves join
|
||||
// smoothly, so a curve becomes a line only when the line keeps both of its joins smooth.
|
||||
// smoothly, so a curve becomes a line only when the line keeps both of its joins smooth. The last
|
||||
// curve of a loop fitted across its start leaves along the loop's first segment.
|
||||
let (t0, t1) = ends.map_or((start_tangent(&first.c), end_tangent(&last.c)), |d| (d, d));
|
||||
let fitted = fit_cubics(&pts, t0, t1, tol * 0.98);
|
||||
let from = result.len();
|
||||
let mut arrive = t0;
|
||||
for (k, &c) in fitted.iter().enumerate() {
|
||||
let leave = fitted.get(k + 1).map_or(t1, start_tangent);
|
||||
let leave = match fitted.get(k + 1) {
|
||||
Some(next) => start_tangent(next),
|
||||
None if ends.is_some() => result.get(from).map_or(t1, |s| start_tangent(&s.c)),
|
||||
None => t1,
|
||||
};
|
||||
let line = is_straight(&c, tol * 0.02) && line_keeps_joins(&c, arrive, leave);
|
||||
arrive = if line { unit(c.p3 - c.p0).unwrap_or(arrive) } else { end_tangent(&c) };
|
||||
result.push(if line { Seg::line(c.p0, c.p3) } else { Seg { c, line } });
|
||||
|
||||
@@ -462,6 +462,32 @@ fn simplify_turns_a_curve_into_a_line_only_where_the_joins_stay_smooth() {
|
||||
assert!(!has_curves(&s), "{s:?}");
|
||||
}
|
||||
|
||||
/// A smoothed loop with no corner is fitted across its start, and the join there also turns by at
|
||||
/// most 0.5°. Each loop is a flat side that bends by 0.6° at the loop's start, closed by an arc of
|
||||
/// `n` lines `height` tall, with `inner` anchors along each half of the side.
|
||||
#[test]
|
||||
fn simplify_keeps_the_join_at_the_start_of_a_smoothed_loop_smooth() {
|
||||
for (half, height, n, inner, tolerance, smooth) in
|
||||
[(40.0, 20.0, 8, 3, 2.5, 2.0), (80.0, 60.0, 8, 3, 5.0, 0.5), (160.0, 200.0, 24, 3, 10.0, 2.0), (160.0, 20.0, 24, 8, 10.0, 5.0)]
|
||||
{
|
||||
let rise = half * 0.3f64.to_radians().tan();
|
||||
let along = |i: i32| Point::new(half * f64::from(i) / f64::from(inner), -rise * f64::from(i.abs()) / f64::from(inner));
|
||||
let mut pts: Vec<Point> = (0..=inner).map(along).collect();
|
||||
pts.extend((1..n).map(|i| {
|
||||
let a = f64::from(i) / f64::from(n) * PI;
|
||||
Point::new(half * a.cos(), -rise - height * a.sin())
|
||||
}));
|
||||
pts.extend((-inner..0).map(along));
|
||||
let path = PathData::single(SubPath::polyline(&pts, true));
|
||||
let s = simplify_with(&path, &SimplifyOptions { tolerance, corner_angle_deg: 179.0, smooth, ..Default::default() });
|
||||
let sp = &s.subpaths[0];
|
||||
assert!(sp.closed && sp.anchors.len() < pts.len(), "{tolerance} pt: {s:?}");
|
||||
assert!(sp.segment_is_line(0), "{tolerance} pt: the first segment is a curve: {s:?}");
|
||||
let turn = largest_join_turn(sp);
|
||||
assert!(turn <= 0.5 + 1e-9, "{tolerance} pt: an anchor turns {turn}°: {s:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simplify_straight_lines_option() {
|
||||
let c = dense_circle(50.0, 200);
|
||||
|
||||
Reference in New Issue
Block a user