1
- use crate :: utils:: { f64_compare, ComputeType } ;
1
+ use crate :: utils:: { f64_compare, TValue } ;
2
2
3
3
use super :: * ;
4
4
5
5
/// Functionality relating to looking up properties of the `Bezier` or points along the `Bezier`.
6
6
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 {
32
9
let mut low = 0. ;
33
10
let mut mid = 0. ;
34
11
let mut high = 1. ;
35
12
let total_length = self . length ( None ) ;
36
13
37
14
while low < high {
38
15
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) {
41
18
break ;
42
- } else if test_d < d {
19
+ } else if test_ratio < ratio {
43
20
low = mid;
44
21
} else {
45
22
high = mid;
46
23
}
47
24
}
48
- self . unrestricted_parametric_evaluate ( mid)
25
+
26
+ mid
49
27
}
50
28
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 {
54
31
match t {
55
- ComputeType :: Parametric ( t) => {
32
+ TValue :: Parametric ( t) => {
56
33
assert ! ( ( 0.0 ..=1. ) . contains( & t) ) ;
57
- self . unrestricted_parametric_evaluate ( t )
34
+ t
58
35
}
59
- ComputeType :: Euclidean ( t) => {
36
+ TValue :: Euclidean ( t) => {
60
37
assert ! ( ( 0.0 ..=1. ) . contains( & t) ) ;
61
- self . unrestricted_euclidean_evaluate ( t, 0.0001 )
38
+ self . euclidean_to_parametric ( t, DEFAULT_EUCLIDEAN_ERROR_BOUND )
62
39
}
63
- ComputeType :: EuclideanWithinError { t, epsilon } => {
40
+ TValue :: EuclideanWithinError { t, error } => {
64
41
assert ! ( ( 0.0 ..=1. ) . contains( & t) ) ;
65
- self . unrestricted_euclidean_evaluate ( t, epsilon )
42
+ self . euclidean_to_parametric ( t, error )
66
43
}
67
44
}
68
45
}
69
46
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
+
70
73
/// Return a selection of equidistant points on the bezier curve.
71
74
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
72
75
pub fn compute_lookup_table ( & self , steps : Option < usize > ) -> Vec < DVec2 > {
@@ -75,7 +78,7 @@ impl Bezier {
75
78
let mut steps_array = Vec :: with_capacity ( steps_unwrapped + 1 ) ;
76
79
77
80
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) ) )
79
82
}
80
83
81
84
steps_array
@@ -107,7 +110,7 @@ impl Bezier {
107
110
}
108
111
}
109
112
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.
111
114
/// Uses a searching algorithm akin to binary search that can be customized using the [ProjectionOptions] structure.
112
115
pub fn project ( & self , point : DVec2 , options : ProjectionOptions ) -> f64 {
113
116
let ProjectionOptions {
@@ -162,7 +165,7 @@ impl Bezier {
162
165
if step_index == 0 {
163
166
distance = * table_distance;
164
167
} else {
165
- distance = point. distance ( self . evaluate ( ComputeType :: Parametric ( iterator_t) ) ) ;
168
+ distance = point. distance ( self . evaluate ( TValue :: Parametric ( iterator_t) ) ) ;
166
169
* table_distance = distance;
167
170
}
168
171
if distance < new_minimum_distance {
@@ -212,27 +215,27 @@ mod tests {
212
215
let p4 = DVec2 :: new ( 30. , 21. ) ;
213
216
214
217
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 ) ) ;
216
219
217
220
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 ) ) ;
219
222
}
220
223
221
224
#[ test]
222
225
fn test_compute_lookup_table ( ) {
223
226
let bezier1 = Bezier :: from_quadratic_coordinates ( 10. , 10. , 30. , 30. , 50. , 10. ) ;
224
227
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( ) ] ) ;
226
229
227
230
let bezier2 = Bezier :: from_cubic_coordinates ( 10. , 10. , 30. , 30. , 70. , 70. , 90. , 10. ) ;
228
231
let lookup_table2 = bezier2. compute_lookup_table ( Some ( 4 ) ) ;
229
232
assert_eq ! (
230
233
lookup_table2,
231
234
vec![
232
235
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 ) ) ,
236
239
bezier2. end( )
237
240
]
238
241
) ;
0 commit comments