mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-09 18:50:56 +08:00
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:
committed by
Keavon Chambers
co-authored by
Linda Zheng
Keavon Chambers
parent
f0ad4c91d3
commit
a64c856ec4
@@ -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()
|
||||
]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user