Skip to main content

geomcore/intersect/
routing.rs

1//! The routed intersection entry point.
2//!
3//! [`Intersect`] gives the full answer for every supported pair: the exact
4//! intersection when a closed form exists, the numeric one otherwise, and
5//! the result says which method ran. [`IntersectExact`] is the typed exact
6//! path, and never falls back to numeric work.
7//!
8//! This module also holds the helpers the routed implementations share:
9//! [`CurveOperand`], which tells the router whether a curve has an
10//! elementary basis and which parameter range restricts it, and the
11//! parameter-range clipping that turns an exact intersection of the basis
12//! into the intersection of the trimmed curve.
13
14use 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
38/// Routed intersection: exact when a closed form exists, numeric otherwise.
39///
40/// Unlike [`IntersectExact`], the typed exact path that never runs numeric
41/// work, `intersect` works for every supported pair, and its result says which
42/// method ran (`method()`) and whether it is complete.
43///
44/// The operands may come in either order; a curve-surface result always
45/// reads the curve parameter first.
46///
47/// # Examples
48///
49/// ```
50/// use geomcore::{Intersect, IntersectionMethod, Line, Point, Sphere, Tolerance, Vector};
51///
52/// // A line along z through a sphere of radius 2 crosses it at both poles.
53/// let line = Line::new(Point::new(0.0, 0.0, -5.0), Vector::Z).unwrap();
54/// let sphere = Sphere::new(Point::ORIGIN, 2.0).unwrap();
55/// let result = line.intersect(&sphere, Tolerance::DEFAULT);
56/// assert_eq!(result.method(), IntersectionMethod::Exact);
57/// assert!(result.is_complete());
58/// let parameters: Vec<Option<f64>> = result.hits().iter().map(|h| h.curve_parameter).collect();
59/// assert_eq!(parameters, [Some(3.0), Some(7.0)]);
60/// assert_eq!(sphere.intersect(&line, Tolerance::DEFAULT), result);
61/// ```
62pub trait Intersect<Rhs: ?Sized = Self> {
63    /// The result of intersecting `Self` with `Rhs`.
64    type Output;
65
66    /// Intersects `self` with `other`, deciding tangency and coincidence
67    /// with `tol`.
68    fn intersect(&self, other: &Rhs, tol: Tolerance) -> Self::Output;
69}
70
71/// An elementary curve the router solves exactly against every surface
72/// basis and every curve basis, through the pair's typed solver.
73///
74/// The concrete curves are their own basis, so a pair whose types are known
75/// at compile time is solved without converting to the untyped
76/// [`ElementaryCurve`] or dispatching at run time; [`ElementaryCurve`] is
77/// the basis of the curves whose kind is only known at run time.
78///
79/// [`basis_parameter_of`](Self::basis_parameter_of) gives the parameter of
80/// a hit's point when the curve is restricted to a range and the solver
81/// did not compute it: the range test needs it.
82pub(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    /// The parameter of `point`, a point on the curve.
100    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
116/// How a curve enters the router: its elementary basis (when it has one)
117/// and the parameter range it is restricted to (`None` = the basis's own
118/// bounds).
119pub(crate) trait CurveOperand: ParametricCurve {
120    /// The basis type: the curve's own type when it is a concrete
121    /// elementary curve, so the pair is solved by its typed solver.
122    type Basis: CurveBasis;
123
124    /// The elementary basis and the restricting parameter range, or `None`
125    /// when the curve has no elementary basis.
126    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    /// A trimmed curve of a trimmed curve keeps the inner basis and the
165    /// outer range: the outer range lies within the inner one.
166    #[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
174/// How a surface enters the router: its elementary basis, when it has one.
175pub(crate) trait SurfaceOperand: ParametricSurface {
176    /// The basis type: the surface's own type when it is a concrete
177    /// elementary surface, so the pair is solved by its typed solver.
178    type Basis: Copy;
179
180    /// The elementary basis, or `None` when the surface has none.
181    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
216/// The representative in `range` of a point at `parameter`, or `None`
217/// when the point lies farther than `tol.confusion` in space beyond the
218/// range.
219///
220/// On a periodic curve the parameter is first shifted by whole periods
221/// into `[range.0, range.0 + period)`. A parameter inside the range is
222/// returned as it is. One beyond an end is measured in space: the
223/// parameter excess times `speed` at that end (the line direction's
224/// length, the circle's radius, the derivative's length on other curves).
225/// Within `tol.confusion` the point is kept, snapped to that end. On a
226/// periodic curve the end measured is the nearer one modulo the period,
227/// so a range starting or ending at the seam keeps a point a hair on the
228/// other side of it. `speed` is only called for a point outside the range.
229pub(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
259/// The end of `range` nearest `parameter`, measured modulo `period` on a
260/// periodic curve: the representative of a point that touches the range
261/// from outside it. Clamping would pick the wrong end for a point just past
262/// the seam (angle `5e-8` against a range ending at 2π).
263fn 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/// The parameter a hit keeps on one curve, or `None` when the hit lies
279/// outside the curve's range.
280///
281/// On a curve restricted to `range` the range test needs the parameter:
282/// the `stored` one, or the one `recover` computes from the point when the
283/// solver did not, and the hit keeps its representative in the range (see
284/// [`in_range`]). On an unrestricted curve the hit keeps what the solver
285/// stored, computed or not.
286#[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
303/// Clips an overlap `(a, b)` to `range`, shifting by periods like
304/// [`in_range`].
305///
306/// Returns the pieces of the overlap that lie in the range, ordered by
307/// start, each expressed in the range's representation: none, one, or two
308/// when the overlap meets the range on both sides of the seam of a periodic
309/// curve. An overlap covering the whole period returns the range as one
310/// piece. The three slots hold the three shifted copies the search tries,
311/// so the list never fills up.
312///
313/// Lengths are measured in space: `speed` gives the length of the curve's
314/// derivative at a parameter (the line direction's length, the circle's
315/// radius). A piece whose length in space is at most `tol.confusion`,
316/// including a gap that small between the overlap and the range, is kept
317/// as a single point (see [`is_single_point`]): the overlap only touches
318/// the range there. Such a point is dropped when it is, up to a period,
319/// the end of another piece, so a touch is never reported twice.
320pub(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
371/// Whether a piece clipped by [`clip_range`] is at most `tol.confusion`
372/// long in space, the parameter length times the curve's `speed` there: a
373/// single point where the overlap touches the range. A piece with its ends
374/// swapped is a gap, measured the same way.
375pub(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/// Intersects a curve with a surface: the exact path when the curve has an
385/// elementary basis, the surface is elementary and the pair has a closed
386/// form; the numeric search otherwise.
387#[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    // A trimmed curve is searched over its own range only: no clipping.
405    intersect_curve_surface(curve, surface, tol)
406}
407
408/// Turns the exact intersection of the curve's elementary `basis` with the
409/// surface into the routed result: hits outside `range` dropped, the kept
410/// ones carrying their representative in `range`. The surface parameters
411/// are not computed: the caller recovers them from the point if needed.
412#[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        // The whole curve lies on the surface: a trimmed curve over its
442        // range, the basis over its bounds (infinite for a line).
443        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
456/// Implements [`Intersect`] for every curve of the first list with every
457/// surface of the second, in both orders. The surface-first order returns
458/// the same result as the curve-first one.
459macro_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
489/// Implements [`Intersect`] between a trimmed curve over any curve operand
490/// and each surface, in both orders.
491macro_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
524/// Exact intersection of two surface bases, whatever the shape of the
525/// pair's typed result: the general [`ExactSurfaceIntersection`].
526///
527/// A pair of concrete surfaces converts its own typed result (a small,
528/// stack-only value), so the pair is solved without dispatching at run
529/// time; only an enum operand goes through the enum's dispatch.
530pub(crate) trait ExactSurfaceSurface<B> {
531    /// Intersects `self` with `other`, deciding tangency and coincidence
532    /// with `tol`.
533    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
547/// Implements [`ExactSurfaceSurface`] between each concrete surface and
548/// the enum, in both orders, by wrapping the concrete one.
549macro_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/// Intersects two surfaces: the exact path when both have an elementary
578/// basis and the pair has a closed form; marching otherwise.
579#[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/// Turns the exact intersection of two surface bases into the routed
598/// result.
599#[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
625/// Implements [`Intersect`] for every surface of the list with every
626/// surface of the list, in both orders (the full product).
627macro_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/// Intersects two curves: the exact path when both have an elementary
649/// basis, the numeric search otherwise.
650#[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    // A trimmed curve is searched over its own range only: no clipping.
670    intersect_curve_curve(a, b, tol)
671}
672
673/// An elementary basis and the parameter range restricting it.
674type Operand<C> = (C, Option<(f64, f64)>);
675
676/// Turns the exact intersection of two elementary bases into the routed
677/// result: hits outside either range dropped, the kept ones carrying their
678/// representative in each range (see [`kept_parameter`]), and a
679/// coincidence clipped to both ranges.
680#[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        // Neither basis is restricted: the solver's hits are kept as they
689        // are, with no range test.
690        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        // Rare: the coincidence is worked out on the untyped curves, so the
723        // overlap mapping exists once.
724        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
749/// The overlaps of two coincident curves restricted to `first_range` and
750/// `second_range`.
751///
752/// Each overlap holds a monotone range in each curve's own range: the
753/// second range is mapped onto the first curve and clipped there (two
754/// pieces when it crosses the first curve's seam), then each piece is
755/// mapped back onto the second curve and split where it crosses the
756/// second curve's seam.
757///
758/// A piece at most `tol.confusion` long in space, where the ranges only
759/// touch (two consecutive pieces of one line or circle), is not an overlap: it
760/// comes back in [`CoincidentPieces::touches`] as a parameter pair, each
761/// parameter the point's representative in its range.
762fn 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        // The piece crosses the second curve's seam: read the first curve's
807        // part of each side back from its end points.
808        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
827/// What two coincident curves restricted to parameter ranges share.
828struct CoincidentPieces {
829    /// The overlaps, ordered by start on the first curve.
830    overlaps: CurveCurveOverlapList,
831    /// The `(first, second)` parameters of the points where the ranges
832    /// only touch, ordered by first parameter. At most one per clipped
833    /// piece, so three slots never fill up.
834    touches: InlineList<(f64, f64), 3>,
835}
836
837/// What the overlap mapping needs from a curve, in 2D or 3D, to relate it
838/// to a coincident curve of the same kind.
839pub(crate) trait CoincidentCurve {
840    /// The period, or `None` when the curve is not periodic.
841    fn cycle_length(&self) -> Option<f64>;
842
843    /// The parameter, on this curve, of the point `other` has at
844    /// `parameter`; `other` coincides with this curve.
845    fn parameter_of_image(&self, other: &Self, parameter: f64) -> f64;
846
847    /// Whether this curve at `parameter` and `other` at the same point
848    /// run the same way.
849    fn runs_with(&self, parameter: f64, other: &Self) -> bool;
850
851    /// The length of the first derivative at `parameter`: the line
852    /// direction's length, the circle's radius.
853    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
902/// Whether the coincident curves `from` and `to` run the same way: the
903/// sign of the tangents' dot product at a sample of `range`.
904fn 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
914/// The image of `range` of curve `from` on the coincident curve `to`, as a
915/// monotone interval that may lie outside `to`'s own representation.
916/// `along` says whether the curves run the same way.
917///
918/// An infinite end maps to the infinity its direction gives on `to`. On a
919/// periodic curve the image starts at the image of the range's start in
920/// the direction of travel, and a full period maps to a full period.
921fn 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
955/// Implements [`Intersect`] for every curve of the list with every curve of
956/// the list, the receiver read first.
957macro_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
987/// Implements [`Intersect`] between a trimmed curve over any curve operand
988/// and each curve of the list, in both orders.
989macro_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
1037/// The exact intersection of two 2D bases before the parameter ranges are
1038/// applied: at most two hits, or a coincidence.
1039pub(crate) enum ExactCurveCurve2DIntersection {
1040    /// The hits; `None` marks an empty slot.
1041    Hits([Option<CurveCurveHit2D>; 2]),
1042    /// The curves coincide.
1043    Coincident,
1044}
1045
1046/// The exact intersection of a 2D curve with a 2D curve of type `B`, solved
1047/// by the pair's typed solver. A hit stores the parameters the solver
1048/// computed (a line's) and leaves the others `None`.
1049pub(crate) trait ExactCurveCurve2D<B> {
1050    /// Intersects `self` with `other`, deciding tangency and coincidence
1051    /// with `tol`.
1052    fn exact_curve_curve_2d(&self, other: &B, tol: Tolerance) -> ExactCurveCurve2DIntersection;
1053}
1054
1055/// A hit read from the first curve, with the parameters the solver
1056/// computed.
1057fn 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
1135/// Implements [`ExactCurveCurve2D`] between each concrete 2D curve and the
1136/// untyped [`Curve2D`], in both orders, by dispatching on the untyped
1137/// operand.
1138macro_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
1186/// A 2D curve the router solves exactly against every 2D basis: the
1187/// concrete curves are their own basis, [`Curve2D`] the basis of a curve
1188/// whose kind is only known at run time.
1189pub(crate) trait CurveBasis2D:
1190    ParametricCurve2D
1191    + Copy
1192    + Into<Curve2D>
1193    + ExactCurveCurve2D<Line2D>
1194    + ExactCurveCurve2D<Circle2D>
1195    + ExactCurveCurve2D<Curve2D>
1196{
1197    /// The parameter of `point`, a point on the curve: the range test of a
1198    /// restricted curve needs it when the solver did not compute it.
1199    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
1226/// How a 2D curve enters the router: its basis and the parameter range it
1227/// is restricted to (`None` = the basis's own bounds).
1228pub(crate) trait CurveOperand2D: ParametricCurve2D {
1229    /// The basis type: the curve's own type when it is concrete, so the
1230    /// pair is solved by its typed solver.
1231    type Basis: CurveBasis2D;
1232
1233    /// The basis and the restricting parameter range.
1234    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    /// A trimmed curve of a trimmed curve keeps the inner basis and the
1256    /// outer range: the outer range lies within the inner one.
1257    #[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/// Intersects two 2D curves. Every pair has a closed form, so the result
1265/// is exact and complete.
1266#[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        // Neither basis is restricted: the solver's hits are kept as they
1282        // are, with no range test.
1283        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
1318/// The result for two coincident 2D bases restricted to their ranges.
1319fn 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
1341/// Implements [`Intersect`] for every 2D curve of the list with every one
1342/// of the list, the receiver read first.
1343macro_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
1364/// Implements [`Intersect`] between a trimmed 2D curve over any operand and
1365/// each 2D curve of the list, in both orders.
1366macro_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    /// The speed of a unit-speed line or of a circle of radius 1.
1412    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        // Just past 2π against a range ending there: the end, not the start.
1447        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    /// The speed of a unit-speed line or of a circle of radius 1.
1468    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        // Arcs [0, 5] and [4, 7] of one circle share [4, 5] and [0, 7 - TAU].
1502        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        // The copy shifted by a period touches the full range at TAU, the
1522        // same point as the start of the real piece.
1523        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        // A trimmed circle routes through the typed circle solver.
1535        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}