1use crate::scalar::Scalar;
2use crate::CubicBezierSegment;
3use crate::{point, Box2D, Point};
12use arrayvec::ArrayVec;
13use core::mem;
14use core::ops::Range;
15
16pub fn cubic_bezier_intersections_t<S: Scalar>(
24 curve1: &CubicBezierSegment<S>,
25 curve2: &CubicBezierSegment<S>,
26) -> ArrayVec<(S, S), 9> {
27 if !curve1
28 .fast_bounding_box()
29 .intersects(&curve2.fast_bounding_box())
30 || curve1 == curve2
31 || (curve1.from == curve2.to
32 && curve1.ctrl1 == curve2.ctrl2
33 && curve1.ctrl2 == curve2.ctrl1
34 && curve1.to == curve2.from)
35 {
36 return ArrayVec::new();
37 }
38
39 let mut result = ArrayVec::new();
40
41 #[inline]
42 fn midpoint<S: Scalar>(point1: &Point<S>, point2: &Point<S>) -> Point<S> {
43 point(
44 (point1.x + point2.x) * S::HALF,
45 (point1.y + point2.y) * S::HALF,
46 )
47 }
48
49 let curve1_is_a_point = curve1.is_a_point(S::EPSILON);
50 let curve2_is_a_point = curve2.is_a_point(S::EPSILON);
51 if curve1_is_a_point && !curve2_is_a_point {
52 let point1 = midpoint(&curve1.from, &curve1.to);
53 let curve_params = point_curve_intersections(&point1, curve2, S::EPSILON);
54 for t in curve_params {
55 if t > S::EPSILON && t < S::ONE - S::EPSILON {
56 result.push((S::ZERO, t));
57 }
58 }
59 } else if !curve1_is_a_point && curve2_is_a_point {
60 let point2 = midpoint(&curve2.from, &curve2.to);
61 let curve_params = point_curve_intersections(&point2, curve1, S::EPSILON);
62 for t in curve_params {
63 if t > S::EPSILON && t < S::ONE - S::EPSILON {
64 result.push((t, S::ZERO));
65 }
66 }
67 }
68 if curve1_is_a_point || curve2_is_a_point {
69 return result;
72 }
73
74 let linear1 = curve1.is_linear(S::EPSILON);
75 let linear2 = curve2.is_linear(S::EPSILON);
76 if linear1 && !linear2 {
77 result = line_curve_intersections(curve1, curve2, false);
78 } else if !linear1 && linear2 {
79 result = line_curve_intersections(curve2, curve1, true);
80 } else if linear1 && linear2 {
81 result = line_line_intersections(curve1, curve2);
82 } else {
83 add_curve_intersections(
84 curve1,
85 curve2,
86 &(S::ZERO..S::ONE),
87 &(S::ZERO..S::ONE),
88 &mut result,
89 false,
90 0,
91 0,
92 curve1,
93 curve2,
94 );
95 }
96
97 result
98}
99
100fn point_curve_intersections<S: Scalar>(
101 pt: &Point<S>,
102 curve: &CubicBezierSegment<S>,
103 epsilon: S,
104) -> ArrayVec<S, 9> {
105 let mut result = ArrayVec::new();
106
107 if (*pt - curve.from).square_length() < epsilon {
109 result.push(S::ZERO);
110 return result;
111 }
112 if (*pt - curve.to).square_length() < epsilon {
113 result.push(S::ONE);
114 return result;
115 }
116
117 let curve_x_t_params = curve.solve_t_for_x(pt.x);
118 let curve_y_t_params = curve.solve_t_for_y(pt.y);
119 let param_eps = S::TEN * epsilon;
124 for params in [curve_x_t_params, curve_y_t_params].iter() {
125 for t in params {
126 let t = *t;
127 if (*pt - curve.sample(t)).square_length() > epsilon {
128 continue;
129 }
130 let mut already_found_t = false;
131 for u in &result {
132 if S::abs(t - *u) < param_eps {
133 already_found_t = true;
134 break;
135 }
136 }
137 if !already_found_t {
138 result.push(t);
139 }
140 }
141 }
142
143 if !result.is_empty() {
144 return result;
145 }
146
147 #[inline]
153 fn maybe_add<S: Scalar>(
154 t: S,
155 pt: &Point<S>,
156 curve: &CubicBezierSegment<S>,
157 epsilon: S,
158 result: &mut ArrayVec<S, 9>,
159 ) -> bool {
160 if (curve.sample(t) - *pt).square_length() < epsilon {
161 result.push(t);
162 return true;
163 }
164 false
165 }
166
167 let _ = maybe_add(curve.x_minimum_t(), pt, curve, epsilon, &mut result)
168 || maybe_add(curve.x_maximum_t(), pt, curve, epsilon, &mut result)
169 || maybe_add(curve.y_minimum_t(), pt, curve, epsilon, &mut result)
170 || maybe_add(curve.y_maximum_t(), pt, curve, epsilon, &mut result);
171
172 result
173}
174
175fn line_curve_intersections<S: Scalar>(
176 line_as_curve: &CubicBezierSegment<S>,
177 curve: &CubicBezierSegment<S>,
178 flip: bool,
179) -> ArrayVec<(S, S), 9> {
180 let mut result = ArrayVec::new();
181 let baseline = line_as_curve.baseline();
182 let curve_intersections = curve.line_intersections_t(&baseline.to_line());
183 let line_is_mostly_vertical =
184 S::abs(baseline.from.y - baseline.to.y) >= S::abs(baseline.from.x - baseline.to.x);
185 for curve_t in curve_intersections {
186 let line_intersections = if line_is_mostly_vertical {
187 let intersection_y = curve.y(curve_t);
188 line_as_curve.solve_t_for_y(intersection_y)
189 } else {
190 let intersection_x = curve.x(curve_t);
191 line_as_curve.solve_t_for_x(intersection_x)
192 };
193
194 for line_t in line_intersections {
195 add_intersection(line_t, line_as_curve, curve_t, curve, flip, &mut result);
196 }
197 }
198
199 result
200}
201
202fn line_line_intersections<S: Scalar>(
203 curve1: &CubicBezierSegment<S>,
204 curve2: &CubicBezierSegment<S>,
205) -> ArrayVec<(S, S), 9> {
206 let mut result = ArrayVec::new();
207
208 let intersection = curve1
209 .baseline()
210 .to_line()
211 .intersection(&curve2.baseline().to_line());
212 if intersection.is_none() {
213 return result;
214 }
215
216 let intersection = intersection.unwrap();
217
218 #[inline]
219 fn parameters_for_line_point<S: Scalar>(
220 curve: &CubicBezierSegment<S>,
221 pt: &Point<S>,
222 ) -> ArrayVec<S, 3> {
223 let line_is_mostly_vertical =
224 S::abs(curve.from.y - curve.to.y) >= S::abs(curve.from.x - curve.to.x);
225 if line_is_mostly_vertical {
226 curve.solve_t_for_y(pt.y)
227 } else {
228 curve.solve_t_for_x(pt.x)
229 }
230 }
231
232 let line1_params = parameters_for_line_point(curve1, &intersection);
233 if line1_params.is_empty() {
234 return result;
235 }
236
237 let line2_params = parameters_for_line_point(curve2, &intersection);
238 if line2_params.is_empty() {
239 return result;
240 }
241
242 for t1 in &line1_params {
243 for t2 in &line2_params {
244 add_intersection(*t1, curve1, *t2, curve2, false, &mut result);
247 }
248 }
249
250 result
251}
252
253#[allow(clippy::too_many_arguments)]
263fn add_curve_intersections<S: Scalar>(
264 curve1: &CubicBezierSegment<S>,
265 curve2: &CubicBezierSegment<S>,
266 domain1: &Range<S>,
267 domain2: &Range<S>,
268 intersections: &mut ArrayVec<(S, S), 9>,
269 flip: bool,
270 mut recursion_count: u32,
271 mut call_count: u32,
272 orig_curve1: &CubicBezierSegment<S>,
273 orig_curve2: &CubicBezierSegment<S>,
274) -> u32 {
275 call_count += 1;
276 recursion_count += 1;
277 if call_count >= 4096 || recursion_count >= 60 {
278 return call_count;
279 }
280
281 let epsilon = if inputs_are_f32::<S>() {
282 S::value(5e-6)
283 } else {
284 S::value(1e-9)
285 };
286
287 if domain2.start == domain2.end || curve2.is_a_point(S::ZERO) {
288 add_point_curve_intersection(
289 curve2,
290 false,
291 curve1,
292 domain2,
293 domain1,
294 intersections,
295 flip,
296 );
297 return call_count;
298 } else if curve2.from == curve2.to {
299 let new_2_curves = orig_curve2.split_range(domain2.clone()).split(S::HALF);
302 let domain2_mid = (domain2.start + domain2.end) * S::HALF;
303 call_count = add_curve_intersections(
304 curve1,
305 &new_2_curves.0,
306 domain1,
307 &(domain2.start..domain2_mid),
308 intersections,
309 flip,
310 recursion_count,
311 call_count,
312 orig_curve1,
313 orig_curve2,
314 );
315 call_count = add_curve_intersections(
316 curve1,
317 &new_2_curves.1,
318 domain1,
319 &(domain2_mid..domain2.end),
320 intersections,
321 flip,
322 recursion_count,
323 call_count,
324 orig_curve1,
325 orig_curve2,
326 );
327 return call_count;
328 }
329
330 if !rectangles_overlap(&curve1.fast_bounding_box(), &curve2.fast_bounding_box()) {
333 return call_count;
334 }
335
336 let (t_min_clip, t_max_clip) = match restrict_curve_to_fat_line(curve1, curve2) {
337 Some((min, max)) => (min, max),
338 None => return call_count,
339 };
340
341 let new_domain1 =
344 &(domain_value_at_t(domain1, t_min_clip)..domain_value_at_t(domain1, t_max_clip));
345
346 if S::max(
347 domain2.end - domain2.start,
348 new_domain1.end - new_domain1.start,
349 ) < epsilon
350 {
351 let t1 = (new_domain1.start + new_domain1.end) * S::HALF;
352 let t2 = (domain2.start + domain2.end) * S::HALF;
353 if inputs_are_f32::<S>() {
354 let end_eps = S::value(1e-3);
358 if (t2 < end_eps || t2 > S::ONE - end_eps)
359 && (orig_curve1.sample(t1) - orig_curve2.sample(t2)).length() > S::FIVE
360 {
361 return call_count;
362 }
363 }
364 add_intersection(t1, orig_curve1, t2, orig_curve2, flip, intersections);
365 return call_count;
366 }
367
368 let curve1 = &orig_curve1.split_range(new_domain1.clone());
370
371 if new_domain1.start == new_domain1.end || curve1.is_a_point(S::ZERO) {
375 add_point_curve_intersection(
376 curve1,
377 true,
378 curve2,
379 new_domain1,
380 domain2,
381 intersections,
382 flip,
383 );
384 return call_count;
385 }
386
387 if t_max_clip - t_min_clip > S::EIGHT / S::TEN {
389 if new_domain1.end - new_domain1.start > domain2.end - domain2.start {
391 let new_1_curves = curve1.split(S::HALF);
392 let new_domain1_mid = (new_domain1.start + new_domain1.end) * S::HALF;
393 call_count = add_curve_intersections(
394 curve2,
395 &new_1_curves.0,
396 domain2,
397 &(new_domain1.start..new_domain1_mid),
398 intersections,
399 !flip,
400 recursion_count,
401 call_count,
402 orig_curve2,
403 orig_curve1,
404 );
405 call_count = add_curve_intersections(
406 curve2,
407 &new_1_curves.1,
408 domain2,
409 &(new_domain1_mid..new_domain1.end),
410 intersections,
411 !flip,
412 recursion_count,
413 call_count,
414 orig_curve2,
415 orig_curve1,
416 );
417 } else {
418 let new_2_curves = orig_curve2.split_range(domain2.clone()).split(S::HALF);
419 let domain2_mid = (domain2.start + domain2.end) * S::HALF;
420 call_count = add_curve_intersections(
421 &new_2_curves.0,
422 curve1,
423 &(domain2.start..domain2_mid),
424 new_domain1,
425 intersections,
426 !flip,
427 recursion_count,
428 call_count,
429 orig_curve2,
430 orig_curve1,
431 );
432 call_count = add_curve_intersections(
433 &new_2_curves.1,
434 curve1,
435 &(domain2_mid..domain2.end),
436 new_domain1,
437 intersections,
438 !flip,
439 recursion_count,
440 call_count,
441 orig_curve2,
442 orig_curve1,
443 );
444 }
445 } else {
446 if domain2.end - domain2.start >= epsilon {
448 call_count = add_curve_intersections(
449 curve2,
450 curve1,
451 domain2,
452 new_domain1,
453 intersections,
454 !flip,
455 recursion_count,
456 call_count,
457 orig_curve2,
458 orig_curve1,
459 );
460 } else {
461 call_count = add_curve_intersections(
463 curve1,
464 curve2,
465 new_domain1,
466 domain2,
467 intersections,
468 flip,
469 recursion_count,
470 call_count,
471 orig_curve1,
472 orig_curve2,
473 );
474 }
475 }
476
477 call_count
478}
479
480fn add_point_curve_intersection<S: Scalar>(
481 pt_curve: &CubicBezierSegment<S>,
482 pt_curve_is_curve1: bool,
483 curve: &CubicBezierSegment<S>,
484 pt_domain: &Range<S>,
485 curve_domain: &Range<S>,
486 intersections: &mut ArrayVec<(S, S), 9>,
487 flip: bool,
488) {
489 let pt = pt_curve.from;
490 let flip = if pt_curve_is_curve1 { flip } else { !flip };
492
493 let epsilon = epsilon_for_point(&pt);
497 let pt_t = (pt_domain.start + pt_domain.end) * S::HALF;
498
499 let curve_t = {
500 let mut t_for_min = S::ZERO;
501 let mut min_dist_sq = epsilon;
502 let tenths = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
503 for &t in tenths.iter() {
504 let t = S::value(t);
505 let d = (pt - curve.sample(t)).square_length();
506 if d < min_dist_sq {
507 t_for_min = t;
508 min_dist_sq = d;
509 }
510 }
511
512 if min_dist_sq == epsilon {
513 -S::ONE
514 } else {
515 let curve_t = domain_value_at_t(curve_domain, t_for_min);
516 curve_t
517 }
518 };
519
520 if curve_t != -S::ONE {
521 add_intersection(pt_t, pt_curve, curve_t, curve, flip, intersections);
522 return;
523 }
524
525 let results = point_curve_intersections(&pt, curve, epsilon);
527 for t in results {
528 let curve_t = domain_value_at_t(curve_domain, t);
529 add_intersection(pt_t, pt_curve, curve_t, curve, flip, intersections);
530 }
531}
532
533fn epsilon_for_point<S: Scalar>(pt: &Point<S>) -> S {
537 let max = S::max(S::abs(pt.x), S::abs(pt.y));
538 let epsilon = if inputs_are_f32::<S>() {
539 match max.to_i32().unwrap() {
540 0..=9 => S::value(0.001),
541 10..=99 => S::value(0.01),
542 100..=999 => S::value(0.1),
543 1_000..=9_999 => S::value(0.25),
544 10_000..=999_999 => S::HALF,
545 _ => S::ONE,
546 }
547 } else {
548 match max.to_i64().unwrap() {
549 0..=99_999 => S::EPSILON,
550 100_000..=99_999_999 => S::value(1e-5),
551 100_000_000..=9_999_999_999 => S::value(1e-3),
552 _ => S::value(1e-1),
553 }
554 };
555
556 epsilon
557}
558
559fn add_intersection<S: Scalar>(
560 t1: S,
561 orig_curve1: &CubicBezierSegment<S>,
562 t2: S,
563 orig_curve2: &CubicBezierSegment<S>,
564 flip: bool,
565 intersections: &mut ArrayVec<(S, S), 9>,
566) {
567 let (t1, t2) = if flip { (t2, t1) } else { (t1, t2) };
568 let epsilon = if inputs_are_f32::<S>() {
570 S::value(1e-3)
571 } else {
572 S::EPSILON
573 };
574 let t1_is_an_endpoint = t1 < epsilon || t1 > S::ONE - epsilon;
576 let t2_is_an_endpoint = t2 < epsilon || t2 > S::ONE - epsilon;
577 if t1_is_an_endpoint && t2_is_an_endpoint {
578 return;
579 }
580
581 for i in 0..intersections.len() {
585 let (old_t1, old_t2) = intersections[i];
586 if S::abs(t1 - old_t1) < epsilon && S::abs(t2 - old_t2) < epsilon {
589 let cur_dist =
590 (orig_curve1.sample(old_t1) - orig_curve2.sample(old_t2)).square_length();
591 let new_dist = (orig_curve1.sample(t1) - orig_curve2.sample(t2)).square_length();
592 if new_dist < cur_dist {
593 intersections[i] = (t1, t2);
594 }
595 return;
596 }
597 }
598
599 if intersections.len() < 9 {
600 intersections.push((t1, t2));
601 }
602}
603
604fn restrict_curve_to_fat_line<S: Scalar>(
608 curve1: &CubicBezierSegment<S>,
609 curve2: &CubicBezierSegment<S>,
610) -> Option<(S, S)> {
611 let baseline2 = curve2.baseline().to_line().equation();
617
618 let d_0 = baseline2.signed_distance_to_point(&curve1.from);
619 let d_1 = baseline2.signed_distance_to_point(&curve1.ctrl1);
620 let d_2 = baseline2.signed_distance_to_point(&curve1.ctrl2);
621 let d_3 = baseline2.signed_distance_to_point(&curve1.to);
622
623 let mut top = ArrayVec::new();
624 let mut bottom = ArrayVec::new();
625 convex_hull_of_distance_curve(d_0, d_1, d_2, d_3, &mut top, &mut bottom);
626 let (d_min, d_max) = curve2.fat_line_min_max();
627
628 clip_convex_hull_to_fat_line(&mut top, &mut bottom, d_min, d_max)
629}
630
631fn convex_hull_of_distance_curve<S: Scalar>(
636 d0: S,
637 d1: S,
638 d2: S,
639 d3: S,
640 top: &mut ArrayVec<Point<S>, 4>,
641 bottom: &mut ArrayVec<Point<S>, 4>,
642) {
643 debug_assert!(top.is_empty());
644 debug_assert!(bottom.is_empty());
645
646 let p0 = point(S::ZERO, d0);
647 let p1 = point(S::ONE / S::THREE, d1);
648 let p2 = point(S::TWO / S::THREE, d2);
649 let p3 = point(S::ONE, d3);
650 let dist1 = d1 - (S::TWO * d0 + d3) / S::THREE;
652 let dist2 = d2 - (d0 + S::TWO * d3) / S::THREE;
653
654 if dist1 * dist2 < S::ZERO {
656 let _ = top.try_extend_from_slice(&[p0, p1, p3]);
658 let _ = bottom.try_extend_from_slice(&[p0, p2, p3]);
659 } else {
660 let dist1 = S::abs(dist1);
665 let dist2 = S::abs(dist2);
666 if dist1 >= S::TWO * dist2 {
667 let _ = top.try_extend_from_slice(&[p0, p1, p3]);
668 let _ = bottom.try_extend_from_slice(&[p0, p3]);
669 } else if dist2 >= S::TWO * dist1 {
670 let _ = top.try_extend_from_slice(&[p0, p2, p3]);
671 let _ = bottom.try_extend_from_slice(&[p0, p3]);
672 } else {
673 let _ = top.try_extend_from_slice(&[p0, p1, p2, p3]);
674 let _ = bottom.try_extend_from_slice(&[p0, p3]);
675 }
676 }
677
678 if dist1 < S::ZERO || (dist1 == S::ZERO && dist2 < S::ZERO) {
680 mem::swap(top, bottom);
681 }
682}
683
684fn clip_convex_hull_to_fat_line<S: Scalar>(
686 hull_top: &mut [Point<S>],
687 hull_bottom: &mut [Point<S>],
688 d_min: S,
689 d_max: S,
690) -> Option<(S, S)> {
691 let t_clip_min = walk_convex_hull_start_to_fat_line(hull_top, hull_bottom, d_min, d_max)?;
693
694 hull_top.reverse();
698 hull_bottom.reverse();
699 let t_clip_max = walk_convex_hull_start_to_fat_line(hull_top, hull_bottom, d_min, d_max)?;
700
701 Some((t_clip_min, t_clip_max))
702}
703
704fn walk_convex_hull_start_to_fat_line<S: Scalar>(
707 hull_top_vertices: &[Point<S>],
708 hull_bottom_vertices: &[Point<S>],
709 d_min: S,
710 d_max: S,
711) -> Option<S> {
712 let start_corner = hull_top_vertices[0];
713
714 if start_corner.y < d_min {
715 walk_convex_hull_edges_to_fat_line(hull_top_vertices, true, d_min)
716 } else if start_corner.y > d_max {
717 walk_convex_hull_edges_to_fat_line(hull_bottom_vertices, false, d_max)
718 } else {
719 Some(start_corner.x)
720 }
721}
722
723fn walk_convex_hull_edges_to_fat_line<S: Scalar>(
725 hull_vertices: &[Point<S>],
726 vertices_are_for_top: bool,
727 threshold: S,
728) -> Option<S> {
729 for i in 0..hull_vertices.len() - 1 {
730 let p = hull_vertices[i];
731 let q = hull_vertices[i + 1];
732 if (vertices_are_for_top && q.y >= threshold) || (!vertices_are_for_top && q.y <= threshold)
733 {
734 return if q.y == threshold {
735 Some(q.x)
736 } else {
737 Some(p.x + (threshold - p.y) * (q.x - p.x) / (q.y - p.y))
738 };
739 }
740 }
741 None
743}
744
745#[inline]
746fn inputs_are_f32<S: Scalar>() -> bool {
747 S::EPSILON > S::value(1e-6)
748}
749
750#[inline]
751fn domain_value_at_t<S: Scalar>(domain: &Range<S>, t: S) -> S {
753 domain.start + (domain.end - domain.start) * t
754}
755
756#[inline]
757fn rectangles_overlap<S: Scalar>(r1: &Box2D<S>, r2: &Box2D<S>) -> bool {
759 r1.min.x <= r2.max.x && r2.min.x <= r1.max.x && r1.min.y <= r2.max.y && r2.min.y <= r1.max.y
760}
761
762#[cfg(test)]
763fn do_test<S: Scalar>(
764 curve1: &CubicBezierSegment<S>,
765 curve2: &CubicBezierSegment<S>,
766 intersection_count: i32,
767) {
768 do_test_once(curve1, curve2, intersection_count);
769 do_test_once(curve2, curve1, intersection_count);
770}
771
772#[cfg(test)]
773fn do_test_once<S: Scalar>(
774 curve1: &CubicBezierSegment<S>,
775 curve2: &CubicBezierSegment<S>,
776 intersection_count: i32,
777) {
778 let intersections = cubic_bezier_intersections_t(curve1, curve2);
779 for intersection in &intersections {
780 let p1 = curve1.sample(intersection.0);
781 let p2 = curve2.sample(intersection.1);
782 check_dist(&p1, &p2);
783 }
784
785 assert_eq!(intersections.len() as i32, intersection_count);
786}
787
788#[cfg(test)]
789fn check_dist<S: Scalar>(p1: &Point<S>, p2: &Point<S>) {
790 let dist = S::sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
791 if dist > S::HALF {
792 assert!(false, "Intersection points too far apart.");
793 }
794}
795
796#[test]
797fn test_cubic_curve_curve_intersections() {
798 do_test(
799 &CubicBezierSegment {
800 from: point(0.0, 0.0),
801 ctrl1: point(0.0, 1.0),
802 ctrl2: point(0.0, 1.0),
803 to: point(1.0, 1.0),
804 },
805 &CubicBezierSegment {
806 from: point(0.0, 1.0),
807 ctrl1: point(1.0, 1.0),
808 ctrl2: point(1.0, 1.0),
809 to: point(1.0, 0.0),
810 },
811 1,
812 );
813 do_test(
814 &CubicBezierSegment {
815 from: point(48.0f32, 84.0),
816 ctrl1: point(104.0, 176.0),
817 ctrl2: point(190.0, 37.0),
818 to: point(121.0, 75.0),
819 },
820 &CubicBezierSegment {
821 from: point(68.0, 145.0),
822 ctrl1: point(74.0, 6.0),
823 ctrl2: point(143.0, 197.0),
824 to: point(138.0, 55.0),
825 },
826 4,
827 );
828 do_test(
829 &CubicBezierSegment {
830 from: point(0.0, 0.0),
831 ctrl1: point(0.5, 1.0),
832 ctrl2: point(0.5, 1.0),
833 to: point(1.0, 0.0),
834 },
835 &CubicBezierSegment {
836 from: point(0.0, 1.0),
837 ctrl1: point(0.5, 0.0),
838 ctrl2: point(0.5, 0.0),
839 to: point(1.0, 1.0),
840 },
841 2,
842 );
843 do_test(
844 &CubicBezierSegment {
845 from: point(0.2, 0.0),
846 ctrl1: point(0.5, 3.0),
847 ctrl2: point(0.5, -2.0),
848 to: point(0.8, 1.0),
849 },
850 &CubicBezierSegment {
851 from: point(0.0, 0.0),
852 ctrl1: point(2.5, 0.5),
853 ctrl2: point(-1.5, 0.5),
854 to: point(1.0, 0.0),
855 },
856 9,
857 );
858
859 do_test(
862 &CubicBezierSegment {
863 from: point(718133.1363092018, 673674.987999388),
864 ctrl1: point(-53014.13135835016, 286988.87959900266),
865 ctrl2: point(-900630.1880107201, -7527.6889376943),
866 to: point(417822.48349384824, -149039.14932848653),
867 },
868 &CubicBezierSegment {
869 from: point(924715.3309247112, 719414.5221912428),
870 ctrl1: point(965365.9679664494, -563421.3040676294),
871 ctrl2: point(273552.85484064696, 643090.0890117711),
872 to: point(-113963.134524995, 732017.9466050486),
873 },
874 1,
875 );
876
877 do_test(
882 &CubicBezierSegment {
883 from: point(423394.5967598548, -91342.7434613118),
884 ctrl1: point(333212.450870987, 225564.45711810607),
885 ctrl2: point(668108.668469816, -626100.8367380127),
886 to: point(-481885.0610437216, 893767.5320803947),
887 },
888 &CubicBezierSegment {
889 from: point(-484505.2601961801, -222621.44229855016),
890 ctrl1: point(22432.829984141514, -944727.7102144773),
891 ctrl2: point(-433294.66549074976, -168018.60431004688),
892 to: point(567688.5977972192, 13975.09633399453),
893 },
894 3,
895 );
896}
897
898#[test]
899fn test_cubic_control_point_touching() {
900 do_test(
905 &CubicBezierSegment {
906 from: point(-1.0, 0.0),
907 ctrl1: point(0.0, 0.0),
908 ctrl2: point(-1.0, -0.1),
909 to: point(-1.0, -0.1),
910 },
911 &CubicBezierSegment {
912 from: point(0.0, 0.0),
913 ctrl1: point(5.0, -5.0),
914 ctrl2: point(-5.0, -5.0),
915 to: point(0.0, 0.0),
916 },
917 0,
918 );
919}
920
921#[test]
922fn test_cubic_self_intersections() {
923 do_test(
925 &CubicBezierSegment {
926 from: point(-10.0, -13.636363636363636),
927 ctrl1: point(15.0, 11.363636363636363),
928 ctrl2: point(-15.0, 11.363636363636363),
929 to: point(10.0, -13.636363636363636),
930 },
931 &CubicBezierSegment {
932 from: point(13.636363636363636, -10.0),
933 ctrl1: point(-11.363636363636363, 15.0),
934 ctrl2: point(-11.363636363636363, -15.0),
935 to: point(13.636363636363636, 10.0),
936 },
937 4,
938 );
939}
940
941#[test]
942fn test_cubic_loops() {
943 do_test(
946 &CubicBezierSegment {
947 from: point(0.0, 0.0),
948 ctrl1: point(-10.0, 10.0),
949 ctrl2: point(10.0, 10.0),
950 to: point(0.0, 0.0),
951 },
952 &CubicBezierSegment {
953 from: point(0.0, 0.0),
954 ctrl1: point(-1.0, 1.0),
955 ctrl2: point(1.0, 1.0),
956 to: point(0.0, 0.0),
957 },
958 0,
959 );
960
961 do_test(
962 &CubicBezierSegment {
963 from: point(0.0f32, 0.0),
964 ctrl1: point(-100.0, 0.0),
965 ctrl2: point(-500.0, 500.0),
966 to: point(0.0, 0.0),
967 },
968 &CubicBezierSegment {
969 from: point(0.0, 0.0),
970 ctrl1: point(-800.0, 100.0),
971 ctrl2: point(500.0, 500.0),
972 to: point(0.0, 0.0),
973 },
974 3,
975 );
976}
977
978#[test]
979fn test_cubic_line_curve_intersections() {
980 do_test(
981 &CubicBezierSegment {
982 from: point(1.0, 2.0),
984 ctrl1: point(20.0, 1.0),
985 ctrl2: point(1.0, 2.0),
986 to: point(20.0, 1.0),
987 },
988 &CubicBezierSegment {
989 from: point(1.0, 0.0),
990 ctrl1: point(1.0, 5.0),
991 ctrl2: point(20.0, 25.0),
992 to: point(20.0, 0.0),
993 },
994 2,
995 );
996
997 do_test(
998 &CubicBezierSegment {
999 from: point(0.0f32, 0.0),
1001 ctrl1: point(-10.0, 0.0),
1002 ctrl2: point(20.0, 0.0),
1003 to: point(10.0, 0.0),
1004 },
1005 &CubicBezierSegment {
1006 from: point(-1.0, -1.0),
1007 ctrl1: point(0.0, 4.0),
1008 ctrl2: point(10.0, -4.0),
1009 to: point(11.0, 1.0),
1010 },
1011 5,
1012 );
1013
1014 do_test(
1015 &CubicBezierSegment {
1016 from: point(-1.0, -2.0),
1017 ctrl1: point(-1.0, 8.0),
1018 ctrl2: point(1.0, -8.0),
1019 to: point(1.0, 2.0),
1020 },
1021 &CubicBezierSegment {
1022 from: point(-10.0, -10.0),
1024 ctrl1: point(20.0, 20.0),
1025 ctrl2: point(-20.0, -20.0),
1026 to: point(10.0, 10.0),
1027 },
1028 9,
1029 );
1030}
1031
1032#[test]
1033fn test_cubic_line_line_intersections() {
1034 do_test(
1035 &CubicBezierSegment {
1036 from: point(-10.0, -10.0),
1037 ctrl1: point(20.0, 20.0),
1038 ctrl2: point(-20.0, -20.0),
1039 to: point(10.0, 10.0),
1040 },
1041 &CubicBezierSegment {
1042 from: point(-10.0, 10.0),
1043 ctrl1: point(20.0, -20.0),
1044 ctrl2: point(-20.0, 20.0),
1045 to: point(10.0, -10.0),
1046 },
1047 9,
1048 );
1049
1050 do_test(
1052 &CubicBezierSegment {
1053 from: point(0.0, 0.0),
1054 ctrl1: point(0.0, 0.0),
1055 ctrl2: point(10.0, 0.0),
1056 to: point(10.0, 0.0),
1057 },
1058 &CubicBezierSegment {
1059 from: point(5.0, 0.0),
1060 ctrl1: point(5.0, 0.0),
1061 ctrl2: point(15.0, 0.0),
1062 to: point(15.0, 0.0),
1063 },
1064 0,
1065 );
1066}
1067
1068#[test]
1069fn test_cubic_similar_loops() {
1073 do_test(
1074 &CubicBezierSegment {
1075 from: point(-0.281604145719379, -0.3129629924180608),
1076 ctrl1: point(-0.04393998118946163, 0.13714701102906668),
1077 ctrl2: point(0.4472584256288119, 0.2876115686206546),
1078 to: point(-0.281604145719379, -0.3129629924180608),
1079 },
1080 &CubicBezierSegment {
1081 from: point(-0.281604145719379, -0.3129629924180608),
1082 ctrl1: point(-0.1560415754252551, -0.22924729391648402),
1083 ctrl2: point(-0.9224550447067958, 0.19110227764357646),
1084 to: point(-0.281604145719379, -0.3129629924180608),
1085 },
1086 2,
1087 );
1088}
1089
1090#[test]
1091fn test_cubic_no_duplicated_root() {
1094 do_test(
1095 &CubicBezierSegment {
1096 from: point(0.0, 0.0),
1097 ctrl1: point(-10.0, 1.0),
1098 ctrl2: point(10.0, 1.0),
1099 to: point(0.0, 0.0),
1100 },
1101 &CubicBezierSegment {
1102 from: point(0.0, 0.0),
1103 ctrl1: point(-1.0, 1.0),
1104 ctrl2: point(1.0, 1.0),
1105 to: point(0.0, 0.0),
1106 },
1107 1,
1108 );
1109}
1110
1111#[test]
1112fn test_cubic_glancing_intersection() {
1113 use std::panic;
1114 let result = panic::catch_unwind(|| {
1116 do_test(
1117 &CubicBezierSegment {
1118 from: point(0.0, 0.0),
1119 ctrl1: point(0.0, 8.0),
1120 ctrl2: point(10.0, 8.0),
1121 to: point(10.0, 0.0),
1122 },
1123 &CubicBezierSegment {
1124 from: point(0.0, 12.0),
1125 ctrl1: point(0.0, 4.0),
1126 ctrl2: point(10.0, 4.0),
1127 to: point(10.0, 12.0),
1128 },
1129 1,
1130 );
1131 });
1132 assert!(result.is_err());
1133
1134 let result = panic::catch_unwind(|| {
1135 do_test(
1136 &CubicBezierSegment {
1137 from: point(0.0f32, 0.0),
1138 ctrl1: point(0.0, 8.0),
1139 ctrl2: point(10.0, 8.0),
1140 to: point(10.0, 0.0),
1141 },
1142 &CubicBezierSegment {
1143 from: point(0.0, 12.0),
1144 ctrl1: point(0.0, 4.0),
1145 ctrl2: point(10.0, 4.0),
1146 to: point(10.0, 12.0),
1147 },
1148 1,
1149 );
1150 });
1151 assert!(result.is_err());
1152}
1153
1154#[test]
1155fn test_cubic_duplicated_intersections() {
1156 use std::panic;
1157 let result = panic::catch_unwind(|| {
1158 do_test(
1162 &CubicBezierSegment {
1163 from: point(-33307.36f32, -1804.0625),
1164 ctrl1: point(-59259.727, 70098.31),
1165 ctrl2: point(98661.78, 48235.703),
1166 to: point(28422.234, 31845.219),
1167 },
1168 &CubicBezierSegment {
1169 from: point(-21501.133, 51935.344),
1170 ctrl1: point(-95301.96, 95031.45),
1171 ctrl2: point(-25882.242, -12896.75),
1172 to: point(94618.97, 94288.66),
1173 },
1174 2,
1175 );
1176 });
1177 assert!(result.is_err());
1178}
1179
1180#[test]
1181fn test_cubic_endpoint_not_an_intersection() {
1182 use std::panic;
1186 let result = panic::catch_unwind(|| {
1187 do_test(
1188 &CubicBezierSegment {
1189 from: point(76868.875f32, 47679.28),
1190 ctrl1: point(65326.86, 856.21094),
1191 ctrl2: point(-85621.64, -80823.375),
1192 to: point(-56517.53, 28062.227),
1193 },
1194 &CubicBezierSegment {
1195 from: point(-67977.72, 77673.53),
1196 ctrl1: point(-59829.57, -41917.87),
1197 ctrl2: point(57.4375, 52822.97),
1198 to: point(51075.86, 85772.84),
1199 },
1200 0,
1201 );
1202 });
1203 assert!(result.is_err());
1204}
1205
1206#[test]
1207fn test_cubic_interior_endpoint() {
1209 do_test(
1210 &CubicBezierSegment {
1211 from: point(-5.0, 0.0),
1213 ctrl1: point(-5.0, 8.0),
1214 ctrl2: point(5.0, 8.0),
1215 to: point(5.0, 0.0),
1216 },
1217 &CubicBezierSegment {
1218 from: point(0.0, 6.0),
1219 ctrl1: point(-5.0, 0.0),
1220 ctrl2: point(5.0, 0.0),
1221 to: point(0.0, 6.0),
1222 },
1223 2,
1224 );
1225}
1226
1227#[test]
1228fn test_cubic_point_curve_intersections() {
1229 let epsilon = 1e-5;
1230 {
1231 let curve1 = CubicBezierSegment {
1232 from: point(0.0, 0.0),
1233 ctrl1: point(0.0, 1.0),
1234 ctrl2: point(0.0, 1.0),
1235 to: point(1.0, 1.0),
1236 };
1237 let sample_t = 0.123456789;
1238 let pt = curve1.sample(sample_t);
1239 let curve2 = CubicBezierSegment {
1240 from: pt,
1241 ctrl1: pt,
1242 ctrl2: pt,
1243 to: pt,
1244 };
1245 let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
1246 assert_eq!(intersections.len(), 1);
1247 let intersection_t = intersections[0].0;
1248 assert!(f64::abs(intersection_t - sample_t) < epsilon);
1249 }
1250 {
1251 let curve1 = CubicBezierSegment {
1252 from: point(-10.0, -13.636363636363636),
1253 ctrl1: point(15.0, 11.363636363636363),
1254 ctrl2: point(-15.0, 11.363636363636363),
1255 to: point(10.0, -13.636363636363636),
1256 };
1257 let parameter1 = 0.7611164839335472;
1259 let parameter2 = 0.23888351606645375;
1260 let pt = curve1.sample(parameter1);
1261 let curve2 = CubicBezierSegment {
1262 from: pt,
1263 ctrl1: pt,
1264 ctrl2: pt,
1265 to: pt,
1266 };
1267 let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
1268 assert_eq!(intersections.len(), 2);
1269 let intersection_t1 = intersections[0].0;
1270 let intersection_t2 = intersections[1].0;
1271 assert!(f64::abs(intersection_t1 - parameter1) < epsilon);
1272 assert!(f64::abs(intersection_t2 - parameter2) < epsilon);
1273 }
1274 {
1275 let epsilon = epsilon as f32;
1276 let curve1 = CubicBezierSegment {
1277 from: point(0.0f32, 0.0),
1278 ctrl1: point(50.0, 50.0),
1279 ctrl2: point(-50.0, -50.0),
1280 to: point(10.0, 10.0),
1281 };
1282 let parameter1 = 0.96984464;
1284 let parameter2 = 0.037427425;
1285 let parameter3 = 0.44434106;
1286 let pt = curve1.sample(parameter1);
1287 let curve2 = CubicBezierSegment {
1288 from: pt,
1289 ctrl1: pt,
1290 ctrl2: pt,
1291 to: pt,
1292 };
1293 let intersections = cubic_bezier_intersections_t(&curve1, &curve2);
1294 assert_eq!(intersections.len(), 3);
1295 let intersection_t1 = intersections[0].0;
1296 let intersection_t2 = intersections[1].0;
1297 let intersection_t3 = intersections[2].0;
1298 assert!(f32::abs(intersection_t1 - parameter1) < epsilon);
1299 assert!(f32::abs(intersection_t2 - parameter2) < epsilon);
1300 assert!(f32::abs(intersection_t3 - parameter3) < epsilon);
1301 }
1302}
1303
1304#[test]
1305fn test_cubic_subcurve_intersections() {
1306 let curve1 = CubicBezierSegment {
1307 from: point(0.0, 0.0),
1308 ctrl1: point(0.0, 1.0),
1309 ctrl2: point(0.0, 1.0),
1310 to: point(1.0, 1.0),
1311 };
1312 let curve2 = curve1.split_range(0.25..0.75);
1313 do_test(&curve1, &curve2, 9);
1316}
1317
1318#[test]
1319fn test_cubic_result_distance() {
1320 do_test(
1326 &CubicBezierSegment {
1327 from: point(5893.133f32, -51377.152),
1328 ctrl1: point(-94403.984, 37668.156),
1329 ctrl2: point(-58914.684, 30339.195),
1330 to: point(4895.875, 83473.3),
1331 },
1332 &CubicBezierSegment {
1333 from: point(-51523.734, 75047.05),
1334 ctrl1: point(-58162.76, -91093.875),
1335 ctrl2: point(82137.516, -59844.35),
1336 to: point(46856.406, 40479.64),
1337 },
1338 3,
1339 );
1340}