pub type Point = (f64, f64);
pub enum Seg {
Line {
to: Point,
},
Arc {
to: Point,
center: Point,
radius: f64,
sweep: u8,
},
}
pub struct RoundedPath {
pub start: Point,
pub segs: Vec<Seg>,
}
pub fn path_d(pts: &[Point], targets: &[f64]) -> String {
use super::values::num;
use std::fmt::Write;
let rounded = round(pts, targets);
let mut d = format!("M {} {}", num(rounded.start.0), num(rounded.start.1));
for seg in &rounded.segs {
match seg {
Seg::Line { to } => write!(d, " L {} {}", num(to.0), num(to.1)).unwrap(),
Seg::Arc {
to, radius, sweep, ..
} => write!(
d,
" A {r} {r} 0 0 {sweep} {} {}",
num(to.0),
num(to.1),
r = num(*radius),
)
.unwrap(),
}
}
d
}
pub fn round(pts: &[Point], targets: &[f64]) -> RoundedPath {
let mut segs = Vec::new();
for i in 1..pts.len() - 1 {
let (a, b, c) = (pts[i - 1], pts[i], pts[i + 1]);
let (in_dx, in_dy) = (b.0 - a.0, b.1 - a.1);
let (out_dx, out_dy) = (c.0 - b.0, c.1 - b.1);
let in_len = in_dx.abs() + in_dy.abs();
let out_len = out_dx.abs() + out_dy.abs();
let r = targets
.get(i - 1)
.copied()
.unwrap_or(0.0)
.min(in_len / 2.0)
.min(out_len / 2.0);
let cross = in_dx * out_dy - in_dy * out_dx;
if r < 0.5 || cross == 0.0 {
segs.push(Seg::Line { to: b });
continue;
}
let (ux, uy) = (in_dx / in_len, in_dy / in_len);
let (vx, vy) = (out_dx / out_len, out_dy / out_len);
let enter = (b.0 - ux * r, b.1 - uy * r);
segs.push(Seg::Line { to: enter });
segs.push(Seg::Arc {
to: (b.0 + vx * r, b.1 + vy * r),
center: (enter.0 + vx * r, enter.1 + vy * r),
radius: r,
sweep: if cross > 0.0 { 1 } else { 0 },
});
}
segs.push(Seg::Line {
to: *pts.last().expect("a link polyline has at least two points"),
});
RoundedPath {
start: pts[0],
segs,
}
}