use crate::{Path, PathF64, PointF64, Point2};
use flo_curves::{Coord2, bezier, BezierCurveFactory};
pub(crate) struct SubdivideSmooth;
use super::util::{angle, find_intersection, find_mid_point, norm, normalize, signed_angle_difference};
impl SubdivideSmooth {
pub fn find_corners<T>(path: &Path<Point2<T>>, threshold: f64) -> Vec<bool>
where T: std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + Copy + Into<f64> {
let path = &path.path[0..(path.path.len()-1)];
let len = path.len();
if len == 0 {
return vec![];
}
let mut corners: Vec<bool> = vec![false; len];
for i in 0..len {
let prev = if i==0 {len-1} else {i-1};
let next = (i+1) % len;
let v1: Point2<T> = path[i]-path[prev];
let v2: Point2<T> = path[next]-path[i];
let angle_v1: f64 = angle(&normalize(&v1));
let angle_v2: f64 = angle(&normalize(&v2));
let angle_diff = signed_angle_difference(&angle_v1, &angle_v2).abs();
if angle_diff >= threshold {
corners[i] = true;
}
}
corners
}
pub fn find_splice_points(path: &PathF64, threshold: f64) -> Vec<bool> {
let path = &path.path[0..(path.path.len()-1)];
let len = path.len();
if len == 0 {
return vec![];
}
let mut splice_points: Vec<bool> = vec![false; len];
let mut is_angle_increasing = false;
let mut angle_disp = 0.0;
for i in 0..len {
let prev = if i==0 {len-1} else {i-1};
let next = (i+1) % len;
let v1: PointF64 = path[i]-path[prev];
let v2: PointF64 = path[next]-path[i];
let angle_v1: f64 = angle(&normalize(&v1));
let angle_v2: f64 = angle(&normalize(&v2));
let angle_diff = signed_angle_difference(&angle_v1, &angle_v2);
let is_currently_increasing = angle_diff.is_sign_positive();
if i==0 {
is_angle_increasing = is_currently_increasing;
} else if is_angle_increasing != is_currently_increasing {
splice_points[i] = true;
is_angle_increasing = is_currently_increasing;
}
angle_disp += angle_diff;
if angle_disp.abs() >= threshold {
splice_points[i] = true;
}
if splice_points[i] {
angle_disp = 0.0;
}
}
splice_points
}
pub fn fit_points_with_bezier(points: &[PointF64]) -> [PointF64; 4] {
let mut coords:Vec<Coord2> = vec![];
for i in points.iter() {
coords.push(Coord2(i.x as f64, i.y as f64));
}
let opt = bezier::Curve::fit_from_points(&coords, 10.0);
match opt {
None => [PointF64::default(),PointF64::default(),PointF64::default(),PointF64::default()],
Some(curves) => {
if curves.is_empty() {
return [PointF64::default(),PointF64::default(),PointF64::default(),PointF64::default()];
}
let curve = curves[0];
let p1 = points[0];
let p4 = points[points.len()-1];
let (cp1, cp2) = curve.control_points;
let p2 = PointF64 {x: cp1.0, y: cp1.1};
let p3 = PointF64 {x: cp2.0, y: cp2.1};
Self::retract_handles(&p1, &p2, &p3, &p4)
}
}
}
pub fn subdivide_keep_corners(
path: &PathF64, corners: &[bool], outset_ratio: f64, segment_length: f64
) -> (PathF64, Vec<bool>, bool) {
let path = &path.path[0..(path.path.len()-1)];
let len = path.len();
let mut can_terminate_iteration = true;
let mut new_path: Vec<PointF64> = vec![];
let mut new_corners: Vec<bool> = vec![];
for i in 0..len {
new_path.push(PointF64 {x: path[i].x, y: path[i].y});
if corners[i] {
new_corners.push(true);
} else {
new_corners.push(false);
}
let j = (i+1)%len;
let length_curr = norm(&(path[i] - path[j]));
if length_curr <= segment_length {
continue;
}
let mut prev = if i==0 {len-1} else {i-1};
let mut next = (j+1)%len;
let length_prev = norm(&(path[prev] - path[i]));
let length_next = norm(&(path[next] - path[j]));
if length_prev/length_curr >= 2.0 || length_next/length_curr >= 2.0 {
continue;
}
if corners[i] {
prev = i;
}
if corners[j] {
next = j;
}
if prev==i && next==j {
continue;
} else {
let new_point = Self::find_new_point_from_4_point_scheme(
&path[i], &path[j], &path[prev], &path[next], outset_ratio
);
new_path.push(new_point);
new_corners.push(false); if norm(&(path[i] - new_point)) > segment_length || norm(&(path[j] - new_point)) > segment_length {
can_terminate_iteration = false;
}
}
}
new_path.push(new_path[0]);
(PathF64 { path: new_path }, new_corners, can_terminate_iteration)
}
fn find_new_point_from_4_point_scheme(
p_i: &PointF64, p_j: &PointF64, p_1: &PointF64, p_2: &PointF64, outset_ratio: f64
) -> PointF64 {
let mid_out = find_mid_point(p_i, p_j);
let mid_in = find_mid_point(p_1, p_2);
let vector_out: PointF64 = mid_out - mid_in;
let new_magnitude = norm(&vector_out) / outset_ratio;
if new_magnitude < 1e-5 {
return mid_out;
}
let unit_vector = normalize(&vector_out);
let frac_vector = PointF64 {x: unit_vector.x * new_magnitude, y: unit_vector.y * new_magnitude};
mid_out + frac_vector
}
fn retract_handles(a: &PointF64, b: &PointF64, c: &PointF64, d: &PointF64) -> [PointF64; 4] {
let da: PointF64 = *a-*d;
let ab: PointF64 = *b-*a;
let dab = signed_angle_difference(&angle(&normalize(&da)), &angle(&normalize(&ab)));
let bc: PointF64 = *c-*b;
let abc = signed_angle_difference(&angle(&normalize(&ab)), &angle(&normalize(&bc)));
if dab.is_sign_positive() != abc.is_sign_positive() {
let intersection = find_intersection(a, b, c, d);
[*a, intersection, intersection, *d]
} else {
[*a, *b, *c, *d]
}
}
}