Bezier-rs: Add Euclidean parameterization to Bezier::evaluate (#828)

* Add enum to evaluate for differenciating compute type

* Add euclidean parameterization and update styling in the UI

Co-authored-by: Rob Nadal <robnadal44@gmail.com>

* Update usage of evaluate in graphite

* Add description

* Code review changes

* Update tests

* Improve ComputeType ergonomics

* Large code review/cleanup pass

Co-authored-by: Rob Nadal <robnadal44@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Hannah Li
2022-11-18 20:37:52 -08:00
committed by Keavon Chambers
co-authored by Rob Nadal Keavon Chambers
parent a220bfa759
commit 01a9724389
15 changed files with 341 additions and 276 deletions
+55 -10
View File
@@ -1,9 +1,11 @@
use crate::utils::{f64_compare, ComputeType};
use super::*;
/// Functionality relating to looking up properties of the `Bezier` or points along the `Bezier`.
impl Bezier {
/// Calculate the point on the curve based on the `t`-value provided.
pub(crate) fn unrestricted_evaluate(&self, t: f64) -> DVec2 {
pub(crate) fn unrestricted_parametric_evaluate(&self, t: f64) -> DVec2 {
// Basis code based off of pseudocode found here: <https://pomax.github.io/bezierinfo/#explanation>.
let t_squared = t * t;
@@ -21,11 +23,48 @@ 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.
/// Expects `t` to be within the inclusive range `[0, 1]`.
pub fn evaluate(&self, t: f64) -> DVec2 {
assert!((0.0..=1.).contains(&t));
self.unrestricted_evaluate(t)
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)
}
}
}
/// Return a selection of equidistant points on the bezier curve.
@@ -36,7 +75,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(f64::from(t as i32) * ratio))
steps_array.push(self.evaluate(ComputeType::Parametric(f64::from(t as i32) * ratio)))
}
steps_array
@@ -123,7 +162,7 @@ impl Bezier {
if step_index == 0 {
distance = *table_distance;
} else {
distance = point.distance(self.evaluate(iterator_t));
distance = point.distance(self.evaluate(ComputeType::Parametric(iterator_t)));
*table_distance = distance;
}
if distance < new_minimum_distance {
@@ -173,23 +212,29 @@ mod tests {
let p4 = DVec2::new(30., 21.);
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
assert_eq!(bezier1.evaluate(0.5), DVec2::new(12.5, 6.25));
assert_eq!(bezier1.evaluate(ComputeType::Parametric(0.5)), DVec2::new(12.5, 6.25));
let bezier2 = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
assert_eq!(bezier2.evaluate(0.5), DVec2::new(16.5, 9.625));
assert_eq!(bezier2.evaluate(ComputeType::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(0.5), bezier1.end()]);
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(ComputeType::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));
assert_eq!(
lookup_table2,
vec![bezier2.start(), bezier2.evaluate(0.25), bezier2.evaluate(0.5), bezier2.evaluate(0.75), bezier2.end()]
vec![
bezier2.start(),
bezier2.evaluate(ComputeType::Parametric(0.25)),
bezier2.evaluate(ComputeType::Parametric(0.50)),
bezier2.evaluate(ComputeType::Parametric(0.75)),
bezier2.end()
]
);
}