|
use super::*; |
|
use crate::utils::{TValue, TValueType}; |
|
|
|
|
|
impl Bezier { |
|
|
|
pub fn euclidean_to_parametric(&self, ratio: f64, error: f64) -> f64 { |
|
let total_length = self.length(None); |
|
self.euclidean_to_parametric_with_total_length(ratio, error, total_length) |
|
} |
|
|
|
|
|
|
|
|
|
pub fn euclidean_to_parametric_with_total_length(&self, euclidean_t: f64, error: f64, total_length: f64) -> f64 { |
|
if euclidean_t < error { |
|
return 0.; |
|
} |
|
if 1. - euclidean_t < error { |
|
return 1.; |
|
} |
|
|
|
match self.handles { |
|
BezierHandles::Linear => euclidean_t, |
|
BezierHandles::Quadratic { handle } => { |
|
|
|
fn recurse(a0: DVec2, a1: DVec2, a2: DVec2, level: u8, desired_len: f64) -> (f64, f64) { |
|
let lower = a0.distance(a2); |
|
let upper = a0.distance(a1) + a1.distance(a2); |
|
if level >= 8 { |
|
let approx_len = (lower + upper) / 2.; |
|
return (approx_len, desired_len / approx_len); |
|
} |
|
|
|
let b1 = 0.5 * (a0 + a1); |
|
let c1 = 0.5 * (a1 + a2); |
|
let b2 = 0.5 * (b1 + c1); |
|
let (first_len, t) = recurse(a0, b1, b2, level + 1, desired_len); |
|
if first_len > desired_len { |
|
return (first_len, t * 0.5); |
|
} |
|
let (second_len, t) = recurse(b2, c1, a2, level + 1, desired_len - first_len); |
|
(first_len + second_len, t * 0.5 + 0.5) |
|
} |
|
recurse(self.start, handle, self.end, 0, total_length * euclidean_t).1 |
|
} |
|
BezierHandles::Cubic { handle_start, handle_end } => { |
|
|
|
fn recurse(a0: DVec2, a1: DVec2, a2: DVec2, a3: DVec2, level: u8, desired_len: f64) -> (f64, f64) { |
|
let lower = a0.distance(a3); |
|
let upper = a0.distance(a1) + a1.distance(a2) + a2.distance(a3); |
|
if level >= 8 { |
|
let approx_len = (lower + upper) / 2.; |
|
return (approx_len, desired_len / approx_len); |
|
} |
|
|
|
let b1 = 0.5 * (a0 + a1); |
|
let t0 = 0.5 * (a1 + a2); |
|
let c1 = 0.5 * (a2 + a3); |
|
let b2 = 0.5 * (b1 + t0); |
|
let c2 = 0.5 * (t0 + c1); |
|
let b3 = 0.5 * (b2 + c2); |
|
let (first_len, t) = recurse(a0, b1, b2, b3, level + 1, desired_len); |
|
if first_len > desired_len { |
|
return (first_len, t * 0.5); |
|
} |
|
let (second_len, t) = recurse(b3, c2, c1, a3, level + 1, desired_len - first_len); |
|
(first_len + second_len, t * 0.5 + 0.5) |
|
} |
|
recurse(self.start, handle_start, handle_end, self.end, 0, total_length * euclidean_t).1 |
|
} |
|
} |
|
.clamp(0., 1.) |
|
} |
|
|
|
|
|
pub(crate) fn t_value_to_parametric(&self, t: TValue) -> f64 { |
|
match t { |
|
TValue::Parametric(t) => { |
|
assert!((0.0..=1.).contains(&t)); |
|
t |
|
} |
|
TValue::Euclidean(t) => { |
|
assert!((0.0..=1.).contains(&t)); |
|
self.euclidean_to_parametric(t, DEFAULT_EUCLIDEAN_ERROR_BOUND) |
|
} |
|
TValue::EuclideanWithinError { t, error } => { |
|
assert!((0.0..=1.).contains(&t)); |
|
self.euclidean_to_parametric(t, error) |
|
} |
|
} |
|
} |
|
|
|
|
|
pub(crate) fn unrestricted_parametric_evaluate(&self, t: f64) -> DVec2 { |
|
|
|
|
|
let t_squared = t * t; |
|
let one_minus_t = 1. - t; |
|
let squared_one_minus_t = one_minus_t * one_minus_t; |
|
|
|
match self.handles { |
|
BezierHandles::Linear => self.start.lerp(self.end, t), |
|
BezierHandles::Quadratic { handle } => squared_one_minus_t * self.start + 2. * one_minus_t * t * handle + t_squared * self.end, |
|
BezierHandles::Cubic { handle_start, handle_end } => { |
|
let t_cubed = t_squared * t; |
|
let cubed_one_minus_t = squared_one_minus_t * one_minus_t; |
|
cubed_one_minus_t * self.start + 3. * squared_one_minus_t * t * handle_start + 3. * one_minus_t * t_squared * handle_end + t_cubed * self.end |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
pub fn evaluate(&self, t: TValue) -> DVec2 { |
|
let t = self.t_value_to_parametric(t); |
|
self.unrestricted_parametric_evaluate(t) |
|
} |
|
|
|
|
|
|
|
|
|
pub fn compute_lookup_table(&self, steps: Option<usize>, tvalue_type: Option<TValueType>) -> impl Iterator<Item = DVec2> + '_ { |
|
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE); |
|
let tvalue_type = tvalue_type.unwrap_or(TValueType::Parametric); |
|
|
|
(0..=steps).map(move |t| { |
|
let tvalue = match tvalue_type { |
|
TValueType::Parametric => TValue::Parametric(t as f64 / steps as f64), |
|
TValueType::Euclidean => TValue::Euclidean(t as f64 / steps as f64), |
|
}; |
|
self.evaluate(tvalue) |
|
}) |
|
} |
|
|
|
|
|
|
|
|
|
pub fn length(&self, tolerance: Option<f64>) -> f64 { |
|
match self.handles { |
|
BezierHandles::Linear => (self.start - self.end).length(), |
|
BezierHandles::Quadratic { handle } => { |
|
|
|
fn recurse(a0: DVec2, a1: DVec2, a2: DVec2, tolerance: f64, level: u8) -> f64 { |
|
let lower = a0.distance(a2); |
|
let upper = a0.distance(a1) + a1.distance(a2); |
|
if upper - lower <= 2. * tolerance || level >= 8 { |
|
return (lower + upper) / 2.; |
|
} |
|
|
|
let b1 = 0.5 * (a0 + a1); |
|
let c1 = 0.5 * (a1 + a2); |
|
let b2 = 0.5 * (b1 + c1); |
|
recurse(a0, b1, b2, 0.5 * tolerance, level + 1) + recurse(b2, c1, a2, 0.5 * tolerance, level + 1) |
|
} |
|
recurse(self.start, handle, self.end, tolerance.unwrap_or_default(), 0) |
|
} |
|
BezierHandles::Cubic { handle_start, handle_end } => { |
|
|
|
fn recurse(a0: DVec2, a1: DVec2, a2: DVec2, a3: DVec2, tolerance: f64, level: u8) -> f64 { |
|
let lower = a0.distance(a3); |
|
let upper = a0.distance(a1) + a1.distance(a2) + a2.distance(a3); |
|
if upper - lower <= 2. * tolerance || level >= 8 { |
|
return (lower + upper) / 2.; |
|
} |
|
|
|
let b1 = 0.5 * (a0 + a1); |
|
let t0 = 0.5 * (a1 + a2); |
|
let c1 = 0.5 * (a2 + a3); |
|
let b2 = 0.5 * (b1 + t0); |
|
let c2 = 0.5 * (t0 + c1); |
|
let b3 = 0.5 * (b2 + c2); |
|
recurse(a0, b1, b2, b3, 0.5 * tolerance, level + 1) + recurse(b3, c2, c1, a3, 0.5 * tolerance, level + 1) |
|
} |
|
recurse(self.start, handle_start, handle_end, self.end, tolerance.unwrap_or_default(), 0) |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn length_centroid_and_length(&self, tolerance: Option<f64>) -> (DVec2, f64) { |
|
match self.handles { |
|
BezierHandles::Linear => ((self.start + self.end()) / 2., (self.start - self.end).length()), |
|
BezierHandles::Quadratic { handle } => { |
|
|
|
fn recurse(a0: DVec2, a1: DVec2, a2: DVec2, tolerance: f64, level: u8) -> (f64, DVec2) { |
|
let lower = a0.distance(a2); |
|
let upper = a0.distance(a1) + a1.distance(a2); |
|
if upper - lower <= 2. * tolerance || level >= 8 { |
|
let length = (lower + upper) / 2.; |
|
return (length, length * (a0 + a1 + a2) / 3.); |
|
} |
|
|
|
let b1 = 0.5 * (a0 + a1); |
|
let c1 = 0.5 * (a1 + a2); |
|
let b2 = 0.5 * (b1 + c1); |
|
|
|
let (length1, centroid_part1) = recurse(a0, b1, b2, 0.5 * tolerance, level + 1); |
|
let (length2, centroid_part2) = recurse(b2, c1, a2, 0.5 * tolerance, level + 1); |
|
(length1 + length2, centroid_part1 + centroid_part2) |
|
} |
|
|
|
let (length, centroid_parts) = recurse(self.start, handle, self.end, tolerance.unwrap_or_default(), 0); |
|
(centroid_parts / length, length) |
|
} |
|
BezierHandles::Cubic { handle_start, handle_end } => { |
|
|
|
fn recurse(a0: DVec2, a1: DVec2, a2: DVec2, a3: DVec2, tolerance: f64, level: u8) -> (f64, DVec2) { |
|
let lower = a0.distance(a3); |
|
let upper = a0.distance(a1) + a1.distance(a2) + a2.distance(a3); |
|
if upper - lower <= 2. * tolerance || level >= 8 { |
|
let length = (lower + upper) / 2.; |
|
return (length, length * (a0 + a1 + a2 + a3) / 4.); |
|
} |
|
|
|
let b1 = 0.5 * (a0 + a1); |
|
let t0 = 0.5 * (a1 + a2); |
|
let c1 = 0.5 * (a2 + a3); |
|
let b2 = 0.5 * (b1 + t0); |
|
let c2 = 0.5 * (t0 + c1); |
|
let b3 = 0.5 * (b2 + c2); |
|
|
|
let (length1, centroid_part1) = recurse(a0, b1, b2, b3, 0.5 * tolerance, level + 1); |
|
let (length2, centroid_part2) = recurse(b3, c2, c1, a3, 0.5 * tolerance, level + 1); |
|
(length1 + length2, centroid_part1 + centroid_part2) |
|
} |
|
let (length, centroid_parts) = recurse(self.start, handle_start, handle_end, self.end, tolerance.unwrap_or_default(), 0); |
|
(centroid_parts / length, length) |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn length_centroid(&self, tolerance: Option<f64>) -> DVec2 { |
|
self.length_centroid_and_length(tolerance).0 |
|
} |
|
|
|
|
|
|
|
pub fn project(&self, point: DVec2) -> f64 { |
|
let sbasis = crate::symmetrical_basis::to_symmetrical_basis_pair(*self); |
|
let derivative = sbasis.derivative(); |
|
let dd = (sbasis - point).dot(&derivative); |
|
let roots = dd.roots(); |
|
|
|
let mut closest = 0.; |
|
let mut min_dist_squared = self.evaluate(TValue::Parametric(0.)).distance_squared(point); |
|
|
|
for time in roots { |
|
let distance = self.evaluate(TValue::Parametric(time)).distance_squared(point); |
|
if distance < min_dist_squared { |
|
closest = time; |
|
min_dist_squared = distance; |
|
} |
|
} |
|
|
|
if self.evaluate(TValue::Parametric(1.)).distance_squared(point) < min_dist_squared { |
|
closest = 1.; |
|
} |
|
closest |
|
} |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use super::*; |
|
|
|
#[test] |
|
fn test_evaluate() { |
|
let p1 = DVec2::new(3., 5.); |
|
let p2 = DVec2::new(14., 3.); |
|
let p3 = DVec2::new(19., 14.); |
|
let p4 = DVec2::new(30., 21.); |
|
|
|
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3); |
|
assert_eq!(bezier1.evaluate(TValue::Parametric(0.5)), DVec2::new(12.5, 6.25)); |
|
|
|
let bezier2 = Bezier::from_cubic_dvec2(p1, p2, p3, p4); |
|
assert_eq!(bezier2.evaluate(TValue::Parametric(0.5)), DVec2::new(16.5, 9.625)); |
|
} |
|
|
|
#[test] |
|
fn test_compute_lookup_table() { |
|
let bezier1 = Bezier::from_quadratic_coordinates(10., 10., 30., 30., 50., 10.); |
|
let lookup_table1 = bezier1.compute_lookup_table(Some(2), Some(TValueType::Parametric)).collect::<Vec<_>>(); |
|
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(TValue::Parametric(0.5)), bezier1.end()]); |
|
|
|
let bezier2 = Bezier::from_cubic_coordinates(10., 10., 30., 30., 70., 70., 90., 10.); |
|
let lookup_table2 = bezier2.compute_lookup_table(Some(4), Some(TValueType::Parametric)).collect::<Vec<_>>(); |
|
assert_eq!( |
|
lookup_table2, |
|
vec![ |
|
bezier2.start(), |
|
bezier2.evaluate(TValue::Parametric(0.25)), |
|
bezier2.evaluate(TValue::Parametric(0.50)), |
|
bezier2.evaluate(TValue::Parametric(0.75)), |
|
bezier2.end() |
|
] |
|
); |
|
} |
|
|
|
#[test] |
|
fn test_length() { |
|
let p1 = DVec2::new(30., 50.); |
|
let p2 = DVec2::new(140., 30.); |
|
let p3 = DVec2::new(160., 170.); |
|
let p4 = DVec2::new(77., 129.); |
|
|
|
let bezier_linear = Bezier::from_linear_dvec2(p1, p2); |
|
assert!(utils::f64_compare(bezier_linear.length(None), p1.distance(p2), MAX_ABSOLUTE_DIFFERENCE)); |
|
|
|
let bezier_quadratic = Bezier::from_quadratic_dvec2(p1, p2, p3); |
|
assert!(utils::f64_compare(bezier_quadratic.length(None), 204., 1e-2)); |
|
|
|
let bezier_cubic = Bezier::from_cubic_dvec2(p1, p2, p3, p4); |
|
assert!(utils::f64_compare(bezier_cubic.length(None), 199., 1e-2)); |
|
} |
|
|
|
#[test] |
|
fn test_length_centroid() { |
|
let p1 = DVec2::new(30., 50.); |
|
let p2 = DVec2::new(140., 30.); |
|
let p3 = DVec2::new(160., 170.); |
|
let p4 = DVec2::new(77., 129.); |
|
|
|
let bezier_linear = Bezier::from_linear_dvec2(p1, p2); |
|
assert!(bezier_linear.length_centroid_and_length(None).0.abs_diff_eq((p1 + p2) / 2., MAX_ABSOLUTE_DIFFERENCE)); |
|
|
|
let bezier_quadratic = Bezier::from_quadratic_dvec2(p1, p2, p3); |
|
let expected = DVec2::new(112.81017736920136, 87.98713052477228); |
|
assert!(bezier_quadratic.length_centroid_and_length(None).0.abs_diff_eq(expected, MAX_ABSOLUTE_DIFFERENCE)); |
|
|
|
let bezier_cubic = Bezier::from_cubic_dvec2(p1, p2, p3, p4); |
|
let expected = DVec2::new(95.23597072432115, 88.0645175770206); |
|
assert!(bezier_cubic.length_centroid_and_length(None).0.abs_diff_eq(expected, MAX_ABSOLUTE_DIFFERENCE)); |
|
} |
|
|
|
#[test] |
|
fn test_project() { |
|
let bezier1 = Bezier::from_cubic_coordinates(4., 4., 23., 45., 10., 30., 56., 90.); |
|
assert_eq!(bezier1.project(DVec2::ZERO), 0.); |
|
assert_eq!(bezier1.project(DVec2::new(100., 100.)), 1.); |
|
|
|
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.); |
|
assert_eq!(bezier2.project(DVec2::new(100., 0.)), 0.); |
|
|
|
let bezier3 = Bezier::from_cubic_coordinates(-50., -50., -50., -50., 50., -50., 50., -50.); |
|
assert_eq!(DVec2::new(0., -50.), bezier3.evaluate(TValue::Parametric(bezier3.project(DVec2::new(0., -50.))))); |
|
} |
|
} |
|
|