1#![allow(missing_docs)]
12
13use crate::{Point, Transform};
14
15use crate::f32x2_t::f32x2;
16use crate::floating_point::FLOAT_PI;
17use crate::scalar::{Scalar, SCALAR_NEARLY_ZERO, SCALAR_ROOT_2_OVER_2};
18
19use crate::floating_point::{NormalizedF32, NormalizedF32Exclusive};
20use crate::path_builder::PathDirection;
21
22#[cfg(all(not(feature = "std"), feature = "no-std-float"))]
23use crate::NoStdFloat;
24
25#[derive(Clone, Copy, Default, Debug)]
27pub struct QuadCoeff {
28 pub a: f32x2,
29 pub b: f32x2,
30 pub c: f32x2,
31}
32
33impl QuadCoeff {
34 pub fn from_points(points: &[Point; 3]) -> Self {
35 let c = points[0].to_f32x2();
36 let p1 = points[1].to_f32x2();
37 let p2 = points[2].to_f32x2();
38 let b = times_2(p1 - c);
39 let a = p2 - times_2(p1) + c;
40
41 QuadCoeff { a, b, c }
42 }
43
44 pub fn eval(&self, t: f32x2) -> f32x2 {
45 (self.a * t + self.b) * t + self.c
46 }
47}
48
49#[derive(Clone, Copy, Default, Debug)]
50pub struct CubicCoeff {
51 pub a: f32x2,
52 pub b: f32x2,
53 pub c: f32x2,
54 pub d: f32x2,
55}
56
57impl CubicCoeff {
58 pub fn from_points(points: &[Point; 4]) -> Self {
59 let p0 = points[0].to_f32x2();
60 let p1 = points[1].to_f32x2();
61 let p2 = points[2].to_f32x2();
62 let p3 = points[3].to_f32x2();
63 let three = f32x2::splat(3.0);
64
65 CubicCoeff {
66 a: p3 + three * (p1 - p2) - p0,
67 b: three * (p2 - times_2(p1) + p0),
68 c: three * (p1 - p0),
69 d: p0,
70 }
71 }
72
73 pub fn eval(&self, t: f32x2) -> f32x2 {
74 ((self.a * t + self.b) * t + self.c) * t + self.d
75 }
76}
77
78pub fn new_t_values() -> [NormalizedF32Exclusive; 3] {
80 [
81 NormalizedF32Exclusive::ANY,
82 NormalizedF32Exclusive::ANY,
83 NormalizedF32Exclusive::ANY,
84 ]
85}
86
87pub fn chop_quad_at(src: &[Point], t: NormalizedF32Exclusive, dst: &mut [Point; 5]) {
88 let p0 = src[0].to_f32x2();
89 let p1 = src[1].to_f32x2();
90 let p2 = src[2].to_f32x2();
91 let tt = f32x2::splat(t.get());
92
93 let p01 = interp(p0, p1, tt);
94 let p12 = interp(p1, p2, tt);
95
96 dst[0] = Point::from_f32x2(p0);
97 dst[1] = Point::from_f32x2(p01);
98 dst[2] = Point::from_f32x2(interp(p01, p12, tt));
99 dst[3] = Point::from_f32x2(p12);
100 dst[4] = Point::from_f32x2(p2);
101}
102
103pub fn find_unit_quad_roots(
109 a: f32,
110 b: f32,
111 c: f32,
112 roots: &mut [NormalizedF32Exclusive; 3],
113) -> usize {
114 if a == 0.0 {
115 if let Some(r) = valid_unit_divide(-c, b) {
116 roots[0] = r;
117 return 1;
118 } else {
119 return 0;
120 }
121 }
122
123 let mut dr = f64::from(b) * f64::from(b) - 4.0 * f64::from(a) * f64::from(c);
125 if dr < 0.0 {
126 return 0;
127 }
128 dr = dr.sqrt();
129 let r = dr as f32;
130 if !r.is_finite() {
131 return 0;
132 }
133
134 let q = if b < 0.0 {
135 -(b - r) / 2.0
136 } else {
137 -(b + r) / 2.0
138 };
139
140 let mut roots_offset = 0;
141 if let Some(r) = valid_unit_divide(q, a) {
142 roots[roots_offset] = r;
143 roots_offset += 1;
144 }
145
146 if let Some(r) = valid_unit_divide(c, q) {
147 roots[roots_offset] = r;
148 roots_offset += 1;
149 }
150
151 if roots_offset == 2 {
152 if roots[0].get() > roots[1].get() {
153 roots.swap(0, 1);
154 } else if roots[0] == roots[1] {
155 roots_offset -= 1; }
158 }
159
160 roots_offset
161}
162
163pub fn chop_cubic_at2(src: &[Point; 4], t: NormalizedF32Exclusive, dst: &mut [Point]) {
164 let p0 = src[0].to_f32x2();
165 let p1 = src[1].to_f32x2();
166 let p2 = src[2].to_f32x2();
167 let p3 = src[3].to_f32x2();
168 let tt = f32x2::splat(t.get());
169
170 let ab = interp(p0, p1, tt);
171 let bc = interp(p1, p2, tt);
172 let cd = interp(p2, p3, tt);
173 let abc = interp(ab, bc, tt);
174 let bcd = interp(bc, cd, tt);
175 let abcd = interp(abc, bcd, tt);
176
177 dst[0] = Point::from_f32x2(p0);
178 dst[1] = Point::from_f32x2(ab);
179 dst[2] = Point::from_f32x2(abc);
180 dst[3] = Point::from_f32x2(abcd);
181 dst[4] = Point::from_f32x2(bcd);
182 dst[5] = Point::from_f32x2(cd);
183 dst[6] = Point::from_f32x2(p3);
184}
185
186pub fn valid_unit_divide(mut numer: f32, mut denom: f32) -> Option<NormalizedF32Exclusive> {
187 if numer < 0.0 {
188 numer = -numer;
189 denom = -denom;
190 }
191
192 if denom == 0.0 || numer == 0.0 || numer >= denom {
193 return None;
194 }
195
196 let r = numer / denom;
197 NormalizedF32Exclusive::new(r)
198}
199
200fn interp(v0: f32x2, v1: f32x2, t: f32x2) -> f32x2 {
201 v0 + (v1 - v0) * t
202}
203
204fn times_2(value: f32x2) -> f32x2 {
205 value + value
206}
207
208pub(crate) fn find_quad_max_curvature(src: &[Point; 3]) -> NormalizedF32 {
220 let ax = src[1].x - src[0].x;
221 let ay = src[1].y - src[0].y;
222 let bx = src[0].x - src[1].x - src[1].x + src[2].x;
223 let by = src[0].y - src[1].y - src[1].y + src[2].y;
224
225 let mut numer = -(ax * bx + ay * by);
226 let mut denom = bx * bx + by * by;
227 if denom < 0.0 {
228 numer = -numer;
229 denom = -denom;
230 }
231
232 if numer <= 0.0 {
233 return NormalizedF32::ZERO;
234 }
235
236 if numer >= denom {
237 return NormalizedF32::ONE;
239 }
240
241 let t = numer / denom;
242 NormalizedF32::new(t).unwrap()
243}
244
245pub(crate) fn eval_quad_at(src: &[Point; 3], t: NormalizedF32) -> Point {
246 Point::from_f32x2(QuadCoeff::from_points(src).eval(f32x2::splat(t.get())))
247}
248
249pub(crate) fn eval_quad_tangent_at(src: &[Point; 3], tol: NormalizedF32) -> Point {
250 if (tol == NormalizedF32::ZERO && src[0] == src[1])
254 || (tol == NormalizedF32::ONE && src[1] == src[2])
255 {
256 return src[2] - src[0];
257 }
258
259 let p0 = src[0].to_f32x2();
260 let p1 = src[1].to_f32x2();
261 let p2 = src[2].to_f32x2();
262
263 let b = p1 - p0;
264 let a = p2 - p1 - b;
265 let t = a * f32x2::splat(tol.get()) + b;
266
267 Point::from_f32x2(t + t)
268}
269
270pub fn find_cubic_max_curvature<'a>(
281 src: &[Point; 4],
282 t_values: &'a mut [NormalizedF32; 3],
283) -> &'a [NormalizedF32] {
284 let mut coeff_x = formulate_f1_dot_f2(&[src[0].x, src[1].x, src[2].x, src[3].x]);
285 let coeff_y = formulate_f1_dot_f2(&[src[0].y, src[1].y, src[2].y, src[3].y]);
286
287 for i in 0..4 {
288 coeff_x[i] += coeff_y[i];
289 }
290
291 let len = solve_cubic_poly(&coeff_x, t_values);
292 &t_values[0..len]
293}
294
295fn formulate_f1_dot_f2(src: &[f32; 4]) -> [f32; 4] {
306 let a = src[1] - src[0];
307 let b = src[2] - 2.0 * src[1] + src[0];
308 let c = src[3] + 3.0 * (src[1] - src[2]) - src[0];
309
310 [c * c, 3.0 * b * c, 2.0 * b * b + c * a, a * b]
311}
312
313fn solve_cubic_poly(coeff: &[f32; 4], t_values: &mut [NormalizedF32; 3]) -> usize {
319 if coeff[0].is_nearly_zero() {
320 let mut tmp_t = new_t_values();
322 let count = find_unit_quad_roots(coeff[1], coeff[2], coeff[3], &mut tmp_t);
323 for i in 0..count {
324 t_values[i] = tmp_t[i].to_normalized();
325 }
326
327 return count;
328 }
329
330 debug_assert!(coeff[0] != 0.0);
331
332 let inva = coeff[0].invert();
333 let a = coeff[1] * inva;
334 let b = coeff[2] * inva;
335 let c = coeff[3] * inva;
336
337 let q = (a * a - b * 3.0) / 9.0;
338 let r = (2.0 * a * a * a - 9.0 * a * b + 27.0 * c) / 54.0;
339
340 let q3 = q * q * q;
341 let r2_minus_q3 = r * r - q3;
342 let adiv3 = a / 3.0;
343
344 if r2_minus_q3 < 0.0 {
345 let theta = (r / q3.sqrt()).bound(-1.0, 1.0).acos();
348 let neg2_root_q = -2.0 * q.sqrt();
349
350 t_values[0] = NormalizedF32::new_clamped(neg2_root_q * (theta / 3.0).cos() - adiv3);
351 t_values[1] = NormalizedF32::new_clamped(
352 neg2_root_q * ((theta + 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
353 );
354 t_values[2] = NormalizedF32::new_clamped(
355 neg2_root_q * ((theta - 2.0 * FLOAT_PI) / 3.0).cos() - adiv3,
356 );
357
358 sort_array3(t_values);
360 collapse_duplicates3(t_values)
361 } else {
362 let mut a = r.abs() + r2_minus_q3.sqrt();
364 a = scalar_cube_root(a);
365 if r > 0.0 {
366 a = -a;
367 }
368
369 if a != 0.0 {
370 a += q / a;
371 }
372
373 t_values[0] = NormalizedF32::new_clamped(a - adiv3);
374 1
375 }
376}
377
378fn sort_array3(array: &mut [NormalizedF32; 3]) {
379 if array[0] > array[1] {
380 array.swap(0, 1);
381 }
382
383 if array[1] > array[2] {
384 array.swap(1, 2);
385 }
386
387 if array[0] > array[1] {
388 array.swap(0, 1);
389 }
390}
391
392fn collapse_duplicates3(array: &mut [NormalizedF32; 3]) -> usize {
393 let mut len = 3;
394
395 if array[1] == array[2] {
396 len = 2;
397 }
398
399 if array[0] == array[1] {
400 len = 1;
401 }
402
403 len
404}
405
406fn scalar_cube_root(x: f32) -> f32 {
407 x.powf(0.3333333)
408}
409
410pub(crate) fn eval_cubic_pos_at(src: &[Point; 4], t: NormalizedF32) -> Point {
412 Point::from_f32x2(CubicCoeff::from_points(src).eval(f32x2::splat(t.get())))
413}
414
415pub(crate) fn eval_cubic_tangent_at(src: &[Point; 4], t: NormalizedF32) -> Point {
417 if (t.get() == 0.0 && src[0] == src[1]) || (t.get() == 1.0 && src[2] == src[3]) {
421 let mut tangent = if t.get() == 0.0 {
422 src[2] - src[0]
423 } else {
424 src[3] - src[1]
425 };
426
427 if tangent.x == 0.0 && tangent.y == 0.0 {
428 tangent = src[3] - src[0];
429 }
430
431 tangent
432 } else {
433 eval_cubic_derivative(src, t)
434 }
435}
436
437fn eval_cubic_derivative(src: &[Point; 4], t: NormalizedF32) -> Point {
438 let p0 = src[0].to_f32x2();
439 let p1 = src[1].to_f32x2();
440 let p2 = src[2].to_f32x2();
441 let p3 = src[3].to_f32x2();
442
443 let coeff = QuadCoeff {
444 a: p3 + f32x2::splat(3.0) * (p1 - p2) - p0,
445 b: times_2(p2 - times_2(p1) + p0),
446 c: p1 - p0,
447 };
448
449 Point::from_f32x2(coeff.eval(f32x2::splat(t.get())))
450}
451
452pub(crate) fn find_cubic_inflections<'a>(
463 src: &[Point; 4],
464 t_values: &'a mut [NormalizedF32Exclusive; 3],
465) -> &'a [NormalizedF32Exclusive] {
466 let ax = src[1].x - src[0].x;
467 let ay = src[1].y - src[0].y;
468 let bx = src[2].x - 2.0 * src[1].x + src[0].x;
469 let by = src[2].y - 2.0 * src[1].y + src[0].y;
470 let cx = src[3].x + 3.0 * (src[1].x - src[2].x) - src[0].x;
471 let cy = src[3].y + 3.0 * (src[1].y - src[2].y) - src[0].y;
472
473 let len = find_unit_quad_roots(
474 bx * cy - by * cx,
475 ax * cy - ay * cx,
476 ax * by - ay * bx,
477 t_values,
478 );
479
480 &t_values[0..len]
481}
482
483pub(crate) fn find_cubic_cusp(src: &[Point; 4]) -> Option<NormalizedF32Exclusive> {
487 if src[0] == src[1] {
492 return None;
493 }
494
495 if src[2] == src[3] {
496 return None;
497 }
498
499 if on_same_side(src, 0, 2) || on_same_side(src, 2, 0) {
502 return None;
503 }
504
505 let mut t_values = [NormalizedF32::ZERO; 3];
508 let max_curvature = find_cubic_max_curvature(src, &mut t_values);
509 for test_t in max_curvature {
510 if 0.0 >= test_t.get() || test_t.get() >= 1.0 {
511 continue;
513 }
514
515 let d_pt = eval_cubic_derivative(src, *test_t);
519 let d_pt_magnitude = d_pt.length_sqd();
520 let precision = calc_cubic_precision(src);
521 if d_pt_magnitude < precision {
522 return Some(NormalizedF32Exclusive::new_bounded(test_t.get()));
525 }
526 }
527
528 None
529}
530
531fn on_same_side(src: &[Point; 4], test_index: usize, line_index: usize) -> bool {
534 let origin = src[line_index];
535 let line = src[line_index + 1] - origin;
536 let mut crosses = [0.0, 0.0];
537 for index in 0..2 {
538 let test_line = src[test_index + index] - origin;
539 crosses[index] = line.cross(test_line);
540 }
541
542 crosses[0] * crosses[1] >= 0.0
543}
544
545fn calc_cubic_precision(src: &[Point; 4]) -> f32 {
548 (src[1].distance_to_sqd(src[0])
549 + src[2].distance_to_sqd(src[1])
550 + src[3].distance_to_sqd(src[2]))
551 * 1e-8
552}
553
554#[derive(Copy, Clone, Default, Debug)]
555pub(crate) struct Conic {
556 pub points: [Point; 3],
557 pub weight: f32,
558}
559
560impl Conic {
561 pub fn new(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Self {
562 Conic {
563 points: [pt0, pt1, pt2],
564 weight,
565 }
566 }
567
568 pub fn from_points(points: &[Point], weight: f32) -> Self {
569 Conic {
570 points: [points[0], points[1], points[2]],
571 weight,
572 }
573 }
574
575 fn compute_quad_pow2(&self, tolerance: f32) -> Option<u8> {
576 if tolerance < 0.0 || !tolerance.is_finite() {
577 return None;
578 }
579
580 if !self.points[0].is_finite() || !self.points[1].is_finite() || !self.points[2].is_finite()
581 {
582 return None;
583 }
584
585 const MAX_CONIC_TO_QUAD_POW2: usize = 4;
587
588 let a = self.weight - 1.0;
591 let k = a / (4.0 * (2.0 + a));
592 let x = k * (self.points[0].x - 2.0 * self.points[1].x + self.points[2].x);
593 let y = k * (self.points[0].y - 2.0 * self.points[1].y + self.points[2].y);
594
595 let mut error = (x * x + y * y).sqrt();
596 let mut pow2 = 0;
597 for _ in 0..MAX_CONIC_TO_QUAD_POW2 {
598 if error <= tolerance {
599 break;
600 }
601
602 error *= 0.25;
603 pow2 += 1;
604 }
605
606 Some(pow2.max(1))
609 }
610
611 pub fn chop_into_quads_pow2(&self, pow2: u8, points: &mut [Point]) -> u8 {
614 debug_assert!(pow2 < 5);
615
616 points[0] = self.points[0];
617 subdivide(self, &mut points[1..], pow2);
618
619 let quad_count = 1 << pow2;
620 let pt_count = 2 * quad_count + 1;
621 if points.iter().take(pt_count).any(|n| !n.is_finite()) {
622 for p in points.iter_mut().take(pt_count - 1).skip(1) {
625 *p = self.points[1];
626 }
627 }
628
629 1 << pow2
630 }
631
632 fn chop(&self) -> (Conic, Conic) {
633 let scale = f32x2::splat((1.0 + self.weight).invert());
634 let new_w = subdivide_weight_value(self.weight);
635
636 let p0 = self.points[0].to_f32x2();
637 let p1 = self.points[1].to_f32x2();
638 let p2 = self.points[2].to_f32x2();
639 let ww = f32x2::splat(self.weight);
640
641 let wp1 = ww * p1;
642 let m = (p0 + times_2(wp1) + p2) * scale * f32x2::splat(0.5);
643 let mut m_pt = Point::from_f32x2(m);
644 if !m_pt.is_finite() {
645 let w_d = self.weight as f64;
646 let w_2 = w_d * 2.0;
647 let scale_half = 1.0 / (1.0 + w_d) * 0.5;
648 m_pt.x = ((self.points[0].x as f64
649 + w_2 * self.points[1].x as f64
650 + self.points[2].x as f64)
651 * scale_half) as f32;
652
653 m_pt.y = ((self.points[0].y as f64
654 + w_2 * self.points[1].y as f64
655 + self.points[2].y as f64)
656 * scale_half) as f32;
657 }
658
659 (
660 Conic {
661 points: [self.points[0], Point::from_f32x2((p0 + wp1) * scale), m_pt],
662 weight: new_w,
663 },
664 Conic {
665 points: [m_pt, Point::from_f32x2((wp1 + p2) * scale), self.points[2]],
666 weight: new_w,
667 },
668 )
669 }
670
671 pub fn build_unit_arc(
672 u_start: Point,
673 u_stop: Point,
674 dir: PathDirection,
675 user_transform: Transform,
676 dst: &mut [Conic; 5],
677 ) -> Option<&[Conic]> {
678 let x = u_start.dot(u_stop);
680 let mut y = u_start.cross(u_stop);
681
682 let abs_y = y.abs();
683
684 if abs_y <= SCALAR_NEARLY_ZERO
688 && x > 0.0
689 && ((y >= 0.0 && dir == PathDirection::CW) || (y <= 0.0 && dir == PathDirection::CCW))
690 {
691 return None;
692 }
693
694 if dir == PathDirection::CCW {
695 y = -y;
696 }
697
698 let mut quadrant = 0;
705 if y == 0.0 {
706 quadrant = 2; debug_assert!((x + 1.0) <= SCALAR_NEARLY_ZERO);
708 } else if x == 0.0 {
709 debug_assert!(abs_y - 1.0 <= SCALAR_NEARLY_ZERO);
710 quadrant = if y > 0.0 { 1 } else { 3 }; } else {
712 if y < 0.0 {
713 quadrant += 2;
714 }
715
716 if (x < 0.0) != (y < 0.0) {
717 quadrant += 1;
718 }
719 }
720
721 let quadrant_points = [
722 Point::from_xy(1.0, 0.0),
723 Point::from_xy(1.0, 1.0),
724 Point::from_xy(0.0, 1.0),
725 Point::from_xy(-1.0, 1.0),
726 Point::from_xy(-1.0, 0.0),
727 Point::from_xy(-1.0, -1.0),
728 Point::from_xy(0.0, -1.0),
729 Point::from_xy(1.0, -1.0),
730 ];
731
732 const QUADRANT_WEIGHT: f32 = SCALAR_ROOT_2_OVER_2;
733
734 let mut conic_count = quadrant;
735 for i in 0..conic_count {
736 dst[i] = Conic::from_points(&quadrant_points[i * 2..], QUADRANT_WEIGHT);
737 }
738
739 let final_pt = Point::from_xy(x, y);
741 let last_q = quadrant_points[quadrant * 2]; let dot = last_q.dot(final_pt);
743 debug_assert!(0.0 <= dot && dot <= 1.0 + SCALAR_NEARLY_ZERO);
744
745 if dot < 1.0 {
746 let mut off_curve = Point::from_xy(last_q.x + x, last_q.y + y);
747 let cos_theta_over_2 = ((1.0 + dot) / 2.0).sqrt();
752 off_curve.set_length(cos_theta_over_2.invert());
753 if !last_q.almost_equal(off_curve) {
754 dst[conic_count] = Conic::new(last_q, off_curve, final_pt, cos_theta_over_2);
755 conic_count += 1;
756 }
757 }
758
759 let mut transform = Transform::from_sin_cos(u_start.y, u_start.x);
761 if dir == PathDirection::CCW {
762 transform = transform.pre_scale(1.0, -1.0);
763 }
764
765 transform = transform.post_concat(user_transform);
766
767 for conic in dst.iter_mut().take(conic_count) {
768 transform.map_points(&mut conic.points);
769 }
770
771 if conic_count == 0 {
772 None
773 } else {
774 Some(&dst[0..conic_count])
775 }
776 }
777}
778
779fn subdivide_weight_value(w: f32) -> f32 {
780 (0.5 + w * 0.5).sqrt()
781}
782
783fn subdivide<'a>(src: &Conic, mut points: &'a mut [Point], mut level: u8) -> &'a mut [Point] {
784 if level == 0 {
785 points[0] = src.points[1];
786 points[1] = src.points[2];
787 &mut points[2..]
788 } else {
789 let mut dst = src.chop();
790
791 let start_y = src.points[0].y;
792 let end_y = src.points[2].y;
793 if between(start_y, src.points[1].y, end_y) {
794 let mid_y = dst.0.points[2].y;
797 if !between(start_y, mid_y, end_y) {
798 let closer_y = if (mid_y - start_y).abs() < (mid_y - end_y).abs() {
800 start_y
801 } else {
802 end_y
803 };
804 dst.0.points[2].y = closer_y;
805 dst.1.points[0].y = closer_y;
806 }
807
808 if !between(start_y, dst.0.points[1].y, dst.0.points[2].y) {
809 dst.0.points[1].y = start_y;
812 }
813
814 if !between(dst.1.points[0].y, dst.1.points[1].y, end_y) {
815 dst.1.points[1].y = end_y;
818 }
819
820 debug_assert!(between(start_y, dst.0.points[1].y, dst.0.points[2].y));
822 debug_assert!(between(
823 dst.0.points[1].y,
824 dst.0.points[2].y,
825 dst.1.points[1].y
826 ));
827 debug_assert!(between(dst.0.points[2].y, dst.1.points[1].y, end_y));
828 }
829
830 level -= 1;
831 points = subdivide(&dst.0, points, level);
832 subdivide(&dst.1, points, level)
833 }
834}
835
836fn between(a: f32, b: f32, c: f32) -> bool {
839 (a - b) * (c - b) <= 0.0
840}
841
842pub(crate) struct AutoConicToQuads {
843 pub points: [Point; 64],
844 pub len: u8, }
846
847impl AutoConicToQuads {
848 pub fn compute(pt0: Point, pt1: Point, pt2: Point, weight: f32) -> Option<Self> {
849 let conic = Conic::new(pt0, pt1, pt2, weight);
850 let pow2 = conic.compute_quad_pow2(0.25)?;
851 let mut points = [Point::zero(); 64];
852 let len = conic.chop_into_quads_pow2(pow2, &mut points);
853 Some(AutoConicToQuads { points, len })
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860
861 #[test]
862 fn eval_cubic_at_1() {
863 let src = [
864 Point::from_xy(30.0, 40.0),
865 Point::from_xy(30.0, 40.0),
866 Point::from_xy(171.0, 45.0),
867 Point::from_xy(180.0, 155.0),
868 ];
869
870 assert_eq!(
871 eval_cubic_pos_at(&src, NormalizedF32::ZERO),
872 Point::from_xy(30.0, 40.0)
873 );
874 assert_eq!(
875 eval_cubic_tangent_at(&src, NormalizedF32::ZERO),
876 Point::from_xy(141.0, 5.0)
877 );
878 }
879
880 #[test]
881 fn find_cubic_max_curvature_1() {
882 let src = [
883 Point::from_xy(20.0, 160.0),
884 Point::from_xy(20.0001, 160.0),
885 Point::from_xy(160.0, 20.0),
886 Point::from_xy(160.0001, 20.0),
887 ];
888
889 let mut t_values = [NormalizedF32::ZERO; 3];
890 let t_values = find_cubic_max_curvature(&src, &mut t_values);
891
892 assert_eq!(
893 &t_values,
894 &[
895 NormalizedF32::ZERO,
896 NormalizedF32::new_clamped(0.5),
897 NormalizedF32::ONE,
898 ]
899 );
900 }
901}