fn chaikin_once(pts: &[(i32, i32)], closed: bool) -> Vec<(i32, i32)> {
let n = pts.len();
if n < 2 {
return pts.to_vec();
}
let mut out: Vec<(i32, i32)> = Vec::with_capacity(n * 2);
if !closed {
out.push(pts[0]);
}
let count = if closed { n } else { n - 1 };
for i in 0..count {
let a = pts[i];
let b = pts[(i + 1) % n];
let qx = 0.75 * a.0 as f64 + 0.25 * b.0 as f64;
let qy = 0.75 * a.1 as f64 + 0.25 * b.1 as f64;
let rx = 0.25 * a.0 as f64 + 0.75 * b.0 as f64;
let ry = 0.25 * a.1 as f64 + 0.75 * b.1 as f64;
out.push((qx.round() as i32, qy.round() as i32));
out.push((rx.round() as i32, ry.round() as i32));
}
if !closed {
out.push(*pts.last().unwrap());
}
out
}
pub fn smooth(pts: &[(i32, i32)], iterations: u32, closed: bool) -> Vec<(i32, i32)> {
let mut cur = pts.to_vec();
for _ in 0..iterations {
cur = chaikin_once(&cur, closed);
}
cur
}
pub fn densify(pts: &[(i32, i32)], target_px: f64, closed: bool) -> Vec<(i32, i32)> {
if pts.len() < 2 || target_px <= 0.0 {
return pts.to_vec();
}
let n = pts.len();
let count = if closed { n } else { n - 1 };
let mut out: Vec<(i32, i32)> = Vec::with_capacity(n);
for i in 0..count {
let a = pts[i];
let b = pts[(i + 1) % n];
out.push(a);
let dx = (b.0 - a.0) as f64;
let dy = (b.1 - a.1) as f64;
let len = (dx * dx + dy * dy).sqrt();
if len <= target_px {
continue;
}
let steps = (len / target_px).ceil() as usize;
for k in 1..steps {
let t = k as f64 / steps as f64;
let x = a.0 as f64 + dx * t;
let y = a.1 as f64 + dy * t;
out.push((x.round() as i32, y.round() as i32));
}
}
if !closed {
out.push(*pts.last().unwrap());
}
out
}
pub fn resample(pts: &[(i32, i32)], spacing_px: f64, closed: bool) -> Vec<(i32, i32)> {
if pts.len() < 2 || spacing_px <= 0.0 {
return pts.to_vec();
}
let n = pts.len();
let count = if closed { n } else { n - 1 };
let mut total = 0.0;
let mut seg_lens: Vec<f64> = Vec::with_capacity(count);
for i in 0..count {
let a = pts[i];
let b = pts[(i + 1) % n];
let dx = (b.0 - a.0) as f64;
let dy = (b.1 - a.1) as f64;
let l = (dx * dx + dy * dy).sqrt();
seg_lens.push(l);
total += l;
}
if total < spacing_px {
return pts.to_vec();
}
let n_out = (total / spacing_px).round() as usize;
if n_out < 2 {
return pts.to_vec();
}
let actual_spacing = total / n_out as f64;
let mut out: Vec<(i32, i32)> = Vec::with_capacity(n_out + 1);
out.push(pts[0]);
let mut target = actual_spacing;
let mut acc = 0.0;
for i in 0..count {
let a = pts[i];
let b = pts[(i + 1) % n];
let seg_len = seg_lens[i];
while target <= acc + seg_len && out.len() < n_out {
let t = (target - acc) / seg_len;
let x = a.0 as f64 + (b.0 - a.0) as f64 * t;
let y = a.1 as f64 + (b.1 - a.1) as f64 * t;
out.push((x.round() as i32, y.round() as i32));
target += actual_spacing;
}
acc += seg_len;
}
if !closed {
let last = *pts.last().unwrap();
if out.last().copied() != Some(last) {
out.push(last);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smooth_open_polyline_keeps_endpoints() {
let pts = vec![(0, 0), (10, 0), (10, 10)];
let sm = smooth(&pts, 1, false);
assert_eq!(sm.first(), Some(&(0, 0)));
assert_eq!(sm.last(), Some(&(10, 10)));
assert!(sm.len() > pts.len(), "smoothing should add vertices");
}
#[test]
fn densify_inserts_intermediate_points() {
let pts = vec![(0, 0), (20, 0)];
let d = densify(&pts, 5.0, false);
assert_eq!(d.len(), 5);
assert_eq!(d[0], (0, 0));
assert_eq!(d[4], (20, 0));
}
#[test]
fn resample_emits_uniform_arclength_points() {
let pts = vec![(0, 0), (100, 0)];
let r = resample(&pts, 25.0, false);
assert_eq!(r.len(), 5);
assert_eq!(r[0], (0, 0));
assert_eq!(r[4], (100, 0));
assert_eq!(r[1], (25, 0));
}
}