Bezier-rs: Updated Bezier function signatures to accept TValue (#967)

* Create helper for converting d to t values

* Add euclidean option for tangent and normal

* Modified bezier functions signatures to accept ComputeType

* Stylistic changes per review

* Added ComputeType documentation

* Renamed ComputeType to TValue

* Fixed comments

* Fixed failing unit tests

* Code review

* Fix comments in code review

* Renamed compute_type_to_parametric to t_value_to_parametric

---------

Co-authored-by: Linda Zheng <thelindazheng@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Rob Nadal
2023-02-13 12:31:51 -05:00
committed by GitHub
co-authored by Linda Zheng Keavon Chambers
parent 1c2b8f67b2
commit 76be1f8515
25 changed files with 456 additions and 433 deletions
+54 -51
View File
@@ -1,9 +1,49 @@
use crate::utils::{f64_compare, ComputeType};
use crate::utils::{f64_compare, TValue};
use super::*;
/// Functionality relating to looking up properties of the `Bezier` or points along the `Bezier`.
impl Bezier {
/// Convert a euclidean distance ratio along the `Bezier` curve to a parametric `t`-value.
pub fn euclidean_to_parametric(&self, ratio: f64, error: f64) -> f64 {
let mut low = 0.;
let mut mid = 0.;
let mut high = 1.;
let total_length = self.length(None);
while low < high {
mid = (low + high) / 2.;
let test_ratio = self.trim(TValue::Parametric(0.), TValue::Parametric(mid)).length(None) / total_length;
if f64_compare(test_ratio, ratio, error) {
break;
} else if test_ratio < ratio {
low = mid;
} else {
high = mid;
}
}
mid
}
/// Convert a [TValue] to a parametric `t`-value.
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)
}
}
}
/// Calculate the point on the curve based on the `t`-value provided.
pub(crate) fn unrestricted_parametric_evaluate(&self, t: f64) -> DVec2 {
// Basis code based off of pseudocode found here: <https://pomax.github.io/bezierinfo/#explanation>.
@@ -23,48 +63,11 @@ impl Bezier {
}
}
/// Calculate the point along the curve that is a factor of `d` away from the start.
pub(crate) fn unrestricted_euclidean_evaluate(&self, d: f64, error: f64) -> DVec2 {
if let BezierHandles::Linear = self.handles {
return self.unrestricted_parametric_evaluate(d);
}
let mut low = 0.;
let mut mid = 0.;
let mut high = 1.;
let total_length = self.length(None);
while low < high {
mid = (low + high) / 2.;
let test_d = self.trim(0., mid).length(None) / total_length;
if f64_compare(test_d, d, error) {
break;
} else if test_d < d {
low = mid;
} else {
high = mid;
}
}
self.unrestricted_parametric_evaluate(mid)
}
/// Calculate the point on the curve based on the `t`-value provided.
/// Calculate the coordinates of the point `t` along the curve.
/// Expects `t` to be within the inclusive range `[0, 1]`.
pub fn evaluate(&self, t: ComputeType) -> DVec2 {
match t {
ComputeType::Parametric(t) => {
assert!((0.0..=1.).contains(&t));
self.unrestricted_parametric_evaluate(t)
}
ComputeType::Euclidean(t) => {
assert!((0.0..=1.).contains(&t));
self.unrestricted_euclidean_evaluate(t, 0.0001)
}
ComputeType::EuclideanWithinError { t, epsilon } => {
assert!((0.0..=1.).contains(&t));
self.unrestricted_euclidean_evaluate(t, epsilon)
}
}
pub fn evaluate(&self, t: TValue) -> DVec2 {
let t = self.t_value_to_parametric(t);
self.unrestricted_parametric_evaluate(t)
}
/// Return a selection of equidistant points on the bezier curve.
@@ -75,7 +78,7 @@ impl Bezier {
let mut steps_array = Vec::with_capacity(steps_unwrapped + 1);
for t in 0..steps_unwrapped + 1 {
steps_array.push(self.evaluate(ComputeType::Parametric(f64::from(t as i32) * ratio)))
steps_array.push(self.evaluate(TValue::Parametric(f64::from(t as i32) * ratio)))
}
steps_array
@@ -107,7 +110,7 @@ impl Bezier {
}
}
/// Returns the `t` value that corresponds to the closest point on the curve to the provided point.
/// Returns the parametric `t`-value that corresponds to the closest point on the curve to the provided point.
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
pub fn project(&self, point: DVec2, options: ProjectionOptions) -> f64 {
let ProjectionOptions {
@@ -162,7 +165,7 @@ impl Bezier {
if step_index == 0 {
distance = *table_distance;
} else {
distance = point.distance(self.evaluate(ComputeType::Parametric(iterator_t)));
distance = point.distance(self.evaluate(TValue::Parametric(iterator_t)));
*table_distance = distance;
}
if distance < new_minimum_distance {
@@ -212,17 +215,17 @@ mod tests {
let p4 = DVec2::new(30., 21.);
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
assert_eq!(bezier1.evaluate(ComputeType::Parametric(0.5)), DVec2::new(12.5, 6.25));
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(ComputeType::Parametric(0.5)), DVec2::new(16.5, 9.625));
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));
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(ComputeType::Parametric(0.5)), bezier1.end()]);
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));
@@ -230,9 +233,9 @@ mod tests {
lookup_table2,
vec![
bezier2.start(),
bezier2.evaluate(ComputeType::Parametric(0.25)),
bezier2.evaluate(ComputeType::Parametric(0.50)),
bezier2.evaluate(ComputeType::Parametric(0.75)),
bezier2.evaluate(TValue::Parametric(0.25)),
bezier2.evaluate(TValue::Parametric(0.50)),
bezier2.evaluate(TValue::Parametric(0.75)),
bezier2.end()
]
);