Skip to content

Commit 76be1f8

Browse files
RobNadallinda-zhengKeavon
authored
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 <[email protected]> Co-authored-by: Keavon Chambers <[email protected]>
1 parent 1c2b8f6 commit 76be1f8

File tree

25 files changed

+453
-430
lines changed

25 files changed

+453
-430
lines changed

editor/src/messages/tool/common_functionality/shape_editor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::messages::prelude::*;
22

3-
use bezier_rs::ComputeType;
3+
use bezier_rs::TValue;
44
use document_legacy::{LayerId, Operation};
55
use graphene_std::vector::consts::ManipulatorType;
66
use graphene_std::vector::manipulator_group::ManipulatorGroup;
@@ -296,7 +296,7 @@ impl ShapeEditor {
296296
for bezier_id in document.layer(layer_path).ok()?.as_subpath()?.bezier_iter() {
297297
let bezier = bezier_id.internal;
298298
let t = bezier.project(layer_pos, projection_options);
299-
let layerspace = bezier.evaluate(ComputeType::Parametric(t));
299+
let layerspace = bezier.evaluate(TValue::Parametric(t));
300300

301301
let screenspace = transform.transform_point2(layerspace);
302302
let distance_squared = screenspace.distance_squared(position);
@@ -314,7 +314,7 @@ impl ShapeEditor {
314314
pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) {
315315
for layer_path in &self.selected_layers {
316316
if let Some((bezier_id, t)) = self.closest_segment(document, layer_path, position, tolerance) {
317-
let [first, second] = bezier_id.internal.split(t);
317+
let [first, second] = bezier_id.internal.split(TValue::Parametric(t));
318318

319319
// Adjust the first manipulator group's out handle
320320
let out_handle = Operation::SetManipulatorPoints {

libraries/bezier-rs/src/bezier/core.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ impl Bezier {
203203

204204
#[cfg(test)]
205205
mod tests {
206-
use crate::utils::ComputeType;
206+
use crate::utils::TValue;
207207

208208
use super::compare::compare_points;
209209
use super::*;
@@ -215,13 +215,13 @@ mod tests {
215215
let p3 = DVec2::new(160., 170.);
216216

217217
let bezier1 = Bezier::quadratic_through_points(p1, p2, p3, None);
218-
assert!(compare_points(bezier1.evaluate(ComputeType::Parametric(0.5)), p2));
218+
assert!(compare_points(bezier1.evaluate(TValue::Parametric(0.5)), p2));
219219

220220
let bezier2 = Bezier::quadratic_through_points(p1, p2, p3, Some(0.8));
221-
assert!(compare_points(bezier2.evaluate(ComputeType::Parametric(0.8)), p2));
221+
assert!(compare_points(bezier2.evaluate(TValue::Parametric(0.8)), p2));
222222

223223
let bezier3 = Bezier::quadratic_through_points(p1, p2, p3, Some(0.));
224-
assert!(compare_points(bezier3.evaluate(ComputeType::Parametric(0.)), p2));
224+
assert!(compare_points(bezier3.evaluate(TValue::Parametric(0.)), p2));
225225
}
226226

227227
#[test]
@@ -231,12 +231,12 @@ mod tests {
231231
let p3 = DVec2::new(160., 160.);
232232

233233
let bezier1 = Bezier::cubic_through_points(p1, p2, p3, Some(0.3), Some(10.));
234-
assert!(compare_points(bezier1.evaluate(ComputeType::Parametric(0.3)), p2));
234+
assert!(compare_points(bezier1.evaluate(TValue::Parametric(0.3)), p2));
235235

236236
let bezier2 = Bezier::cubic_through_points(p1, p2, p3, Some(0.8), Some(91.7));
237-
assert!(compare_points(bezier2.evaluate(ComputeType::Parametric(0.8)), p2));
237+
assert!(compare_points(bezier2.evaluate(TValue::Parametric(0.8)), p2));
238238

239239
let bezier3 = Bezier::cubic_through_points(p1, p2, p3, Some(0.), Some(91.7));
240-
assert!(compare_points(bezier3.evaluate(ComputeType::Parametric(0.)), p2));
240+
assert!(compare_points(bezier3.evaluate(TValue::Parametric(0.)), p2));
241241
}
242242
}

libraries/bezier-rs/src/bezier/lookup.rs

Lines changed: 51 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,75 @@
1-
use crate::utils::{f64_compare, ComputeType};
1+
use crate::utils::{f64_compare, TValue};
22

33
use super::*;
44

55
/// Functionality relating to looking up properties of the `Bezier` or points along the `Bezier`.
66
impl Bezier {
7-
/// Calculate the point on the curve based on the `t`-value provided.
8-
pub(crate) fn unrestricted_parametric_evaluate(&self, t: f64) -> DVec2 {
9-
// Basis code based off of pseudocode found here: <https://pomax.github.io/bezierinfo/#explanation>.
10-
11-
let t_squared = t * t;
12-
let one_minus_t = 1. - t;
13-
let squared_one_minus_t = one_minus_t * one_minus_t;
14-
15-
match self.handles {
16-
BezierHandles::Linear => self.start.lerp(self.end, t),
17-
BezierHandles::Quadratic { handle } => squared_one_minus_t * self.start + 2. * one_minus_t * t * handle + t_squared * self.end,
18-
BezierHandles::Cubic { handle_start, handle_end } => {
19-
let t_cubed = t_squared * t;
20-
let cubed_one_minus_t = squared_one_minus_t * one_minus_t;
21-
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
22-
}
23-
}
24-
}
25-
26-
/// Calculate the point along the curve that is a factor of `d` away from the start.
27-
pub(crate) fn unrestricted_euclidean_evaluate(&self, d: f64, error: f64) -> DVec2 {
28-
if let BezierHandles::Linear = self.handles {
29-
return self.unrestricted_parametric_evaluate(d);
30-
}
31-
7+
/// Convert a euclidean distance ratio along the `Bezier` curve to a parametric `t`-value.
8+
pub fn euclidean_to_parametric(&self, ratio: f64, error: f64) -> f64 {
329
let mut low = 0.;
3310
let mut mid = 0.;
3411
let mut high = 1.;
3512
let total_length = self.length(None);
3613

3714
while low < high {
3815
mid = (low + high) / 2.;
39-
let test_d = self.trim(0., mid).length(None) / total_length;
40-
if f64_compare(test_d, d, error) {
16+
let test_ratio = self.trim(TValue::Parametric(0.), TValue::Parametric(mid)).length(None) / total_length;
17+
if f64_compare(test_ratio, ratio, error) {
4118
break;
42-
} else if test_d < d {
19+
} else if test_ratio < ratio {
4320
low = mid;
4421
} else {
4522
high = mid;
4623
}
4724
}
48-
self.unrestricted_parametric_evaluate(mid)
25+
26+
mid
4927
}
5028

51-
/// Calculate the point on the curve based on the `t`-value provided.
52-
/// Expects `t` to be within the inclusive range `[0, 1]`.
53-
pub fn evaluate(&self, t: ComputeType) -> DVec2 {
29+
/// Convert a [TValue] to a parametric `t`-value.
30+
pub(crate) fn t_value_to_parametric(&self, t: TValue) -> f64 {
5431
match t {
55-
ComputeType::Parametric(t) => {
32+
TValue::Parametric(t) => {
5633
assert!((0.0..=1.).contains(&t));
57-
self.unrestricted_parametric_evaluate(t)
34+
t
5835
}
59-
ComputeType::Euclidean(t) => {
36+
TValue::Euclidean(t) => {
6037
assert!((0.0..=1.).contains(&t));
61-
self.unrestricted_euclidean_evaluate(t, 0.0001)
38+
self.euclidean_to_parametric(t, DEFAULT_EUCLIDEAN_ERROR_BOUND)
6239
}
63-
ComputeType::EuclideanWithinError { t, epsilon } => {
40+
TValue::EuclideanWithinError { t, error } => {
6441
assert!((0.0..=1.).contains(&t));
65-
self.unrestricted_euclidean_evaluate(t, epsilon)
42+
self.euclidean_to_parametric(t, error)
6643
}
6744
}
6845
}
6946

47+
/// Calculate the point on the curve based on the `t`-value provided.
48+
pub(crate) fn unrestricted_parametric_evaluate(&self, t: f64) -> DVec2 {
49+
// Basis code based off of pseudocode found here: <https://pomax.github.io/bezierinfo/#explanation>.
50+
51+
let t_squared = t * t;
52+
let one_minus_t = 1. - t;
53+
let squared_one_minus_t = one_minus_t * one_minus_t;
54+
55+
match self.handles {
56+
BezierHandles::Linear => self.start.lerp(self.end, t),
57+
BezierHandles::Quadratic { handle } => squared_one_minus_t * self.start + 2. * one_minus_t * t * handle + t_squared * self.end,
58+
BezierHandles::Cubic { handle_start, handle_end } => {
59+
let t_cubed = t_squared * t;
60+
let cubed_one_minus_t = squared_one_minus_t * one_minus_t;
61+
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
62+
}
63+
}
64+
}
65+
66+
/// Calculate the coordinates of the point `t` along the curve.
67+
/// Expects `t` to be within the inclusive range `[0, 1]`.
68+
pub fn evaluate(&self, t: TValue) -> DVec2 {
69+
let t = self.t_value_to_parametric(t);
70+
self.unrestricted_parametric_evaluate(t)
71+
}
72+
7073
/// Return a selection of equidistant points on the bezier curve.
7174
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
7275
pub fn compute_lookup_table(&self, steps: Option<usize>) -> Vec<DVec2> {
@@ -75,7 +78,7 @@ impl Bezier {
7578
let mut steps_array = Vec::with_capacity(steps_unwrapped + 1);
7679

7780
for t in 0..steps_unwrapped + 1 {
78-
steps_array.push(self.evaluate(ComputeType::Parametric(f64::from(t as i32) * ratio)))
81+
steps_array.push(self.evaluate(TValue::Parametric(f64::from(t as i32) * ratio)))
7982
}
8083

8184
steps_array
@@ -107,7 +110,7 @@ impl Bezier {
107110
}
108111
}
109112

110-
/// Returns the `t` value that corresponds to the closest point on the curve to the provided point.
113+
/// Returns the parametric `t`-value that corresponds to the closest point on the curve to the provided point.
111114
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
112115
pub fn project(&self, point: DVec2, options: ProjectionOptions) -> f64 {
113116
let ProjectionOptions {
@@ -162,7 +165,7 @@ impl Bezier {
162165
if step_index == 0 {
163166
distance = *table_distance;
164167
} else {
165-
distance = point.distance(self.evaluate(ComputeType::Parametric(iterator_t)));
168+
distance = point.distance(self.evaluate(TValue::Parametric(iterator_t)));
166169
*table_distance = distance;
167170
}
168171
if distance < new_minimum_distance {
@@ -212,27 +215,27 @@ mod tests {
212215
let p4 = DVec2::new(30., 21.);
213216

214217
let bezier1 = Bezier::from_quadratic_dvec2(p1, p2, p3);
215-
assert_eq!(bezier1.evaluate(ComputeType::Parametric(0.5)), DVec2::new(12.5, 6.25));
218+
assert_eq!(bezier1.evaluate(TValue::Parametric(0.5)), DVec2::new(12.5, 6.25));
216219

217220
let bezier2 = Bezier::from_cubic_dvec2(p1, p2, p3, p4);
218-
assert_eq!(bezier2.evaluate(ComputeType::Parametric(0.5)), DVec2::new(16.5, 9.625));
221+
assert_eq!(bezier2.evaluate(TValue::Parametric(0.5)), DVec2::new(16.5, 9.625));
219222
}
220223

221224
#[test]
222225
fn test_compute_lookup_table() {
223226
let bezier1 = Bezier::from_quadratic_coordinates(10., 10., 30., 30., 50., 10.);
224227
let lookup_table1 = bezier1.compute_lookup_table(Some(2));
225-
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(ComputeType::Parametric(0.5)), bezier1.end()]);
228+
assert_eq!(lookup_table1, vec![bezier1.start(), bezier1.evaluate(TValue::Parametric(0.5)), bezier1.end()]);
226229

227230
let bezier2 = Bezier::from_cubic_coordinates(10., 10., 30., 30., 70., 70., 90., 10.);
228231
let lookup_table2 = bezier2.compute_lookup_table(Some(4));
229232
assert_eq!(
230233
lookup_table2,
231234
vec![
232235
bezier2.start(),
233-
bezier2.evaluate(ComputeType::Parametric(0.25)),
234-
bezier2.evaluate(ComputeType::Parametric(0.50)),
235-
bezier2.evaluate(ComputeType::Parametric(0.75)),
236+
bezier2.evaluate(TValue::Parametric(0.25)),
237+
bezier2.evaluate(TValue::Parametric(0.50)),
238+
bezier2.evaluate(TValue::Parametric(0.75)),
236239
bezier2.end()
237240
]
238241
);

0 commit comments

Comments
 (0)