|
use super::*; |
|
use crate::consts::{DEFAULT_EUCLIDEAN_ERROR_BOUND, DEFAULT_LUT_STEP_SIZE, MAX_ABSOLUTE_DIFFERENCE}; |
|
use crate::utils::{SubpathTValue, TValue, TValueType}; |
|
use glam::DVec2; |
|
|
|
|
|
impl<PointId: crate::Identifier> Subpath<PointId> { |
|
|
|
|
|
|
|
pub fn compute_lookup_table(&self, steps: Option<usize>, tvalue_type: Option<TValueType>) -> Vec<DVec2> { |
|
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE); |
|
let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric); |
|
|
|
(0..=steps) |
|
.map(|t| { |
|
let tvalue = match tvalue_type { |
|
TValueType::Parametric => SubpathTValue::GlobalParametric(t as f64 / steps as f64), |
|
TValueType::Euclidean => SubpathTValue::GlobalEuclidean(t as f64 / steps as f64), |
|
}; |
|
self.evaluate(tvalue) |
|
}) |
|
.collect() |
|
} |
|
|
|
|
|
|
|
|
|
pub fn length(&self, tolerance: Option<f64>) -> f64 { |
|
self.iter().map(|bezier| bezier.length(tolerance)).sum() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn length_centroid_and_length(&self, tolerance: Option<f64>, always_closed: bool) -> Option<(DVec2, f64)> { |
|
if always_closed { self.iter_closed() } else { self.iter() } |
|
.map(|bezier| bezier.length_centroid_and_length(tolerance)) |
|
.map(|(centroid, length)| (centroid * length, length)) |
|
.reduce(|(centroid_part1, length1), (centroid_part2, length2)| (centroid_part1 + centroid_part2, length1 + length2)) |
|
.map(|(centroid_part, length)| (centroid_part / length, length)) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn length_centroid(&self, tolerance: Option<f64>, always_closed: bool) -> Option<DVec2> { |
|
self.length_centroid_and_length(tolerance, always_closed).map(|(centroid, _)| centroid) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn area(&self, error: Option<f64>, minimum_separation: Option<f64>) -> f64 { |
|
let all_intersections = self.all_self_intersections(error, minimum_separation); |
|
let mut current_sign: f64 = 1.; |
|
|
|
let area: f64 = self |
|
.iter_closed() |
|
.enumerate() |
|
.map(|(index, bezier)| { |
|
let (f_x, f_y) = bezier.parametric_polynomial(); |
|
let (f_x, mut f_y) = (f_x.as_size::<7>().unwrap(), f_y.as_size::<7>().unwrap()); |
|
f_y.derivative_mut(); |
|
f_y *= &f_x; |
|
f_y.antiderivative_mut(); |
|
|
|
let mut curve_sum = -current_sign * f_y.eval(0.); |
|
for (_, t) in all_intersections.iter().filter(|(i, _)| *i == index) { |
|
curve_sum += 2. * current_sign * f_y.eval(*t); |
|
current_sign *= -1.; |
|
} |
|
curve_sum += current_sign * f_y.eval(1.); |
|
curve_sum |
|
}) |
|
.sum(); |
|
|
|
if area.abs() < error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE) { |
|
return 0.; |
|
} |
|
|
|
area.abs() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn area_centroid_and_area(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Option<(DVec2, f64)> { |
|
let all_intersections = self.all_self_intersections(error, minimum_separation); |
|
let mut current_sign: f64 = 1.; |
|
|
|
let (x_sum, y_sum, area) = self |
|
.iter_closed() |
|
.enumerate() |
|
.map(|(index, bezier)| { |
|
let (f_x, f_y) = bezier.parametric_polynomial(); |
|
let (f_x, f_y) = (f_x.as_size::<10>().unwrap(), f_y.as_size::<10>().unwrap()); |
|
let f_y_prime = f_y.derivative(); |
|
let f_x_prime = f_x.derivative(); |
|
let f_xy = &f_x * &f_y; |
|
|
|
let mut x_part = &f_xy * &f_x_prime; |
|
let mut y_part = &f_xy * &f_y_prime; |
|
let mut area_part = &f_x * &f_y_prime; |
|
x_part.antiderivative_mut(); |
|
y_part.antiderivative_mut(); |
|
area_part.antiderivative_mut(); |
|
|
|
let mut curve_sum_x = -current_sign * x_part.eval(0.); |
|
let mut curve_sum_y = -current_sign * y_part.eval(0.); |
|
let mut curve_sum_area = -current_sign * area_part.eval(0.); |
|
for (_, t) in all_intersections.iter().filter(|(i, _)| *i == index) { |
|
curve_sum_x += 2. * current_sign * x_part.eval(*t); |
|
curve_sum_y += 2. * current_sign * y_part.eval(*t); |
|
curve_sum_area += 2. * current_sign * area_part.eval(*t); |
|
current_sign *= -1.; |
|
} |
|
curve_sum_x += current_sign * x_part.eval(1.); |
|
curve_sum_y += current_sign * y_part.eval(1.); |
|
curve_sum_area += current_sign * area_part.eval(1.); |
|
|
|
(-curve_sum_x, curve_sum_y, curve_sum_area) |
|
}) |
|
.reduce(|(x1, y1, area1), (x2, y2, area2)| (x1 + x2, y1 + y2, area1 + area2))?; |
|
|
|
if area.abs() < error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE) { |
|
return Some((DVec2::NAN, 0.)); |
|
} |
|
|
|
Some((DVec2::new(x_sum / area, y_sum / area), area.abs())) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn area_centroid(&self, error: Option<f64>, minimum_separation: Option<f64>, tolerance: Option<f64>) -> Option<DVec2> { |
|
let (centroid, area) = self.area_centroid_and_area(error, minimum_separation)?; |
|
|
|
if area != 0. { |
|
Some(centroid) |
|
} else { |
|
self.length_centroid_and_length(tolerance, true).map(|(centroid, _)| centroid) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
pub fn global_euclidean_to_local_euclidean(&self, global_t: f64, lengths: &[f64], total_length: f64) -> (usize, f64) { |
|
let mut accumulator = 0.; |
|
for (index, length) in lengths.iter().enumerate() { |
|
let length_ratio = length / total_length; |
|
if (index == 0 || accumulator <= global_t) && global_t <= accumulator + length_ratio { |
|
return (index, ((global_t - accumulator) / length_ratio).clamp(0., 1.)); |
|
} |
|
accumulator += length_ratio; |
|
} |
|
(self.len() - 2, 1.) |
|
} |
|
|
|
|
|
|
|
|
|
pub(crate) fn t_value_to_parametric(&self, t: SubpathTValue) -> (usize, f64) { |
|
assert!(self.len_segments() >= 1); |
|
|
|
match t { |
|
SubpathTValue::Parametric { segment_index, t } => { |
|
assert!((0.0..=1.).contains(&t)); |
|
assert!((0..self.len_segments()).contains(&segment_index)); |
|
(segment_index, t) |
|
} |
|
SubpathTValue::GlobalParametric(global_t) => { |
|
assert!((0.0..=1.).contains(&global_t)); |
|
|
|
if global_t == 1. { |
|
return (self.len_segments() - 1, 1.); |
|
} |
|
|
|
let scaled_t = global_t * self.len_segments() as f64; |
|
let segment_index = scaled_t.floor() as usize; |
|
let t = scaled_t - segment_index as f64; |
|
|
|
(segment_index, t) |
|
} |
|
SubpathTValue::Euclidean { segment_index, t } => { |
|
assert!((0.0..=1.).contains(&t)); |
|
assert!((0..self.len_segments()).contains(&segment_index)); |
|
(segment_index, self.get_segment(segment_index).unwrap().euclidean_to_parametric(t, DEFAULT_EUCLIDEAN_ERROR_BOUND)) |
|
} |
|
SubpathTValue::GlobalEuclidean(t) => { |
|
let lengths = self.iter().map(|bezier| bezier.length(None)).collect::<Vec<f64>>(); |
|
let total_length: f64 = lengths.iter().sum(); |
|
let (segment_index, segment_t_euclidean) = self.global_euclidean_to_local_euclidean(t, lengths.as_slice(), total_length); |
|
let segment_t_parametric = self.get_segment(segment_index).unwrap().euclidean_to_parametric(segment_t_euclidean, DEFAULT_EUCLIDEAN_ERROR_BOUND); |
|
(segment_index, segment_t_parametric) |
|
} |
|
SubpathTValue::EuclideanWithinError { segment_index, t, error } => { |
|
assert!((0.0..=1.).contains(&t)); |
|
assert!((0..self.len_segments()).contains(&segment_index)); |
|
(segment_index, self.get_segment(segment_index).unwrap().euclidean_to_parametric(t, error)) |
|
} |
|
SubpathTValue::GlobalEuclideanWithinError { t, error } => { |
|
let lengths = self.iter().map(|bezier| bezier.length(None)).collect::<Vec<f64>>(); |
|
let total_length: f64 = lengths.iter().sum(); |
|
let (segment_index, segment_t) = self.global_euclidean_to_local_euclidean(t, lengths.as_slice(), total_length); |
|
(segment_index, self.get_segment(segment_index).unwrap().euclidean_to_parametric(segment_t, error)) |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
pub fn project(&self, point: DVec2) -> Option<(usize, f64)> { |
|
if self.is_empty() { |
|
return None; |
|
} |
|
|
|
|
|
let (index, (_, project_t)) = self |
|
.iter() |
|
.map(|bezier| { |
|
let project_t = bezier.project(point); |
|
(bezier.evaluate(TValue::Parametric(project_t)).distance(point), project_t) |
|
}) |
|
.enumerate() |
|
.min_by(|(_, (distance1, _)), (_, (distance2, _))| distance1.total_cmp(distance2)) |
|
.unwrap_or((0, (0., 0.))); |
|
|
|
Some((index, project_t)) |
|
} |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use super::*; |
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE; |
|
use crate::utils::f64_compare; |
|
|
|
#[test] |
|
fn length_quadratic() { |
|
let start = DVec2::new(20., 30.); |
|
let middle = DVec2::new(80., 90.); |
|
let end = DVec2::new(60., 45.); |
|
let handle1 = DVec2::new(75., 85.); |
|
let handle2 = DVec2::new(40., 30.); |
|
let handle3 = DVec2::new(10., 10.); |
|
|
|
let bezier1 = Bezier::from_quadratic_dvec2(start, handle1, middle); |
|
let bezier2 = Bezier::from_quadratic_dvec2(middle, handle2, end); |
|
let bezier3 = Bezier::from_quadratic_dvec2(end, handle3, start); |
|
|
|
let mut subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: None, |
|
out_handle: Some(handle1), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: middle, |
|
in_handle: None, |
|
out_handle: Some(handle2), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: Some(handle3), |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
assert_eq!(subpath.length(None), bezier1.length(None) + bezier2.length(None)); |
|
|
|
subpath.closed = true; |
|
assert_eq!(subpath.length(None), bezier1.length(None) + bezier2.length(None) + bezier3.length(None)); |
|
} |
|
|
|
#[test] |
|
fn length_mixed() { |
|
let start = DVec2::new(20., 30.); |
|
let middle = DVec2::new(70., 70.); |
|
let end = DVec2::new(60., 45.); |
|
let handle1 = DVec2::new(75., 85.); |
|
let handle2 = DVec2::new(40., 30.); |
|
let handle3 = DVec2::new(10., 10.); |
|
|
|
let linear_bezier = Bezier::from_linear_dvec2(start, middle); |
|
let quadratic_bezier = Bezier::from_quadratic_dvec2(middle, handle1, end); |
|
let cubic_bezier = Bezier::from_cubic_dvec2(end, handle2, handle3, start); |
|
|
|
let mut subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: Some(handle3), |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: middle, |
|
in_handle: None, |
|
out_handle: Some(handle1), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: Some(handle2), |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
assert_eq!(subpath.length(None), linear_bezier.length(None) + quadratic_bezier.length(None)); |
|
|
|
subpath.closed = true; |
|
assert_eq!(subpath.length(None), linear_bezier.length(None) + quadratic_bezier.length(None) + cubic_bezier.length(None)); |
|
} |
|
|
|
#[test] |
|
fn length_centroid() { |
|
let start = DVec2::new(0., 0.); |
|
let end = DVec2::new(1., 1.); |
|
let handle = DVec2::new(0., 1.); |
|
|
|
let mut subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: None, |
|
out_handle: Some(handle), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
let expected_centroid = DVec2::new(0.4153039799983826, 0.5846960200016174); |
|
let epsilon = 0.00001; |
|
|
|
assert!(subpath.length_centroid_and_length(None, true).unwrap().0.abs_diff_eq(expected_centroid, epsilon)); |
|
|
|
subpath.closed = true; |
|
assert!(subpath.length_centroid_and_length(None, true).unwrap().0.abs_diff_eq(expected_centroid, epsilon)); |
|
} |
|
|
|
#[test] |
|
fn area() { |
|
let start = DVec2::new(0., 0.); |
|
let end = DVec2::new(1., 1.); |
|
let handle = DVec2::new(0., 1.); |
|
|
|
let mut subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: None, |
|
out_handle: Some(handle), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
let expected_area = 1. / 3.; |
|
let epsilon = 0.00001; |
|
|
|
assert!((subpath.area(Some(0.001), Some(0.001)) - expected_area).abs() < epsilon); |
|
|
|
subpath.closed = true; |
|
assert!((subpath.area(Some(0.001), Some(0.001)) - expected_area).abs() < epsilon); |
|
} |
|
|
|
#[test] |
|
fn area_centroid() { |
|
let start = DVec2::new(0., 0.); |
|
let end = DVec2::new(1., 1.); |
|
let handle = DVec2::new(0., 1.); |
|
|
|
let mut subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: None, |
|
out_handle: Some(handle), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
let expected_centroid = DVec2::new(0.4, 0.6); |
|
let epsilon = 0.00001; |
|
|
|
assert!(subpath.area_centroid(Some(0.001), Some(0.001), None).unwrap().abs_diff_eq(expected_centroid, epsilon)); |
|
|
|
subpath.closed = true; |
|
assert!(subpath.area_centroid(Some(0.001), Some(0.001), None).unwrap().abs_diff_eq(expected_centroid, epsilon)); |
|
} |
|
|
|
#[test] |
|
fn t_value_to_parametric_global_parametric_open_subpath() { |
|
let mock_manipulator_group = ManipulatorGroup { |
|
anchor: DVec2::new(0., 0.), |
|
in_handle: None, |
|
out_handle: None, |
|
id: EmptyId, |
|
}; |
|
let open_subpath = Subpath { |
|
manipulator_groups: vec![mock_manipulator_group; 5], |
|
closed: false, |
|
}; |
|
|
|
let (segment_index, t) = open_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(0.7)); |
|
assert_eq!(segment_index, 2); |
|
assert!(f64_compare(t, 0.8, MAX_ABSOLUTE_DIFFERENCE)); |
|
|
|
|
|
assert_eq!(open_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(0.)), (0, 0.)); |
|
assert_eq!(open_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(1.)), (3, 1.)); |
|
} |
|
|
|
#[test] |
|
fn t_value_to_parametric_global_parametric_closed_subpath() { |
|
let mock_manipulator_group = ManipulatorGroup { |
|
anchor: DVec2::new(0., 0.), |
|
in_handle: None, |
|
out_handle: None, |
|
id: EmptyId, |
|
}; |
|
let closed_subpath = Subpath { |
|
manipulator_groups: vec![mock_manipulator_group; 5], |
|
closed: true, |
|
}; |
|
|
|
let (segment_index, t) = closed_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(0.7)); |
|
assert_eq!(segment_index, 3); |
|
assert!(f64_compare(t, 0.5, MAX_ABSOLUTE_DIFFERENCE)); |
|
|
|
|
|
assert_eq!(closed_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(0.)), (0, 0.)); |
|
assert_eq!(closed_subpath.t_value_to_parametric(SubpathTValue::GlobalParametric(1.)), (4, 1.)); |
|
} |
|
|
|
#[test] |
|
fn exact_start_end() { |
|
let start = DVec2::new(20., 30.); |
|
let end = DVec2::new(60., 45.); |
|
let handle = DVec2::new(75., 85.); |
|
|
|
let subpath: Subpath<EmptyId> = Subpath::from_bezier(&Bezier::from_quadratic_dvec2(start, handle, end)); |
|
|
|
assert_eq!(subpath.evaluate(SubpathTValue::GlobalEuclidean(0.)), start); |
|
assert_eq!(subpath.evaluate(SubpathTValue::GlobalEuclidean(1.)), end); |
|
} |
|
} |
|
|