1use crate::Tolerance;
15use crate::curves::{
16 BSplineCurve, Circle, Circle2D, Curve, Curve2D, ElementaryCurve, Ellipse, Hyperbola, Line,
17 Line2D, Parabola, ParametricCurve, ParametricCurve2D, TrimmedCurve, TrimmedCurve2D,
18};
19use crate::intersect::dispatch::{ExactCurveCurve, ExactCurveSurface};
20use crate::intersect::marching::marching_intersection;
21use crate::intersect::results::{
22 CurveCurveHitList, CurveCurveOverlapList, CurveSurfaceOverlapList, IntersectionCurve,
23 SurfaceSurfaceIntersection,
24};
25use crate::intersect::{
26 CircleCircle2DIntersection, CurveCurveHit, CurveCurveHit2D, CurveCurveIntersection,
27 CurveCurveIntersection2D, CurveCurveOverlap, CurveSurfaceHit, CurveSurfaceIntersection,
28 ExactCurveCurveIntersection, ExactCurveSurfaceIntersection, ExactSurfaceIntersection,
29 InlineList, IntersectExact, LineCircle2DIntersection, LineLine2DIntersection,
30 intersect_curve_curve, intersect_curve_surface,
31};
32use crate::surfaces::{
33 BSplineSurface, Cone, Cylinder, ElementarySurface, ParametricSurface, Plane, Sphere, Surface,
34 Torus,
35};
36use crate::{Point, Point2D};
37
38pub trait Intersect<Rhs: ?Sized = Self> {
63 type Output;
65
66 fn intersect(&self, other: &Rhs, tol: Tolerance) -> Self::Output;
69}
70
71pub(crate) trait CurveBasis:
83 ParametricCurve
84 + Copy
85 + Into<ElementaryCurve>
86 + ExactCurveSurface<Plane>
87 + ExactCurveSurface<Cylinder>
88 + ExactCurveSurface<Cone>
89 + ExactCurveSurface<Sphere>
90 + ExactCurveSurface<Torus>
91 + ExactCurveSurface<ElementarySurface>
92 + ExactCurveCurve<Line>
93 + ExactCurveCurve<Circle>
94 + ExactCurveCurve<Ellipse>
95 + ExactCurveCurve<Parabola>
96 + ExactCurveCurve<Hyperbola>
97 + ExactCurveCurve<ElementaryCurve>
98{
99 fn basis_parameter_of(&self, point: Point) -> f64;
101}
102
103macro_rules! curve_basis {
104 ($($curve:ty),*) => {$(
105 impl CurveBasis for $curve {
106 #[inline]
107 fn basis_parameter_of(&self, point: Point) -> f64 {
108 self.parameter_of(point)
109 }
110 }
111 )*};
112}
113
114curve_basis!(Line, Circle, Ellipse, Parabola, Hyperbola, ElementaryCurve);
115
116pub(crate) trait CurveOperand: ParametricCurve {
120 type Basis: CurveBasis;
123
124 fn elementary_basis(&self) -> Option<Operand<Self::Basis>>;
127}
128
129macro_rules! elementary_operand {
130 ($($curve:ty),*) => {$(
131 impl CurveOperand for $curve {
132 type Basis = $curve;
133
134 #[inline]
135 fn elementary_basis(&self) -> Option<Operand<$curve>> {
136 Some((*self, None))
137 }
138 }
139 )*};
140}
141
142elementary_operand!(Line, Circle, Ellipse, Parabola, Hyperbola, ElementaryCurve);
143
144impl CurveOperand for BSplineCurve {
145 type Basis = ElementaryCurve;
146
147 fn elementary_basis(&self) -> Option<Operand<ElementaryCurve>> {
148 None
149 }
150}
151
152impl CurveOperand for Curve {
153 type Basis = ElementaryCurve;
154
155 #[inline]
156 fn elementary_basis(&self) -> Option<Operand<ElementaryCurve>> {
157 self.as_elementary().map(|basis| (basis, None))
158 }
159}
160
161impl<C: CurveOperand> CurveOperand for TrimmedCurve<C> {
162 type Basis = C::Basis;
163
164 #[inline]
167 fn elementary_basis(&self) -> Option<Operand<C::Basis>> {
168 self.basis()
169 .elementary_basis()
170 .map(|(basis, _)| (basis, Some((self.first(), self.last()))))
171 }
172}
173
174pub(crate) trait SurfaceOperand: ParametricSurface {
176 type Basis: Copy;
179
180 fn surface_basis(&self) -> Option<Self::Basis>;
182}
183
184macro_rules! elementary_surface_operand {
185 ($($surface:ty),*) => {$(
186 impl SurfaceOperand for $surface {
187 type Basis = $surface;
188
189 #[inline]
190 fn surface_basis(&self) -> Option<$surface> {
191 Some(*self)
192 }
193 }
194 )*};
195}
196
197elementary_surface_operand!(Plane, Cylinder, Cone, Sphere, Torus, ElementarySurface);
198
199impl SurfaceOperand for BSplineSurface {
200 type Basis = ElementarySurface;
201
202 fn surface_basis(&self) -> Option<ElementarySurface> {
203 None
204 }
205}
206
207impl SurfaceOperand for Surface {
208 type Basis = ElementarySurface;
209
210 #[inline]
211 fn surface_basis(&self) -> Option<ElementarySurface> {
212 self.as_elementary()
213 }
214}
215
216pub(crate) fn in_range(
230 parameter: f64,
231 range: (f64, f64),
232 period: Option<f64>,
233 speed: impl FnOnce(f64) -> f64,
234 tol: Tolerance,
235) -> Option<f64> {
236 let (before_start, past_end) = match period {
237 None => {
238 if parameter >= range.0 && parameter <= range.1 {
239 return Some(parameter);
240 }
241 (range.0 - parameter, parameter - range.1)
242 }
243 Some(period) => {
244 let shifted = parameter - period * ((parameter - range.0) / period).floor();
245 if shifted <= range.1 {
246 return Some(shifted);
247 }
248 (range.0 + period - shifted, shifted - range.1)
249 }
250 };
251 let (end, excess) = if past_end > 0.0 && (before_start <= 0.0 || past_end <= before_start) {
252 (range.1, past_end)
253 } else {
254 (range.0, before_start)
255 };
256 (excess * speed(end) <= tol.confusion).then_some(end)
257}
258
259fn nearest_end(parameter: f64, range: (f64, f64), period: Option<f64>) -> f64 {
264 let Some(period) = period else {
265 return parameter.clamp(range.0, range.1);
266 };
267 let distance = |end: f64| {
268 let past = (parameter - end).rem_euclid(period);
269 past.min(period - past)
270 };
271 if distance(range.0) <= distance(range.1) {
272 range.0
273 } else {
274 range.1
275 }
276}
277
278#[inline]
287fn kept_parameter(
288 stored: Option<f64>,
289 range: Option<(f64, f64)>,
290 period: Option<f64>,
291 recover: impl FnOnce() -> f64,
292 speed: impl FnOnce(f64) -> f64,
293 tol: Tolerance,
294) -> Option<Option<f64>> {
295 match range {
296 Some(range) => {
297 in_range(stored.unwrap_or_else(recover), range, period, speed, tol).map(Some)
298 }
299 None => Some(stored),
300 }
301}
302
303pub(crate) fn clip_range(
321 overlap: (f64, f64),
322 range: (f64, f64),
323 period: Option<f64>,
324 speed: &impl Fn(f64) -> f64,
325 tol: Tolerance,
326) -> InlineList<(f64, f64), 3> {
327 let clip = |(low, high): (f64, f64)| {
328 let clipped = (low.max(range.0), high.min(range.1));
329 (clipped.1 >= clipped.0 || is_single_point(clipped, speed, tol)).then_some(clipped)
330 };
331 let mut pieces = InlineList::new();
332 let Some(period) = period else {
333 if let Some(piece) = clip(overlap) {
334 pieces.push(piece);
335 }
336 return pieces;
337 };
338 let missing = period - (overlap.1 - overlap.0);
339 if missing <= 0.0 || missing * speed(overlap.1) <= tol.confusion {
340 pieces.push(range);
341 return pieces;
342 }
343 let first_copy = ((range.0 - overlap.1) / period).floor();
344 let mut copies = InlineList::<(f64, f64), 3>::new();
345 for step in 0..3 {
346 let shift = (first_copy + f64::from(step)) * period;
347 if let Some(piece) = clip((overlap.0 + shift, overlap.1 + shift)) {
348 copies.push(piece);
349 }
350 }
351 let same_point = |a: f64, b: f64| {
352 let gap = (a - b).rem_euclid(period);
353 gap.min(period - gap) * speed(a) <= tol.confusion
354 };
355 for (index, &piece) in copies.iter().enumerate() {
356 let repeated = is_single_point(piece, speed, tol)
357 && copies.iter().enumerate().any(|(other_index, &other)| {
358 let earlier_point = is_single_point(other, speed, tol) && other_index < index;
359 let longer = !is_single_point(other, speed, tol);
360 (earlier_point || longer)
361 && (same_point(piece.0, other.0) || same_point(piece.0, other.1))
362 });
363 if !repeated {
364 pieces.push(piece);
365 }
366 }
367 pieces.sort_by(|a, b| a.0.total_cmp(&b.0));
368 pieces
369}
370
371pub(crate) fn is_single_point(
376 (low, high): (f64, f64),
377 speed: &impl Fn(f64) -> f64,
378 tol: Tolerance,
379) -> bool {
380 let length = high - low;
381 length.is_finite() && length.abs() * speed(0.5 * (low + high)) <= tol.confusion
382}
383
384#[inline]
388pub(crate) fn route_curve_surface<C: CurveOperand, S: SurfaceOperand>(
389 curve: &C,
390 surface: &S,
391 tol: Tolerance,
392) -> CurveSurfaceIntersection
393where
394 C::Basis: ExactCurveSurface<S::Basis>,
395{
396 if let (Some((basis, range)), Some(elementary)) =
397 (curve.elementary_basis(), surface.surface_basis())
398 {
399 let exact = basis.exact_curve_surface(&elementary, tol);
400 if !matches!(exact, ExactCurveSurfaceIntersection::NotAnalytic) {
401 return from_exact_curve_surface(exact, &basis, range, tol);
402 }
403 }
404 intersect_curve_surface(curve, surface, tol)
406}
407
408#[inline]
413fn from_exact_curve_surface(
414 exact: ExactCurveSurfaceIntersection,
415 basis: &impl ParametricCurve,
416 range: Option<(f64, f64)>,
417 tol: Tolerance,
418) -> CurveSurfaceIntersection {
419 match exact {
420 ExactCurveSurfaceIntersection::Points(list) => {
421 let hits = list.iter().filter_map(|hit| {
422 let curve_parameter = match range {
423 Some(range) => in_range(
424 hit.parameter,
425 range,
426 basis.period(),
427 |parameter| basis.eval_derivative(parameter, 1).magnitude(),
428 tol,
429 )?,
430 None => hit.parameter,
431 };
432 Some(CurveSurfaceHit {
433 curve_parameter: Some(curve_parameter),
434 surface_parameters: None,
435 point: hit.point,
436 multiplicity: hit.multiplicity,
437 })
438 });
439 CurveSurfaceIntersection::exact(hits, CurveSurfaceOverlapList::new())
440 }
441 ExactCurveSurfaceIntersection::Coincident => {
444 let overlap = range.unwrap_or(basis.bounds());
445 CurveSurfaceIntersection::exact([], [overlap].into_iter().collect())
446 }
447 ExactCurveSurfaceIntersection::Empty => {
448 CurveSurfaceIntersection::exact([], CurveSurfaceOverlapList::new())
449 }
450 ExactCurveSurfaceIntersection::NotAnalytic => {
451 unreachable!("the router sends a pair with no closed form to the numeric search")
452 }
453 }
454}
455
456macro_rules! curve_surface {
460 (@pairs $curve:ty; [$($surface:ty),*]) => {$(
461 impl Intersect<$surface> for $curve {
462 type Output = CurveSurfaceIntersection;
463
464 #[inline]
465 fn intersect(&self, other: &$surface, tol: Tolerance) -> CurveSurfaceIntersection {
466 route_curve_surface(self, other, tol)
467 }
468 }
469
470 impl Intersect<$curve> for $surface {
471 type Output = CurveSurfaceIntersection;
472
473 #[inline]
474 fn intersect(&self, other: &$curve, tol: Tolerance) -> CurveSurfaceIntersection {
475 route_curve_surface(other, self, tol)
476 }
477 }
478 )*};
479 ($($curve:ty),*; $surfaces:tt) => {$(
480 curve_surface!(@pairs $curve; $surfaces);
481 )*};
482}
483
484curve_surface!(
485 Line, Circle, Ellipse, Parabola, Hyperbola, BSplineCurve, Curve, ElementaryCurve;
486 [Plane, Cylinder, Cone, Sphere, Torus, BSplineSurface, Surface, ElementarySurface]
487);
488
489macro_rules! trimmed_curve_surface {
492 ($($surface:ty),*) => {$(
493 impl<X: CurveOperand> Intersect<$surface> for TrimmedCurve<X> {
494 type Output = CurveSurfaceIntersection;
495
496 #[inline]
497 fn intersect(&self, other: &$surface, tol: Tolerance) -> CurveSurfaceIntersection {
498 route_curve_surface(self, other, tol)
499 }
500 }
501
502 impl<X: CurveOperand> Intersect<TrimmedCurve<X>> for $surface {
503 type Output = CurveSurfaceIntersection;
504
505 #[inline]
506 fn intersect(&self, other: &TrimmedCurve<X>, tol: Tolerance) -> CurveSurfaceIntersection {
507 route_curve_surface(other, self, tol)
508 }
509 }
510 )*};
511}
512
513trimmed_curve_surface!(
514 Plane,
515 Cylinder,
516 Cone,
517 Sphere,
518 Torus,
519 BSplineSurface,
520 Surface,
521 ElementarySurface
522);
523
524pub(crate) trait ExactSurfaceSurface<B> {
531 fn exact_surface_surface(&self, other: &B, tol: Tolerance) -> ExactSurfaceIntersection;
534}
535
536impl<A, B> ExactSurfaceSurface<B> for A
537where
538 A: IntersectExact<B>,
539 A::Output: Into<ExactSurfaceIntersection>,
540{
541 #[inline]
542 fn exact_surface_surface(&self, other: &B, tol: Tolerance) -> ExactSurfaceIntersection {
543 self.intersect_exact(other, tol).into()
544 }
545}
546
547macro_rules! exact_surface_surface_with_enum {
550 ($($surface:ty),*) => {$(
551 impl ExactSurfaceSurface<ElementarySurface> for $surface {
552 #[inline]
553 fn exact_surface_surface(
554 &self,
555 other: &ElementarySurface,
556 tol: Tolerance,
557 ) -> ExactSurfaceIntersection {
558 ElementarySurface::from(*self).intersect_exact(other, tol)
559 }
560 }
561
562 impl ExactSurfaceSurface<$surface> for ElementarySurface {
563 #[inline]
564 fn exact_surface_surface(
565 &self,
566 other: &$surface,
567 tol: Tolerance,
568 ) -> ExactSurfaceIntersection {
569 self.intersect_exact(&ElementarySurface::from(*other), tol)
570 }
571 }
572 )*};
573}
574
575exact_surface_surface_with_enum!(Plane, Cylinder, Cone, Sphere, Torus);
576
577#[inline]
580pub(crate) fn route_surface_surface<A: SurfaceOperand, B: SurfaceOperand>(
581 a: &A,
582 b: &B,
583 tol: Tolerance,
584) -> SurfaceSurfaceIntersection
585where
586 A::Basis: ExactSurfaceSurface<B::Basis>,
587{
588 if let (Some(first), Some(second)) = (a.surface_basis(), b.surface_basis()) {
589 let exact = first.exact_surface_surface(&second, tol);
590 if !matches!(exact, ExactSurfaceIntersection::NotAnalytic) {
591 return from_exact_surface_surface(exact);
592 }
593 }
594 marching_intersection(a, b, tol)
595}
596
597#[inline]
600fn from_exact_surface_surface(exact: ExactSurfaceIntersection) -> SurfaceSurfaceIntersection {
601 match exact {
602 ExactSurfaceIntersection::Meets { curves, points } => SurfaceSurfaceIntersection::exact(
603 curves
604 .iter()
605 .map(|&(curve, tangent)| IntersectionCurve {
606 curve: Curve::from(curve),
607 tangent,
608 })
609 .collect(),
610 points.iter().copied().collect(),
611 false,
612 ),
613 ExactSurfaceIntersection::Coincident => {
614 SurfaceSurfaceIntersection::exact(Vec::new(), Vec::new(), true)
615 }
616 ExactSurfaceIntersection::Empty => {
617 SurfaceSurfaceIntersection::exact(Vec::new(), Vec::new(), false)
618 }
619 ExactSurfaceIntersection::NotAnalytic => {
620 unreachable!("the router sends a pair with no closed form to marching")
621 }
622 }
623}
624
625macro_rules! surface_surface {
628 (@pairs $first:ty; [$($second:ty),*]) => {$(
629 impl Intersect<$second> for $first {
630 type Output = SurfaceSurfaceIntersection;
631
632 #[inline]
633 fn intersect(&self, other: &$second, tol: Tolerance) -> SurfaceSurfaceIntersection {
634 route_surface_surface(self, other, tol)
635 }
636 }
637 )*};
638 ($($first:ty),*; $seconds:tt) => {$(
639 surface_surface!(@pairs $first; $seconds);
640 )*};
641}
642
643surface_surface!(
644 Plane, Cylinder, Cone, Sphere, Torus, BSplineSurface, Surface, ElementarySurface;
645 [Plane, Cylinder, Cone, Sphere, Torus, BSplineSurface, Surface, ElementarySurface]
646);
647
648#[inline]
651pub(crate) fn route_curve_curve<A: CurveOperand, B: CurveOperand>(
652 a: &A,
653 b: &B,
654 tol: Tolerance,
655) -> CurveCurveIntersection
656where
657 A::Basis: ExactCurveCurve<B::Basis>,
658{
659 if let (Some(first), Some(second)) = (a.elementary_basis(), b.elementary_basis()) {
660 let exact = first.0.exact_curve_curve(&second.0, tol);
661 debug_assert!(
662 !matches!(exact, ExactCurveCurveIntersection::NotAnalytic),
663 "every pair of elementary curves has a closed form"
664 );
665 if !matches!(exact, ExactCurveCurveIntersection::NotAnalytic) {
666 return from_exact_curve_curve(exact, first, second, tol);
667 }
668 }
669 intersect_curve_curve(a, b, tol)
671}
672
673type Operand<C> = (C, Option<(f64, f64)>);
675
676#[inline]
681fn from_exact_curve_curve<A: CurveBasis, B: CurveBasis>(
682 exact: ExactCurveCurveIntersection,
683 (first, first_range): Operand<A>,
684 (second, second_range): Operand<B>,
685 tol: Tolerance,
686) -> CurveCurveIntersection {
687 match exact {
688 ExactCurveCurveIntersection::Points(list)
691 if first_range.is_none() && second_range.is_none() =>
692 {
693 CurveCurveIntersection::exact(
694 CurveCurveHitList::Inline(list),
695 CurveCurveOverlapList::new(),
696 )
697 }
698 ExactCurveCurveIntersection::Points(list) => {
699 let hits = list.iter().filter_map(|hit| {
700 Some(CurveCurveHit {
701 first_parameter: kept_parameter(
702 hit.first_parameter,
703 first_range,
704 first.period(),
705 || first.basis_parameter_of(hit.point),
706 |parameter| first.eval_derivative(parameter, 1).magnitude(),
707 tol,
708 )?,
709 second_parameter: kept_parameter(
710 hit.second_parameter,
711 second_range,
712 second.period(),
713 || second.basis_parameter_of(hit.point),
714 |parameter| second.eval_derivative(parameter, 1).magnitude(),
715 tol,
716 )?,
717 ..*hit
718 })
719 });
720 CurveCurveIntersection::exact(hits.collect(), CurveCurveOverlapList::new())
721 }
722 ExactCurveCurveIntersection::Coincident => {
725 let (first, second): (ElementaryCurve, ElementaryCurve) = (first.into(), second.into());
726 let first_range = first_range.unwrap_or(first.bounds());
727 let second_range = second_range.unwrap_or(second.bounds());
728 let shared = coincident_overlaps((&first, first_range), (&second, second_range), tol);
729 let hits = shared
730 .touches
731 .iter()
732 .map(|&(first_parameter, second_parameter)| CurveCurveHit {
733 first_parameter: Some(first_parameter),
734 second_parameter: Some(second_parameter),
735 point: first.eval_point(first_parameter),
736 multiplicity: 1,
737 });
738 CurveCurveIntersection::exact(hits.collect(), shared.overlaps)
739 }
740 ExactCurveCurveIntersection::Empty => {
741 CurveCurveIntersection::exact(CurveCurveHitList::new(), CurveCurveOverlapList::new())
742 }
743 ExactCurveCurveIntersection::NotAnalytic => {
744 unreachable!("the router sends a pair with no closed form to the numeric search")
745 }
746 }
747}
748
749fn coincident_overlaps<C: CoincidentCurve>(
763 (first, first_range): (&C, (f64, f64)),
764 (second, second_range): (&C, (f64, f64)),
765 tol: Tolerance,
766) -> CoincidentPieces {
767 let same_direction = runs_along(second, second_range, first);
768 let on_first = mapped_range(second, second_range, first, same_direction, tol);
769 let mut overlaps = CurveCurveOverlapList::new();
770 let mut touches = InlineList::new();
771 let first_speed = |parameter: f64| first.speed(parameter);
772 let second_speed = |parameter: f64| second.speed(parameter);
773 let first_pieces = clip_range(
774 on_first,
775 first_range,
776 first.cycle_length(),
777 &first_speed,
778 tol,
779 );
780 for &piece in first_pieces.iter() {
781 if is_single_point(piece, &first_speed, tol) {
782 let first_parameter = (0.5 * (piece.0 + piece.1)).clamp(first_range.0, first_range.1);
783 let found = second.parameter_of_image(first, first_parameter);
784 let period = second.cycle_length();
785 let second_parameter = in_range(found, second_range, period, |t| second.speed(t), tol)
786 .unwrap_or_else(|| nearest_end(found, second_range, period));
787 touches.push((first_parameter, second_parameter));
788 continue;
789 }
790 let on_second = mapped_range(first, piece, second, same_direction, tol);
791 let pieces_on_second = clip_range(
792 on_second,
793 second_range,
794 second.cycle_length(),
795 &second_speed,
796 tol,
797 );
798 if let [single] = pieces_on_second[..] {
799 overlaps.push(CurveCurveOverlap {
800 first_range: piece,
801 second_range: single,
802 same_direction,
803 });
804 continue;
805 }
806 for &part in pieces_on_second.iter() {
809 let end = |parameter: f64| {
810 let found = first.parameter_of_image(second, parameter);
811 let period = first.cycle_length();
812 in_range(found, piece, period, |t| first.speed(t), tol)
813 .unwrap_or_else(|| nearest_end(found, piece, period))
814 };
815 let (start, finish) = (end(part.0), end(part.1));
816 overlaps.push(CurveCurveOverlap {
817 first_range: (start.min(finish), start.max(finish)),
818 second_range: part,
819 same_direction,
820 });
821 }
822 }
823 overlaps.sort_by(|p, q| p.first_range.0.total_cmp(&q.first_range.0));
824 CoincidentPieces { overlaps, touches }
825}
826
827struct CoincidentPieces {
829 overlaps: CurveCurveOverlapList,
831 touches: InlineList<(f64, f64), 3>,
835}
836
837pub(crate) trait CoincidentCurve {
840 fn cycle_length(&self) -> Option<f64>;
842
843 fn parameter_of_image(&self, other: &Self, parameter: f64) -> f64;
846
847 fn runs_with(&self, parameter: f64, other: &Self) -> bool;
850
851 fn speed(&self, parameter: f64) -> f64;
854}
855
856impl CoincidentCurve for ElementaryCurve {
857 fn cycle_length(&self) -> Option<f64> {
858 self.period()
859 }
860
861 fn parameter_of_image(&self, other: &Self, parameter: f64) -> f64 {
862 self.parameter_of(other.eval_point(parameter))
863 }
864
865 fn runs_with(&self, parameter: f64, other: &Self) -> bool {
866 let image = other.parameter_of_image(self, parameter);
867 self.eval_derivative(parameter, 1)
868 .dot(other.eval_derivative(image, 1))
869 >= 0.0
870 }
871
872 fn speed(&self, parameter: f64) -> f64 {
873 self.eval_derivative(parameter, 1).magnitude()
874 }
875}
876
877impl CoincidentCurve for Curve2D {
878 fn cycle_length(&self) -> Option<f64> {
879 self.period()
880 }
881
882 fn parameter_of_image(&self, other: &Self, parameter: f64) -> f64 {
883 let point = other.eval_point(parameter);
884 match self {
885 Curve2D::Line(line) => line.parameter_of(point),
886 Curve2D::Circle(circle) => circle.parameter_of(point),
887 }
888 }
889
890 fn runs_with(&self, parameter: f64, other: &Self) -> bool {
891 let image = other.parameter_of_image(self, parameter);
892 self.eval_derivative(parameter, 1)
893 .dot(other.eval_derivative(image, 1))
894 >= 0.0
895 }
896
897 fn speed(&self, parameter: f64) -> f64 {
898 self.eval_derivative(parameter, 1).magnitude()
899 }
900}
901
902fn runs_along<C: CoincidentCurve>(from: &C, range: (f64, f64), to: &C) -> bool {
905 let sample = match (range.0.is_finite(), range.1.is_finite()) {
906 (true, true) => 0.5 * (range.0 + range.1),
907 (true, false) => range.0,
908 (false, true) => range.1,
909 (false, false) => 0.0,
910 };
911 from.runs_with(sample, to)
912}
913
914fn mapped_range<C: CoincidentCurve>(
922 from: &C,
923 range: (f64, f64),
924 to: &C,
925 along: bool,
926 tol: Tolerance,
927) -> (f64, f64) {
928 let image = |parameter: f64| to.parameter_of_image(from, parameter);
929 let Some(period) = to.cycle_length() else {
930 let end = |parameter: f64| {
931 if parameter.is_finite() {
932 image(parameter)
933 } else if along {
934 parameter
935 } else {
936 -parameter
937 }
938 };
939 let (start, finish) = (end(range.0), end(range.1));
940 return (start.min(finish), start.max(finish));
941 };
942 let full_period = from.cycle_length().unwrap_or(period);
943 if range.1 - range.0 >= full_period - tol.parametric {
944 let start = image(range.0);
945 return (start, start + period);
946 }
947 let (start, finish) = if along {
948 (image(range.0), image(range.1))
949 } else {
950 (image(range.1), image(range.0))
951 };
952 (start, start + (finish - start).rem_euclid(period))
953}
954
955macro_rules! curve_curve {
958 (@row $first:ty; [$($second:ty),*]) => {$(
959 impl Intersect<$second> for $first {
960 type Output = CurveCurveIntersection;
961
962 #[inline]
963 fn intersect(&self, other: &$second, tol: Tolerance) -> CurveCurveIntersection {
964 route_curve_curve(self, other, tol)
965 }
966 }
967 )*};
968 ([$($first:ty),*]) => {
969 curve_curve!(@all [$($first),*]; [$($first),*]);
970 };
971 (@all [$($first:ty),*]; $seconds:tt) => {$(
972 curve_curve!(@row $first; $seconds);
973 )*};
974}
975
976curve_curve!([
977 Line,
978 Circle,
979 Ellipse,
980 Parabola,
981 Hyperbola,
982 BSplineCurve,
983 Curve,
984 ElementaryCurve
985]);
986
987macro_rules! trimmed_curve_curve {
990 ($($curve:ty),*) => {$(
991 impl<X: CurveOperand> Intersect<$curve> for TrimmedCurve<X> {
992 type Output = CurveCurveIntersection;
993
994 #[inline]
995 fn intersect(&self, other: &$curve, tol: Tolerance) -> CurveCurveIntersection {
996 route_curve_curve(self, other, tol)
997 }
998 }
999
1000 impl<X: CurveOperand> Intersect<TrimmedCurve<X>> for $curve
1001 where
1002 <$curve as CurveOperand>::Basis: ExactCurveCurve<X::Basis>,
1003 {
1004 type Output = CurveCurveIntersection;
1005
1006 #[inline]
1007 fn intersect(&self, other: &TrimmedCurve<X>, tol: Tolerance) -> CurveCurveIntersection {
1008 route_curve_curve(self, other, tol)
1009 }
1010 }
1011 )*};
1012}
1013
1014trimmed_curve_curve!(
1015 Line,
1016 Circle,
1017 Ellipse,
1018 Parabola,
1019 Hyperbola,
1020 BSplineCurve,
1021 Curve,
1022 ElementaryCurve
1023);
1024
1025impl<X: CurveOperand, Y: CurveOperand> Intersect<TrimmedCurve<Y>> for TrimmedCurve<X>
1026where
1027 X::Basis: ExactCurveCurve<Y::Basis>,
1028{
1029 type Output = CurveCurveIntersection;
1030
1031 #[inline]
1032 fn intersect(&self, other: &TrimmedCurve<Y>, tol: Tolerance) -> CurveCurveIntersection {
1033 route_curve_curve(self, other, tol)
1034 }
1035}
1036
1037pub(crate) enum ExactCurveCurve2DIntersection {
1040 Hits([Option<CurveCurveHit2D>; 2]),
1042 Coincident,
1044}
1045
1046pub(crate) trait ExactCurveCurve2D<B> {
1050 fn exact_curve_curve_2d(&self, other: &B, tol: Tolerance) -> ExactCurveCurve2DIntersection;
1053}
1054
1055fn hit_2d(
1058 first_parameter: Option<f64>,
1059 second_parameter: Option<f64>,
1060 point: Point2D,
1061 multiplicity: u32,
1062) -> Option<CurveCurveHit2D> {
1063 Some(CurveCurveHit2D {
1064 first_parameter,
1065 second_parameter,
1066 point,
1067 multiplicity,
1068 })
1069}
1070
1071impl ExactCurveCurve2D<Line2D> for Line2D {
1072 #[inline]
1073 fn exact_curve_curve_2d(
1074 &self,
1075 other: &Line2D,
1076 tol: Tolerance,
1077 ) -> ExactCurveCurve2DIntersection {
1078 match self.intersect_exact(other, tol) {
1079 LineLine2DIntersection::Point(t, point, s) => {
1080 ExactCurveCurve2DIntersection::Hits([hit_2d(Some(t), Some(s), point, 1), None])
1081 }
1082 LineLine2DIntersection::Parallel => ExactCurveCurve2DIntersection::Hits([None, None]),
1083 LineLine2DIntersection::Coincident => ExactCurveCurve2DIntersection::Coincident,
1084 }
1085 }
1086}
1087
1088impl ExactCurveCurve2D<Circle2D> for Line2D {
1089 #[inline]
1090 fn exact_curve_curve_2d(
1091 &self,
1092 circle: &Circle2D,
1093 tol: Tolerance,
1094 ) -> ExactCurveCurve2DIntersection {
1095 let at = |t, point, multiplicity| hit_2d(Some(t), None, point, multiplicity);
1096 ExactCurveCurve2DIntersection::Hits(match self.intersect_exact(circle, tol) {
1097 LineCircle2DIntersection::Points((t, p), (u, q)) => [at(t, p, 1), at(u, q, 1)],
1098 LineCircle2DIntersection::Tangent(t, p) => [at(t, p, 2), None],
1099 LineCircle2DIntersection::Empty => [None, None],
1100 })
1101 }
1102}
1103
1104impl ExactCurveCurve2D<Line2D> for Circle2D {
1105 #[inline]
1106 fn exact_curve_curve_2d(&self, line: &Line2D, tol: Tolerance) -> ExactCurveCurve2DIntersection {
1107 let at = |t, point, multiplicity| hit_2d(None, Some(t), point, multiplicity);
1108 ExactCurveCurve2DIntersection::Hits(match line.intersect_exact(self, tol) {
1109 LineCircle2DIntersection::Points((t, p), (u, q)) => [at(t, p, 1), at(u, q, 1)],
1110 LineCircle2DIntersection::Tangent(t, p) => [at(t, p, 2), None],
1111 LineCircle2DIntersection::Empty => [None, None],
1112 })
1113 }
1114}
1115
1116impl ExactCurveCurve2D<Circle2D> for Circle2D {
1117 #[inline]
1118 fn exact_curve_curve_2d(
1119 &self,
1120 other: &Circle2D,
1121 tol: Tolerance,
1122 ) -> ExactCurveCurve2DIntersection {
1123 let at = |point, multiplicity| hit_2d(None, None, point, multiplicity);
1124 ExactCurveCurve2DIntersection::Hits(match self.intersect_exact(other, tol) {
1125 CircleCircle2DIntersection::Points(p, q) => [at(p, 1), at(q, 1)],
1126 CircleCircle2DIntersection::Tangent(p) => [at(p, 2), None],
1127 CircleCircle2DIntersection::Empty => [None, None],
1128 CircleCircle2DIntersection::Coincident => {
1129 return ExactCurveCurve2DIntersection::Coincident;
1130 }
1131 })
1132 }
1133}
1134
1135macro_rules! exact_curve_curve_2d_untyped {
1139 ($($curve:ty),*) => {$(
1140 impl ExactCurveCurve2D<Curve2D> for $curve {
1141 #[inline]
1142 fn exact_curve_curve_2d(
1143 &self,
1144 other: &Curve2D,
1145 tol: Tolerance,
1146 ) -> ExactCurveCurve2DIntersection {
1147 match other {
1148 Curve2D::Line(b) => self.exact_curve_curve_2d(b, tol),
1149 Curve2D::Circle(b) => self.exact_curve_curve_2d(b, tol),
1150 }
1151 }
1152 }
1153
1154 impl ExactCurveCurve2D<$curve> for Curve2D {
1155 #[inline]
1156 fn exact_curve_curve_2d(
1157 &self,
1158 other: &$curve,
1159 tol: Tolerance,
1160 ) -> ExactCurveCurve2DIntersection {
1161 match self {
1162 Curve2D::Line(a) => a.exact_curve_curve_2d(other, tol),
1163 Curve2D::Circle(a) => a.exact_curve_curve_2d(other, tol),
1164 }
1165 }
1166 }
1167 )*};
1168}
1169
1170exact_curve_curve_2d_untyped!(Line2D, Circle2D);
1171
1172impl ExactCurveCurve2D<Curve2D> for Curve2D {
1173 #[inline]
1174 fn exact_curve_curve_2d(
1175 &self,
1176 other: &Curve2D,
1177 tol: Tolerance,
1178 ) -> ExactCurveCurve2DIntersection {
1179 match self {
1180 Curve2D::Line(a) => a.exact_curve_curve_2d(other, tol),
1181 Curve2D::Circle(a) => a.exact_curve_curve_2d(other, tol),
1182 }
1183 }
1184}
1185
1186pub(crate) trait CurveBasis2D:
1190 ParametricCurve2D
1191 + Copy
1192 + Into<Curve2D>
1193 + ExactCurveCurve2D<Line2D>
1194 + ExactCurveCurve2D<Circle2D>
1195 + ExactCurveCurve2D<Curve2D>
1196{
1197 fn basis_parameter_of(&self, point: Point2D) -> f64;
1200}
1201
1202impl CurveBasis2D for Line2D {
1203 #[inline]
1204 fn basis_parameter_of(&self, point: Point2D) -> f64 {
1205 self.parameter_of(point)
1206 }
1207}
1208
1209impl CurveBasis2D for Circle2D {
1210 #[inline]
1211 fn basis_parameter_of(&self, point: Point2D) -> f64 {
1212 self.parameter_of(point)
1213 }
1214}
1215
1216impl CurveBasis2D for Curve2D {
1217 #[inline]
1218 fn basis_parameter_of(&self, point: Point2D) -> f64 {
1219 match self {
1220 Curve2D::Line(line) => line.parameter_of(point),
1221 Curve2D::Circle(circle) => circle.parameter_of(point),
1222 }
1223 }
1224}
1225
1226pub(crate) trait CurveOperand2D: ParametricCurve2D {
1229 type Basis: CurveBasis2D;
1232
1233 fn basis_and_range(&self) -> (Self::Basis, Option<(f64, f64)>);
1235}
1236
1237macro_rules! basis_operand_2d {
1238 ($($curve:ty),*) => {$(
1239 impl CurveOperand2D for $curve {
1240 type Basis = $curve;
1241
1242 #[inline]
1243 fn basis_and_range(&self) -> ($curve, Option<(f64, f64)>) {
1244 (*self, None)
1245 }
1246 }
1247 )*};
1248}
1249
1250basis_operand_2d!(Line2D, Circle2D, Curve2D);
1251
1252impl<C: CurveOperand2D> CurveOperand2D for TrimmedCurve2D<C> {
1253 type Basis = C::Basis;
1254
1255 #[inline]
1258 fn basis_and_range(&self) -> (C::Basis, Option<(f64, f64)>) {
1259 let (basis, _) = self.basis().basis_and_range();
1260 (basis, Some((self.first(), self.last())))
1261 }
1262}
1263
1264#[inline]
1267pub(crate) fn route_curve_curve_2d<A: CurveOperand2D, B: CurveOperand2D>(
1268 a: &A,
1269 b: &B,
1270 tol: Tolerance,
1271) -> CurveCurveIntersection2D
1272where
1273 A::Basis: ExactCurveCurve2D<B::Basis>,
1274{
1275 let (first, first_range) = a.basis_and_range();
1276 let (second, second_range) = b.basis_and_range();
1277 match first.exact_curve_curve_2d(&second, tol) {
1278 ExactCurveCurve2DIntersection::Coincident => {
1279 coincidence_2d(first.into(), first_range, second.into(), second_range, tol)
1280 }
1281 ExactCurveCurve2DIntersection::Hits(hits)
1284 if first_range.is_none() && second_range.is_none() =>
1285 {
1286 CurveCurveIntersection2D::exact(
1287 hits.into_iter().flatten(),
1288 CurveCurveOverlapList::new(),
1289 )
1290 }
1291 ExactCurveCurve2DIntersection::Hits(hits) => {
1292 let kept = hits.into_iter().flatten().filter_map(|hit| {
1293 Some(CurveCurveHit2D {
1294 first_parameter: kept_parameter(
1295 hit.first_parameter,
1296 first_range,
1297 first.period(),
1298 || first.basis_parameter_of(hit.point),
1299 |parameter| first.eval_derivative(parameter, 1).magnitude(),
1300 tol,
1301 )?,
1302 second_parameter: kept_parameter(
1303 hit.second_parameter,
1304 second_range,
1305 second.period(),
1306 || second.basis_parameter_of(hit.point),
1307 |parameter| second.eval_derivative(parameter, 1).magnitude(),
1308 tol,
1309 )?,
1310 ..hit
1311 })
1312 });
1313 CurveCurveIntersection2D::exact(kept, CurveCurveOverlapList::new())
1314 }
1315 }
1316}
1317
1318fn coincidence_2d(
1320 first: Curve2D,
1321 first_range: Option<(f64, f64)>,
1322 second: Curve2D,
1323 second_range: Option<(f64, f64)>,
1324 tol: Tolerance,
1325) -> CurveCurveIntersection2D {
1326 let first_range = first_range.unwrap_or(first.bounds());
1327 let second_range = second_range.unwrap_or(second.bounds());
1328 let shared = coincident_overlaps((&first, first_range), (&second, second_range), tol);
1329 let hits = shared
1330 .touches
1331 .iter()
1332 .map(|&(first_parameter, second_parameter)| CurveCurveHit2D {
1333 first_parameter: Some(first_parameter),
1334 second_parameter: Some(second_parameter),
1335 point: first.eval_point(first_parameter),
1336 multiplicity: 1,
1337 });
1338 CurveCurveIntersection2D::exact(hits, shared.overlaps)
1339}
1340
1341macro_rules! curve_curve_2d {
1344 (@row $first:ty; [$($second:ty),*]) => {$(
1345 impl Intersect<$second> for $first {
1346 type Output = CurveCurveIntersection2D;
1347
1348 #[inline]
1349 fn intersect(&self, other: &$second, tol: Tolerance) -> CurveCurveIntersection2D {
1350 route_curve_curve_2d(self, other, tol)
1351 }
1352 }
1353 )*};
1354 ([$($first:ty),*]) => {
1355 curve_curve_2d!(@all [$($first),*]; [$($first),*]);
1356 };
1357 (@all [$($first:ty),*]; $seconds:tt) => {$(
1358 curve_curve_2d!(@row $first; $seconds);
1359 )*};
1360}
1361
1362curve_curve_2d!([Line2D, Circle2D, Curve2D]);
1363
1364macro_rules! trimmed_curve_curve_2d {
1367 ($($curve:ty),*) => {$(
1368 impl<X: CurveOperand2D> Intersect<$curve> for TrimmedCurve2D<X> {
1369 type Output = CurveCurveIntersection2D;
1370
1371 #[inline]
1372 fn intersect(&self, other: &$curve, tol: Tolerance) -> CurveCurveIntersection2D {
1373 route_curve_curve_2d(self, other, tol)
1374 }
1375 }
1376
1377 impl<X: CurveOperand2D> Intersect<TrimmedCurve2D<X>> for $curve
1378 where
1379 <$curve as CurveOperand2D>::Basis: ExactCurveCurve2D<X::Basis>,
1380 {
1381 type Output = CurveCurveIntersection2D;
1382
1383 #[inline]
1384 fn intersect(&self, other: &TrimmedCurve2D<X>, tol: Tolerance) -> CurveCurveIntersection2D {
1385 route_curve_curve_2d(self, other, tol)
1386 }
1387 }
1388 )*};
1389}
1390
1391trimmed_curve_curve_2d!(Line2D, Circle2D, Curve2D);
1392
1393impl<X: CurveOperand2D, Y: CurveOperand2D> Intersect<TrimmedCurve2D<Y>> for TrimmedCurve2D<X>
1394where
1395 X::Basis: ExactCurveCurve2D<Y::Basis>,
1396{
1397 type Output = CurveCurveIntersection2D;
1398
1399 #[inline]
1400 fn intersect(&self, other: &TrimmedCurve2D<Y>, tol: Tolerance) -> CurveCurveIntersection2D {
1401 route_curve_curve_2d(self, other, tol)
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use super::*;
1408 use std::f64::consts::{PI, TAU};
1409 const TOL: Tolerance = Tolerance::DEFAULT;
1410
1411 fn unit_speed(_: f64) -> f64 {
1413 1.0
1414 }
1415
1416 #[test]
1417 fn test_in_range_plain() {
1418 assert_eq!(in_range(0.5, (0.0, 1.0), None, unit_speed, TOL), Some(0.5));
1419 assert_eq!(in_range(1.5, (0.0, 1.0), None, unit_speed, TOL), None);
1420 assert_eq!(
1421 in_range(1.0 + 1e-12, (0.0, 1.0), None, unit_speed, TOL),
1422 Some(1.0)
1423 );
1424 assert_eq!(
1425 in_range(-1e-12, (0.0, 1.0), None, unit_speed, TOL),
1426 Some(0.0)
1427 );
1428 }
1429
1430 #[test]
1431 fn test_in_range_measures_the_excess_in_space() {
1432 let fast = |_: f64| 10.0;
1433 assert_eq!(in_range(1.0 + 5e-9, (0.0, 1.0), None, fast, TOL), Some(1.0));
1434 assert_eq!(in_range(1.0 + 2e-8, (0.0, 1.0), None, fast, TOL), None);
1435 assert_eq!(
1436 in_range(1.0 + 5e-8, (0.0, 1.0), None, unit_speed, TOL),
1437 Some(1.0)
1438 );
1439 }
1440
1441 #[test]
1442 fn test_in_range_wraps_across_the_seam() {
1443 let shifted = in_range(6.0, (-1.0, 1.0), Some(TAU), unit_speed, TOL).unwrap();
1444 assert!((shifted - (6.0 - TAU)).abs() < 1e-15);
1445 assert_eq!(in_range(3.0, (-1.0, 1.0), Some(TAU), unit_speed, TOL), None);
1446 assert_eq!(
1448 in_range(5e-8, (PI, TAU), Some(TAU), unit_speed, TOL),
1449 Some(TAU)
1450 );
1451 assert_eq!(
1452 in_range(TAU - 5e-8, (0.0, PI), Some(TAU), unit_speed, TOL),
1453 Some(0.0)
1454 );
1455 }
1456
1457 #[test]
1458 fn test_in_range_accepts_just_below_the_start_on_a_periodic_curve() {
1459 let found = in_range(-1.0 - 1e-12, (-1.0, 1.0), Some(TAU), unit_speed, TOL).unwrap();
1460 assert_eq!(found, -1.0);
1461 assert_eq!(
1462 in_range(-1.0 - 1e-3, (-1.0, 1.0), Some(TAU), unit_speed, TOL),
1463 None
1464 );
1465 }
1466
1467 const UNIT_SPEED: fn(f64) -> f64 = |_| 1.0;
1469
1470 #[test]
1471 fn test_clip_range() {
1472 assert_eq!(
1473 *clip_range(
1474 (f64::NEG_INFINITY, f64::INFINITY),
1475 (2.0, 3.0),
1476 None,
1477 &UNIT_SPEED,
1478 TOL
1479 ),
1480 [(2.0, 3.0)]
1481 );
1482 assert_eq!(
1483 *clip_range((0.0, TAU), (-1.0, 1.0), Some(TAU), &UNIT_SPEED, TOL),
1484 [(-1.0, 1.0)]
1485 );
1486 assert!(clip_range((0.0, 1.0), (2.0, 3.0), None, &UNIT_SPEED, TOL).is_empty());
1487 }
1488
1489 #[test]
1490 fn test_clip_range_shifts_a_partial_overlap_across_the_seam() {
1491 let pieces = clip_range((6.0, 6.0 + 0.5), (-1.0, 1.0), Some(TAU), &UNIT_SPEED, TOL);
1492 assert_eq!(pieces.len(), 1);
1493 let (low, high) = pieces[0];
1494 assert!((low - (6.0 - TAU)).abs() < 1e-15);
1495 assert!((high - (6.5 - TAU)).abs() < 1e-15);
1496 assert!(clip_range((3.0, 4.0), (-1.0, 1.0), Some(TAU), &UNIT_SPEED, TOL).is_empty());
1497 }
1498
1499 #[test]
1500 fn test_clip_range_returns_both_pieces_across_the_seam() {
1501 let pieces = clip_range((4.0, 7.0), (0.0, 5.0), Some(TAU), &UNIT_SPEED, TOL);
1503 assert_eq!(pieces.len(), 2);
1504 assert!((pieces[0].0 - 0.0).abs() < 1e-15 && (pieces[0].1 - (7.0 - TAU)).abs() < 1e-15);
1505 assert_eq!(pieces[1], (4.0, 5.0));
1506 }
1507
1508 #[test]
1509 fn test_clip_range_keeps_a_touch_as_a_single_point() {
1510 assert_eq!(
1511 *clip_range((1.0, 2.0), (0.0, 1.0), None, &UNIT_SPEED, TOL),
1512 [(1.0, 1.0)]
1513 );
1514 let seam = clip_range((0.0, 1.0), (5.0, TAU), Some(TAU), &UNIT_SPEED, TOL);
1515 assert_eq!(*seam, [(TAU, TAU)]);
1516 assert!(is_single_point(seam[0], &UNIT_SPEED, TOL));
1517 }
1518
1519 #[test]
1520 fn test_clip_range_drops_a_touch_repeating_the_end_of_a_piece() {
1521 assert_eq!(
1524 *clip_range((0.0, 1.0), (0.0, TAU), Some(TAU), &UNIT_SPEED, TOL),
1525 [(0.0, 1.0)]
1526 );
1527 }
1528
1529 #[test]
1530 fn test_trimmed_operand_reports_basis_and_range() {
1531 let circle = crate::Circle::new(crate::Point::ORIGIN, crate::Vector::Z, 1.0).unwrap();
1532 let arc = crate::curves::TrimmedCurve::new(circle, -1.0, 1.0).unwrap();
1533 let (basis, range) = arc.elementary_basis().unwrap();
1534 assert_eq!(basis, circle);
1536 assert_eq!(range, Some((-1.0, 1.0)));
1537 }
1538
1539 #[test]
1540 fn test_trimmed_of_trimmed_keeps_the_outer_range() {
1541 let line = crate::Line::new(crate::Point::ORIGIN, crate::Vector::X).unwrap();
1542 let outer =
1543 TrimmedCurve::new(TrimmedCurve::new(line, 0.0, 10.0).unwrap(), 2.0, 3.0).unwrap();
1544 assert_eq!(outer.elementary_basis().unwrap().1, Some((2.0, 3.0)));
1545 let spline_free: Curve = line.into();
1546 assert_eq!(spline_free.elementary_basis().unwrap().1, None);
1547 }
1548}