math_utils/geometry/intersect/
mod.rs

1//! Primitive intersection routines
2
3use approx;
4
5use crate::*;
6use super::*;
7
8pub mod integer;
9
10/// Discrete intersection test of 1D axis-aligned bounding boxes (intervals).
11///
12/// AABBs that are merely touching are not counted as intersecting:
13///
14/// ```
15/// # use math_utils::geometry::*;
16/// # use math_utils::geometry::intersect::*;
17/// let a = Interval::from_points ( 0.0, 1.0);
18/// let b = Interval::from_points (-1.0, 0.0);
19/// assert!(!discrete_interval (&a, &b));
20/// ```
21#[inline]
22pub fn discrete_interval <S : Ring> (a : &Interval <S>, b : &Interval <S>) -> bool {
23  let (min_a, max_a) = (a.min(), a.max());
24  let (min_b, max_b) = (b.min(), b.max());
25  // intersection if intervals overlap
26  max_a > min_b && min_a < max_b
27}
28
29/// Continuous intersection test of 1D axis-aligned bounding boxes (intervals).
30///
31/// AABBs that are merely touching return no intersection:
32///
33/// ```
34/// # use math_utils::geometry::*;
35/// # use math_utils::geometry::intersect::*;
36/// let a = Interval::from_points ( 0.0, 1.0);
37/// let b = Interval::from_points (-1.0, 0.0);
38/// assert!(continuous_interval (&a, &b).is_none());
39/// ```
40#[inline]
41pub fn continuous_interval <S> (a : &Interval <S>, b : &Interval <S>)
42  -> Option <Interval <S>>
43where S : Ring + std::fmt::Debug {
44  if discrete_interval (a, b) {
45    Some (
46      Interval::with_minmax (S::max (a.min(), b.min()), S::min (a.max(), b.max())))
47  } else {
48    None
49  }
50}
51
52/// Discrete intersection test of 2D axis-aligned bounding boxes.
53///
54/// AABBs that are merely touching are not counted as intersecting:
55///
56/// ```
57/// # use math_utils::geometry::*;
58/// # use math_utils::geometry::intersect::*;
59/// let a = Aabb2::from_points ([ 0.0, 0.0].into(), [1.0, 1.0].into());
60/// let b = Aabb2::from_points ([-1.0, 0.0].into(), [0.0, 1.0].into());
61/// assert!(!discrete_aabb2_aabb2 (&a, &b));
62/// ```
63#[inline]
64pub fn discrete_aabb2_aabb2 <S : Ring> (a : &Aabb2 <S>, b : &Aabb2 <S>) -> bool {
65  let (min_a, max_a) = (a.min(), a.max());
66  let (min_b, max_b) = (b.min(), b.max());
67  // intersection if overlap exists on both axes
68  max_a.0.x > min_b.0.x && min_a.0.x < max_b.0.x &&
69  max_a.0.y > min_b.0.y && min_a.0.y < max_b.0.y
70}
71
72/// Continuous intersection test of 2D axis-aligned bounding boxes.
73///
74/// AABBs that are merely touching return no intersection:
75///
76/// ```
77/// # use math_utils::geometry::*;
78/// # use math_utils::geometry::intersect::*;
79/// let a = Aabb2::from_points ([ 0.0, 0.0].into(), [1.0, 1.0].into());
80/// let b = Aabb2::from_points ([-1.0, 0.0].into(), [0.0, 1.0].into());
81/// assert!(continuous_aabb2_aabb2 (&a, &b).is_none());
82/// ```
83#[inline]
84pub fn continuous_aabb2_aabb2 <S> (a : &Aabb2 <S>, b : &Aabb2 <S>)
85  -> Option <Aabb2 <S>>
86where S : Ring + std::fmt::Debug {
87  if discrete_aabb2_aabb2 (a, b) {
88    Some (
89      Aabb2::with_minmax (point2_max (a.min(), b.min()), point2_min (a.max(), b.max())))
90  } else {
91    None
92  }
93}
94
95/// Discrete intersection test of 3D axis-aligned bounding boxes.
96///
97/// AABBs that are merely touching are not counted as intersecting:
98///
99/// ```
100/// # use math_utils::geometry::*;
101/// # use math_utils::geometry::intersect::*;
102/// let a = Aabb3::from_points ([ 0.0, 0.0, 0.0].into(), [1.0, 1.0, 1.0].into());
103/// let b = Aabb3::from_points ([-1.0, 0.0, 0.0].into(), [0.0, 1.0, 1.0].into());
104/// assert!(!discrete_aabb3_aabb3 (&a, &b));
105/// ```
106#[inline]
107pub fn discrete_aabb3_aabb3 <S : Ring> (a : &Aabb3 <S>, b : &Aabb3 <S>) -> bool {
108  let (min_a, max_a) = (a.min(), a.max());
109  let (min_b, max_b) = (b.min(), b.max());
110  // intersection if overlap exists on all three axes
111  max_a.0.x > min_b.0.x && min_a.0.x < max_b.0.x &&
112  max_a.0.y > min_b.0.y && min_a.0.y < max_b.0.y &&
113  max_a.0.z > min_b.0.z && min_a.0.z < max_b.0.z
114}
115
116/// Continuous intersection test of 3D axis-aligned bounding boxes.
117///
118/// AABBs that are merely touching return no intersection:
119///
120/// ```
121/// # use math_utils::geometry::*;
122/// # use math_utils::geometry::intersect::*;
123/// let a = Aabb3::from_points ([ 0.0, 0.0, 0.0].into(), [1.0, 1.0, 1.0].into());
124/// let b = Aabb3::from_points ([-1.0, 0.0, 0.0].into(), [0.0, 1.0, 1.0].into());
125/// assert!(continuous_aabb3_aabb3 (&a, &b).is_none());
126/// ```
127#[inline]
128pub fn continuous_aabb3_aabb3 <S> (a : &Aabb3 <S>, b : &Aabb3 <S>)
129  -> Option <Aabb3 <S>>
130where S : Ring + std::fmt::Debug {
131  if discrete_aabb3_aabb3 (a, b) {
132    Some (
133      Aabb3::with_minmax (point3_max (a.min(), b.min()), point3_min (a.max(), b.max())))
134  } else {
135    None
136  }
137}
138
139/// Compute the intersection of a 2D line with a 2D AABB (rectangle).
140///
141/// If the line and AABB intersect, the two intersection points are returned with the
142/// scalar parameter corresponding to that point along the line.
143///
144/// ```
145/// # use math_utils::*;
146/// # use math_utils::geometry::*;
147/// # use math_utils::geometry::intersect::*;
148/// let aabb = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into());
149/// let line = Line2::new  ([0.0, 0.0].into(), Unit2::axis_x());
150/// assert_eq!(
151///   continuous_line2_aabb2 (&line, &aabb).unwrap(),
152///   ( (-1.0, [-1.0, 0.0].into()),
153///     ( 1.0, [ 1.0, 0.0].into())
154///   )
155/// );
156/// ```
157///
158/// Returns `None` if the line and AABB are tangent:
159///
160/// ```
161/// # use math_utils::*;
162/// # use math_utils::geometry::*;
163/// # use math_utils::geometry::intersect::*;
164/// let aabb = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into());
165/// let line = Line2::new  ([0.0, 1.0].into(), Unit2::axis_x());
166/// assert_eq!(continuous_line2_aabb2 (&line, &aabb), None);
167/// ```
168#[expect(clippy::type_complexity)]
169pub fn continuous_line2_aabb2 <S> (line : &Line2 <S>, aabb : &Aabb2 <S>)
170  -> Option <((S, Point2 <S>), (S, Point2 <S>))>
171where S : Real + std::fmt::Debug {
172  let aabb_min = aabb.min();
173  let aabb_max = aabb.max();
174  if line.direction.x == S::zero() {
175    // parallel x-axis
176    if aabb_min.0.x < line.base.0.x && line.base.0.x < aabb_max.0.x {
177      let out = if line.direction.y > S::zero() {
178        let (t0, t1) = (aabb_min.0.y - line.base.0.y, aabb_max.0.y - line.base.0.y);
179        ( (t0, [line.base.0.x, aabb_min.0.y].into()),
180          (t1, [line.base.0.x, aabb_max.0.y].into())
181        )
182      } else {
183        let (t0, t1) = (line.base.0.y - aabb_max.0.y, line.base.0.y - aabb_min.0.y);
184        ( (t0, [line.base.0.x, aabb_max.0.y].into()),
185          (t1, [line.base.0.x, aabb_min.0.y].into())
186        )
187      };
188      Some (out)
189    } else {
190      None
191    }
192  } else if line.direction.y == S::zero() {
193    // parallel y-axis
194    if aabb_min.0.y < line.base.0.y && line.base.0.y < aabb_max.0.y {
195      let out = if line.direction.x > S::zero() {
196        let (t0, t1) = (aabb_min.0.x - line.base.0.x, aabb_max.0.x - line.base.0.x);
197        ( (t0, [aabb_min.0.x, line.base.0.y].into()),
198          (t1, [aabb_max.0.x, line.base.0.y].into())
199        )
200      } else {
201        let (t0, t1) = (line.base.0.x - aabb_max.0.x, line.base.0.x - aabb_min.0.x);
202        ( (t0, [aabb_max.0.x, line.base.0.y].into()),
203          (t1, [aabb_min.0.x, line.base.0.y].into())
204        )
205      };
206      Some (out)
207    } else {
208      None
209    }
210  } else {
211    let dir_reciprocal = line.direction.map (|s| S::one() / s);
212    let (t0_x, t1_x)   = {
213      let (near_x, far_x) = if line.direction.x.is_positive() {
214        (aabb_min.0.x, aabb_max.0.x)
215      } else {
216        (aabb_max.0.x, aabb_min.0.x)
217      };
218      ( (near_x - line.base.0.x) * dir_reciprocal.x,
219        (far_x  - line.base.0.x) * dir_reciprocal.x
220      )
221    };
222    let (t0_y, t1_y) = {
223      let (near_y, far_y) = if line.direction.y.is_positive() {
224        (aabb_min.0.y, aabb_max.0.y)
225      } else {
226        (aabb_max.0.y, aabb_min.0.y)
227      };
228      ( (near_y - line.base.0.y) * dir_reciprocal.y,
229        (far_y  - line.base.0.y) * dir_reciprocal.y
230      )
231    };
232    let interval_x = Interval::with_minmax (t0_x, t1_x);
233    let interval_y = Interval::with_minmax (t0_y, t1_y);
234    continuous_interval (&interval_x, &interval_y).map (|interval|{
235      let start = line.point (interval.min());
236      let end   = line.point (interval.max());
237      ( (interval.min(), start), (interval.max(), end) )
238    })
239  }
240}
241
242/// Compute the intersection of a 2D line with a 2D sphere (circle).
243///
244/// If the line and circle intersect, the two intersection points are returned with the
245/// scalar parameter corresponding to that point along the line.
246///
247/// ```
248/// # use math_utils::*;
249/// # use math_utils::geometry::*;
250/// # use math_utils::geometry::intersect::*;
251/// let sphere = Sphere2::unit();
252/// let line   = Line2::new  ([0.0, 0.0].into(), Unit2::axis_x());
253/// assert_eq!(
254///   continuous_line2_sphere2 (&line, &sphere).unwrap(),
255///   ( (-1.0, [-1.0, 0.0].into()),
256///     ( 1.0, [ 1.0, 0.0].into())
257///   )
258/// );
259/// ```
260///
261/// Returns `None` if the line and circle are tangent:
262///
263/// ```
264/// # use math_utils::*;
265/// # use math_utils::geometry::*;
266/// # use math_utils::geometry::intersect::*;
267/// let sphere = Sphere2::unit();
268/// let line   = Line2::new  ([0.0, 1.0].into(), Unit2::axis_x());
269/// assert_eq!(continuous_line2_sphere2 (&line, &sphere), None);
270/// ```
271#[expect(clippy::type_complexity)]
272pub fn continuous_line2_sphere2 <S : Real> (line : &Line2 <S>, sphere : &Sphere2 <S>)
273  -> Option <((S, Point2 <S>), (S, Point2 <S>))>
274{
275  // intersect the line with the cylinder circle in the XY plane
276  let two  = S::two();
277  let four = S::four();
278  // defining intersection points by the equation at^2 + bt + c = 0, solve for t using
279  // quadratic formula:
280  //
281  //  t = (-b +- sqrt (b^2 -4ac)) / 2a
282  //
283  // where a, b, and c are defined in terms of points p1, p2 of the line segment and the
284  // sphere center p3, and sphere radius r
285  let p1   = line.base;
286  #[expect(clippy::no_effect_underscore_binding)]
287  let _p2   = line.base + *line.direction;
288  let p3   = sphere.center;
289  let r    = *sphere.radius;
290  let p1p2 = &line.direction;
291  let p3p1 = p1 - p3;
292  let a    = S::one();
293  let b    = two * p1p2.dot (p3p1);
294  let c    = p3p1.norm_squared() - r * r;
295  // this is the portion of the quadratic equation inside the square root that
296  // determines whether the intersection is none, a tangent point, or a segment
297  let discriminant = b * b - four * a * c;
298  if discriminant <= S::zero() {
299    None
300  } else {
301    let discriminant_sqrt = discriminant.sqrt();
302    let frac_2a           = S::one() / (two * a);
303    let t1                = (-b - discriminant_sqrt) * frac_2a;
304    let t2                = (-b + discriminant_sqrt) * frac_2a;
305    let first  = p1 + (**p1p2) * t1;
306    let second = p1 + (**p1p2) * t2;
307    Some (((t1, first), (t2, second)))
308  }
309}
310
311/// Compute the continuous intersection of a 3D line with a 3D plane.
312///
313/// Returns `None` if the line and plane are parallel:
314///
315/// ```
316/// # use math_utils::*;
317/// # use math_utils::geometry::*;
318/// # use math_utils::geometry::intersect::*;
319/// let plane = Plane3::new ([0.0, 0.0,  0.0].into(), Unit3::axis_z());
320/// let line  = Line3::new  (
321///   [0.0, 0.0, 0.0].into(),
322///   Unit3::normalize ([1.0, 1.0, 0.0].into()));
323/// assert_eq!(continuous_line3_plane3 (&line, &plane), None);
324/// ```
325pub fn continuous_line3_plane3 <S> (line : &Line3 <S>, plane : &Plane3 <S>)
326  -> Option <(S, Point3 <S>)>
327where S : Real + approx::RelativeEq {
328  let normal_dot_direction = plane.normal.dot (*line.direction);
329  if approx::relative_eq!(normal_dot_direction, S::zero()) {
330    None
331  } else {
332    let plane_to_line = line.base - plane.base;
333    let t     = -plane.normal.dot (plane_to_line) / normal_dot_direction;
334    let point = line.point (t);
335    Some ((t, point))
336  }
337}
338
339/// Compute the intersection of a 3D line with a 3D AABB.
340///
341/// If the line and AABB intersect, the two intersection points are returned with the
342/// scalar parameter corresponding to that point along the line.
343///
344/// ```
345/// # use math_utils::*;
346/// # use math_utils::geometry::*;
347/// # use math_utils::geometry::intersect::*;
348/// let aabb = Aabb3::with_minmax (
349///   [-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into());
350/// let line = Line3::new  ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
351/// assert_eq!(
352///   continuous_line3_aabb3 (&line, &aabb).unwrap(),
353///   ( (-1.0, [-1.0, 0.0, 0.0].into()),
354///     ( 1.0, [ 1.0, 0.0, 0.0].into())
355///   )
356/// );
357/// ```
358///
359/// Returns `None` if the line and AABB are tangent:
360///
361/// ```
362/// # use math_utils::*;
363/// # use math_utils::geometry::*;
364/// # use math_utils::geometry::intersect::*;
365/// let aabb = Aabb3::with_minmax (
366///   [-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into());
367/// let line = Line3::new  ([0.0, 1.0, 0.0].into(), Unit3::axis_x());
368/// assert_eq!(continuous_line3_aabb3 (&line, &aabb), None);
369/// ```
370#[expect(clippy::type_complexity)]
371pub fn continuous_line3_aabb3 <S> (line : &Line3 <S>, aabb : &Aabb3 <S>)
372  -> Option <((S, Point3 <S>), (S, Point3 <S>))>
373where S : Real + num_traits::Float + approx::RelativeEq <Epsilon=S> + std::fmt::Debug {
374  let aabb_min = aabb.min();
375  let aabb_max = aabb.max();
376  if line.direction.x == S::zero() {
377    if aabb_min.0.x < line.base.0.x && line.base.0.x < aabb_max.0.x {
378      let line2 = Line2::new (
379        [line.base.0.y, line.base.0.z].into(),
380        Unit2::unchecked_approx ([line.direction.y, line.direction.z].into()));
381      let aabb2 = Aabb2::with_minmax (
382        [aabb_min.0.y, aabb_min.0.z].into(),
383        [aabb_max.0.y, aabb_max.0.z].into());
384      continuous_line2_aabb2 (&line2, &aabb2).map (|((t0, p0), (t1, p1))|
385        ( (t0, [line.base.0.x, p0.0.x, p0.0.y].into()),
386          (t1, [line.base.0.x, p1.0.x, p1.0.y].into())
387        )
388      )
389    } else {
390      None
391    }
392  } else if line.direction.y == S::zero() {
393    if aabb_min.0.y < line.base.0.y && line.base.0.y < aabb_max.0.y {
394      let line2 = Line2::new (
395        [line.base.0.x, line.base.0.z].into(),
396        Unit2::unchecked_approx ([line.direction.x, line.direction.z].into()));
397      let aabb2 = Aabb2::with_minmax (
398        [aabb_min.0.x, aabb_min.0.z].into(),
399        [aabb_max.0.x, aabb_max.0.z].into());
400      continuous_line2_aabb2 (&line2, &aabb2).map (|((t0, p0), (t1, p1))|
401        ( (t0, [p0.0.x, line.base.0.y, p0.0.y].into()),
402          (t1, [p1.0.x, line.base.0.y, p1.0.y].into())
403        )
404      )
405    } else {
406      None
407    }
408  } else if line.direction.z == S::zero() {
409    if aabb_min.0.z < line.base.0.z && line.base.0.z < aabb_max.0.z {
410      let line2 = Line2::new (
411        [line.base.0.x, line.base.0.y].into(),
412        Unit2::unchecked_approx ([line.direction.x, line.direction.y].into()));
413      let aabb2 = Aabb2::with_minmax (
414        [aabb_min.0.x, aabb_min.0.y].into(),
415        [aabb_max.0.x, aabb_max.0.y].into());
416      continuous_line2_aabb2 (&line2, &aabb2).map (|((t0, p0), (t1, p1))|
417        ( (t0, [p0.0.x, p0.0.y, line.base.0.z].into()),
418          (t1, [p1.0.x, p1.0.y, line.base.0.z].into())
419        )
420      )
421    } else {
422      None
423    }
424  } else {
425    let dir_reciprocal = line.direction.map (|s| S::one() / s);
426    let (t0_x, t1_x)   = {
427      let (near_x, far_x) = if line.direction.x.is_positive() {
428        (aabb_min.0.x, aabb_max.0.x)
429      } else {
430        (aabb_max.0.x, aabb_min.0.x)
431      };
432      ( (near_x - line.base.0.x) * dir_reciprocal.x,
433        (far_x  - line.base.0.x) * dir_reciprocal.x
434      )
435    };
436    let (t0_y, t1_y) = {
437      let (near_y, far_y) = if line.direction.y.is_positive() {
438        (aabb_min.0.y, aabb_max.0.y)
439      } else {
440        (aabb_max.0.y, aabb_min.0.y)
441      };
442      ( (near_y - line.base.0.y) * dir_reciprocal.y,
443        (far_y  - line.base.0.y) * dir_reciprocal.y
444      )
445    };
446    let (t0_z, t1_z) = {
447      let (near_z, far_z) = if line.direction.z.is_positive() {
448        (aabb_min.0.z, aabb_max.0.z)
449      } else {
450        (aabb_max.0.z, aabb_min.0.z)
451      };
452      ( (near_z - line.base.0.z) * dir_reciprocal.z,
453        (far_z  - line.base.0.z) * dir_reciprocal.z
454      )
455    };
456    let interval_x = Interval::with_minmax (t0_x, t1_x);
457    let interval_y = Interval::with_minmax (t0_y, t1_y);
458    continuous_interval (&interval_x, &interval_y).and_then (|interval| {
459      let interval_z = Interval::with_minmax (t0_z, t1_z);
460      continuous_interval (&interval, &interval_z).map (|interval|{
461        let start = line.point (interval.min());
462        let end   = line.point (interval.max());
463        ( (interval.min(), start), (interval.max(), end) )
464      })
465    })
466  }
467}
468
469/// Compute the continuous intersection of a 3D line with a 3D sphere.
470///
471/// If the line and sphere intersect, the two intersection points are returned with the
472/// scalar parameter corresponding to that point along the line.
473///
474/// ```
475/// # use math_utils::*;
476/// # use math_utils::geometry::*;
477/// # use math_utils::geometry::intersect::*;
478/// let sphere = Sphere3::unit();
479/// let line = Line3::new  ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
480/// assert_eq!(
481///   continuous_line3_sphere3 (&line, &sphere).unwrap(),
482///   ( (-1.0, [-1.0, 0.0, 0.0].into()),
483///     ( 1.0, [ 1.0, 0.0, 0.0].into())
484///   )
485/// );
486/// ```
487///
488/// Returns `None` if the line and sphere are tangent:
489///
490/// ```
491/// # use math_utils::*;
492/// # use math_utils::geometry::*;
493/// # use math_utils::geometry::intersect::*;
494/// let sphere = Sphere3::unit();
495/// let line   = Line3::new  ([0.0, 0.0, 1.0].into(), Unit3::axis_x());
496/// assert_eq!(continuous_line3_sphere3 (&line, &sphere), None);
497/// ```
498#[expect(clippy::type_complexity)]
499pub fn continuous_line3_sphere3 <S : Real> (line : &Line3 <S>, sphere : &Sphere3 <S>)
500  -> Option <((S, Point3 <S>), (S, Point3 <S>))>
501{
502  let two  = S::two();
503  let four = S::four();
504  // defining intersection points by the equation at^2 + bt + c = 0, solve for t using
505  // quadratic formula:
506  //
507  //  t = (-b +- sqrt (b^2 -4ac)) / 2a
508  //
509  // where a, b, and c are defined in terms of points p1, p2 of the line segment and the
510  // sphere center p3, and sphere radius r
511  let p1   = line.base;
512  #[expect(clippy::no_effect_underscore_binding)]
513  let _p2   = line.base + *line.direction;
514  let p3   = sphere.center;
515  let r    = *sphere.radius;
516  let p1p2 = &line.direction;
517  let p3p1 = p1 - p3;
518  let a    = S::one();
519  let b    = two * p1p2.dot (p3p1);
520  let c    = p3p1.norm_squared() - r * r;
521  // this is the portion of the quadratic equation inside the square root that
522  // determines whether the intersection is none, a tangent point, or a segment
523  let discriminant = b * b - four * a * c;
524  if discriminant <= S::zero() {
525    None
526  } else {
527    let discriminant_sqrt = discriminant.sqrt();
528    let frac_2a           = S::one() / (two * a);
529    let t1                = (-b - discriminant_sqrt) * frac_2a;
530    let t2                = (-b + discriminant_sqrt) * frac_2a;
531    let first  = p1 + (**p1p2) * t1;
532    let second = p1 + (**p1p2) * t2;
533    Some (((t1, first), (t2, second)))
534  }
535}
536
537/// Compute continuous intersection of a 2D line segment with a 2D AABB (rectangle).
538///
539/// The points are returned in the order given by the ordering of the points in the
540/// segment.
541///
542/// Note that a segment that is tangent to the AABB returns no intersection.
543#[expect(clippy::type_complexity)]
544pub fn continuous_segment2_aabb2 <S> (segment : &Segment2 <S>, aabb : &Aabb2 <S>)
545  -> Option <((S, Point2 <S>), (S, Point2 <S>))>
546where S : Real + std::fmt::Debug {
547  let vector = *segment.point_b() - segment.point_a();
548  let length = vector.norm();
549  let line   = Line2::new (*segment.point_a(), Unit2::unchecked (vector / length));
550  if let Some (((t0, _p0), (t1, _p1))) = continuous_line2_aabb2 (&line, aabb) {
551    let interval = Interval::with_minmax (S::zero(), length);
552    continuous_interval (&interval, &Interval::with_minmax (t0, t1)).map (
553      |interval|
554      ( (interval.min() / length, line.point (interval.min())),
555        (interval.max() / length, line.point (interval.max()))
556      )
557    )
558  } else {
559    None
560  }
561}
562
563/// Compute continuous intersection of a 2D line segment with a 2D sphere (circle).
564///
565/// The points are returned in the order given by the ordering of the points in the
566/// segment.
567///
568/// Note that a segment that is tangent to the surface of the circle returns no
569/// intersection.
570#[expect(clippy::type_complexity)]
571pub fn continuous_segment2_sphere2 <S> (segment : &Segment2 <S>, sphere : &Sphere2 <S>)
572  -> Option <((S, Point2 <S>), (S, Point2<S>))>
573where S : Real + std::fmt::Debug {
574  let vector = *segment.point_b() - segment.point_a();
575  let length = vector.norm();
576  let line   = Line2::new (*segment.point_a(), Unit2::unchecked (vector / length));
577  if let Some (((t0, _p0), (t1, _p1))) = continuous_line2_sphere2 (&line, sphere) {
578    let interval = Interval::with_minmax (S::zero(), length);
579    continuous_interval (&interval, &Interval::with_minmax (t0, t1)).map (|interval|
580      ( (interval.min() / length, line.point (interval.min())),
581        (interval.max() / length, line.point (interval.max()))
582      )
583    )
584  } else {
585    None
586  }
587}
588
589/// Compute continuous intersection of a 3D line segment with an AABB.
590///
591/// The points are returned in the order given by the ordering of the points in the
592/// segment.
593///
594/// Note that a segment that is tangent to the AABB returns no intersection.
595#[expect(clippy::type_complexity)]
596pub fn continuous_segment3_aabb3 <S> (segment : &Segment3 <S>, aabb : &Aabb3 <S>)
597  -> Option <((S, Point3 <S>), (S, Point3<S>))>
598where S : Real + num_traits::Float + approx::RelativeEq <Epsilon=S> + std::fmt::Debug {
599  let vector = *segment.point_b() - segment.point_a();
600  let length = vector.norm();
601  let line = Line3::new (*segment.point_a(), Unit3::unchecked_approx (vector / length));
602  if let Some (((t0, _p0), (t1, _p1))) = continuous_line3_aabb3 (&line, aabb) {
603    let interval = Interval::with_minmax (S::zero(), length);
604    continuous_interval (&interval, &Interval::with_minmax (t0, t1)).map (|interval|
605      ( (interval.min() / length, line.point (interval.min())),
606        (interval.max() / length, line.point (interval.max()))
607      )
608    )
609  } else {
610    None
611  }
612}
613
614/// Compute continuous intersection of a 3D line segment with a sphere.
615///
616/// The points are returned in the order given by the ordering of the points in the
617/// segment.
618///
619/// Note that a segment that is tangent to the surface of the sphere returns no
620/// intersection.
621#[expect(clippy::type_complexity)]
622pub fn continuous_segment3_sphere3 <S> (segment : &Segment3 <S>, sphere : &Sphere3 <S>)
623  -> Option <((S, Point3 <S>), (S, Point3<S>))>
624where S : Field + Sqrt {
625  let two  = S::two();
626  let four = S::four();
627  // defining intersection points by the equation at^2 + bt + c = 0, solve for t using
628  // quadratic formula:
629  //
630  //  t = (-b +- sqrt (b^2 -4ac)) / 2a
631  //
632  // where a, b, and c are defined in terms of points p1, p2 of the line segment and the
633  // sphere center p3, and sphere radius r
634  let p1   = segment.point_a();
635  let p2   = segment.point_b();
636  let p3   = sphere.center;
637  let r    = *sphere.radius;
638  let p1p2 = *p2-p1;
639  let p3p1 = *p1-p3;
640  let a    = p1p2.norm_squared();
641  let b    = two * p1p2.dot (p3p1);
642  let c    = p3p1.norm_squared() - r * r;
643  // this is the portion of the quadratic equation inside the square root that
644  // determines whether the intersection is none, a tangent point, or a segment
645  let discriminant = b * b - four * a * c;
646  if discriminant <= S::zero() {
647    None
648  } else {
649    let discriminant_sqrt = discriminant.sqrt();
650    let frac_2a           = S::one() / (two * a);
651    let t1                = S::max ((-b - discriminant_sqrt) * frac_2a, S::zero());
652    let t2                = S::min ((-b + discriminant_sqrt) * frac_2a, S::one());
653    if t2 <= S::zero() || S::one() <= t1 {
654      None
655    } else {
656      let first  = *p1 + p1p2 * t1;
657      let second = *p1 + p1p2 * t2;
658      Some (((t1, first), (t2, second)))
659    }
660  }
661}
662
663/// Compute continuous intersection of a 3D line segment with an axis-aligned cylinder.
664///
665/// The points are returned in the order given by the ordering of the points in the
666/// segment.
667///
668/// Note that a segment that is tangent to the surface of the cylinder returns no
669/// intersection.
670#[expect(clippy::type_complexity)]
671pub fn continuous_segment3_cylinder3 <S> (
672  segment : &Segment3 <S>, cylinder : &Cylinder3 <S>
673) -> Option <((S, Point3 <S>), (S, Point3<S>))> where
674  S : Real + std::fmt::Debug
675{
676  let segment_aabb  = segment.aabb3();
677  let cylinder_aabb = cylinder.aabb3();
678  if !discrete_aabb3_aabb3 (&segment_aabb, &cylinder_aabb) {
679    None
680  } else {
681    let p1       = segment.point_a();
682    let p2       = segment.point_b();
683    let p3       = cylinder.center;
684    let r        = *cylinder.radius;
685    let r2       = r * r;
686    let p1p2     = *p2 - p1;
687    let p1_xy    = Point2::from ([p1.0.x, p1.0.y]);
688    let p2_xy    = Point2::from ([p2.0.x, p2.0.y]);
689    let p3_xy    = Point2::from ([p3.0.x, p3.0.y]);
690    let p3_z_max = cylinder_aabb.max().0.z;
691    let p3_z_min = cylinder_aabb.min().0.z;
692    if p1_xy == p2_xy {   // segment is aligned vertically (Z axis)
693      let d2 = (p1_xy - p3_xy).norm_squared();
694      if d2 >= r2 {
695        None
696      } else {
697        let (t1, begin_z) = if p1.0.z >= p3_z_max {
698          ((p3_z_max - p1.0.z) / p1p2.z, p3_z_max)
699        } else if p1.0.z <= p3_z_min {
700          ((p3_z_min - p1.0.z) / p1p2.z, p3_z_min)
701        } else {
702          (S::zero(), p1.0.z)
703        };
704        let (t2, end_z)   = if p2.0.z >= p3_z_max {
705          ((p3_z_max - p1.0.z) / p1p2.z, p3_z_max)
706        } else if p2.0.z <= p3_z_min {
707          ((p3_z_min - p1.0.z) / p1p2.z, p3_z_min)
708        } else {
709          (S::one(), p2.0.z)
710        };
711        let begin = [p1_xy.0.x, p1_xy.0.y, begin_z].into();
712        let end   = [p1_xy.0.x, p1_xy.0.y, end_z  ].into();
713        Some (((t1, begin), (t2, end)))
714      }
715    } else {    // segment is not aligned vertically
716      // intersect the line with the cylinder circle in the XY plane
717      let two     = S::two();
718      let four    = S::four();
719      let p1p2_xy = p1p2.xy();
720      let p3p1_xy = p1_xy - p3_xy;
721      let a       = p1p2_xy.norm_squared();
722      let b       = two * p1p2_xy.dot (p3p1_xy);
723      let c       = p3p1_xy.norm_squared() - r * r;
724      // this is the portion of the quadratic equation inside the square root that
725      // determines whether the intersection is none, a tangent point, or a segment
726      let discriminant = b * b - four * a * c;
727      if discriminant <= S::zero() {
728        None
729      } else {
730        let discriminant_sqrt = discriminant.sqrt();
731        let frac_2a           = S::one() / (two * a);
732        let t1_xy             = S::max ((-b - discriminant_sqrt) * frac_2a, S::zero());
733        let t2_xy             = S::min ((-b + discriminant_sqrt) * frac_2a, S::one());
734        if t2_xy <= S::zero() || S::one() <= t1_xy {
735          None
736        } else if let Some ((t1, t2)) = if p1.0.z == p2.0.z {
737          // segment aligned horizontally
738          Some ((t1_xy, t2_xy))
739        } else {
740          // segment not aligned horizontally;
741          // intersect the line with the top and bottom of the cylinder
742          let p1p3_z_max = p3_z_max - p1.0.z;
743          let p1p3_z_min = p3_z_min - p1.0.z;
744          let t_z_max    = S::max (S::min (p1p3_z_max / p1p2.z, S::one()), S::zero());
745          let t_z_min    = S::max (S::min (p1p3_z_min / p1p2.z, S::one()), S::zero());
746          let t1_z       = S::min (t_z_max, t_z_min);
747          let t2_z       = S::max (t_z_max, t_z_min);
748          let aabb_xy    = Interval::with_minmax (t1_xy, t2_xy);
749          let aabb_z     = Interval::with_minmax ( t1_z,  t2_z);
750          if !aabb_xy.intersects (&aabb_z) {
751            None
752          } else {
753            Some ((S::max (t1_xy, t1_z), S::min (t2_xy, t2_z)))
754          }
755        } /*then*/ {
756          debug_assert!(t1 < t2);
757          debug_assert!(t1 >= S::zero());
758          debug_assert!(t1 <  S::one());
759          debug_assert!(t2 >  S::zero());
760          debug_assert!(t2 <= S::one());
761          let first  = *p1 + p1p2 * t1;
762          let second = *p1 + p1p2 * t2;
763          Some (((t1, first), (t2, second)))
764        } else {
765          None
766        }
767      }
768    }
769  }
770}
771
772/// Compute continuous intersection of a line segment with an axis-aligned capsule.
773///
774/// The points are returned in the order given by the ordering of the points in the
775/// segment.
776///
777/// Note that a line that is tangent to the surface of the capsule returns no
778/// intersection.
779#[expect(clippy::type_complexity)]
780pub fn continuous_segment3_capsule3 <S> (
781  segment : &Segment3 <S>, capsule : &Capsule3 <S>
782) -> Option <((S, Point3 <S>), (S, Point3 <S>))> where
783  S : Real + std::fmt::Debug
784{
785  let segment_aabb = segment.aabb3();
786  let capsule_aabb = capsule.aabb3();
787  if !discrete_aabb3_aabb3 (&segment_aabb, &capsule_aabb) {
788    None
789  } else {
790    // decompose the capsule into spheres and cylinder
791    let (upper_sphere, cylinder, lower_sphere) = capsule.decompose();
792    let cylinder_result = cylinder
793      .and_then (|cylinder| segment.intersect_cylinder (&cylinder));
794    let upper_result = segment.intersect_sphere   (&upper_sphere);
795    let lower_result = segment.intersect_sphere   (&lower_sphere);
796    match (upper_result, cylinder_result, lower_result) {
797      (None, None, None) => None,
798      (one,  None, None) | (None,  one, None) | (None, None,  one) => one,
799      (Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2))), None) |
800      (Some (((t1,p1), (t2,p2))), None, Some (((u1,q1), (u2,q2)))) |
801      (None, Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2)))) => {
802        let first = if t1 < u1 {
803          (t1,p1)
804        } else {
805          (u1,q1)
806        };
807        let second = if t2 > u2 {
808          (t2,p2)
809        } else {
810          (u2,q2)
811        };
812        Some ((first, second))
813      }
814      ( Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2))), Some (((v1,r1), (v2,r2)))
815      ) => {
816        let min1 = S::min (S::min (t1, u1), v1);
817        let max2 = S::max (S::max (t2, u2), v2);
818        let first = if min1 == t1 {
819          (t1,p1)
820        } else if min1 == u1 {
821          (u1,q1)
822        } else {
823          debug_assert_eq!(min1, v1);
824          (v1,r1)
825        };
826        let second = if max2 == t2 {
827          (t2,p2)
828        } else if max2 == u2 {
829          (u2,q2)
830        } else {
831          debug_assert_eq!(max2, v2);
832          (v2,r2)
833        };
834        Some ((first, second))
835      }
836    }
837  }
838}
839
840/// Discrete intersection test of 2D spheres.
841///
842/// Spheres that are merely touching are not counted as intersecting:
843///
844/// ```
845/// # use math_utils::num_traits::One;
846/// # use math_utils::*;
847/// # use math_utils::geometry::*;
848/// # use math_utils::geometry::intersect::*;
849/// let r = Positive::one();
850/// let a = Sphere2 { center: [ 1.0, 0.0].into(), radius: r };
851/// let b = Sphere2 { center: [-1.0, 0.0].into(), radius: r };
852/// assert!(!discrete_sphere2_sphere2 (&a, &b));
853/// ```
854#[inline]
855pub fn discrete_sphere2_sphere2 <S : Ring> (a : &Sphere2 <S>, b : &Sphere2 <S>) -> bool {
856  let r_ab = *(a.radius + b.radius);
857  (b.center - a.center).self_dot() < r_ab * r_ab
858}
859
860/// Discrete intersection test of 3D spheres.
861///
862/// Spheres that are merely touching are not counted as intersecting:
863///
864/// ```
865/// # use math_utils::num_traits::One;
866/// # use math_utils::*;
867/// # use math_utils::geometry::*;
868/// # use math_utils::geometry::intersect::*;
869/// let r = Positive::one();
870/// let a = Sphere3 { center: [ 1.0, 0.0,  0.0].into(), radius: r };
871/// let b = Sphere3 { center: [-1.0, 0.0,  0.0].into(), radius: r };
872/// assert!(!discrete_sphere3_sphere3 (&a, &b));
873/// ```
874#[inline]
875pub fn discrete_sphere3_sphere3 <S : Ring> (a : &Sphere3 <S>, b : &Sphere3 <S>) -> bool {
876  let r_ab = *(a.radius + b.radius);
877  (b.center - a.center).self_dot() < r_ab * r_ab
878}
879
880#[cfg(test)]
881mod tests {
882  use super::*;
883
884  #[test]
885  fn line2_aabb2() {
886    use std::f64::consts::SQRT_2;
887    let aabb = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into());
888    let line = Line2::new ([0.0, 0.0].into(), Unit2::axis_y());
889    assert_eq!(
890      continuous_line2_aabb2 (&line, &aabb).unwrap(),
891      ((-1.0, [ 0.0, -1.0].into()), (1.0, [ 0.0, 1.0].into())));
892    let line = Line2::new ([0.0, 0.0].into(), Unit2::axis_x());
893    assert_eq!(
894      continuous_line2_aabb2 (&line, &aabb).unwrap(),
895      ((-1.0, [-1.0,  0.0].into()), (1.0, [ 1.0, 0.0].into())));
896    let line = Line2::new ([0.0, 0.0].into(), Unit2::normalize ([1.0, 1.0].into()));
897    assert_eq!(
898      continuous_line2_aabb2 (&line, &aabb).unwrap(),
899      ((-SQRT_2, [-1.0, -1.0].into()), (SQRT_2, [ 1.0,  1.0].into())));
900    let line = Line2::new ([0.0, 0.0].into(), Unit2::normalize ([-1.0, -1.0].into()));
901    assert_eq!(
902      continuous_line2_aabb2 (&line, &aabb).unwrap(),
903      ((-SQRT_2, [1.0,  1.0].into()), (SQRT_2, [-1.0, -1.0].into())));
904    let line = Line2::new ([0.0, 3.0].into(), Unit2::normalize ([-1.0, -1.0].into()));
905    assert!(continuous_line2_aabb2 (&line, &aabb).is_none());
906    let line = Line2::new ([0.0, -3.0].into(), Unit2::normalize ([1.0,  1.0].into()));
907    assert!(continuous_line2_aabb2 (&line, &aabb).is_none());
908  }
909
910  #[test]
911  fn line3_aabb3() {
912    use approx::assert_ulps_eq;
913    let aabb = Aabb3::with_minmax ([-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into());
914    let line = Line3::new ([0.0, 0.0, 0.0].into(), Unit3::axis_z());
915    assert_eq!(
916      continuous_line3_aabb3 (&line, &aabb).unwrap(),
917      ((-1.0, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 1.0].into())));
918    let line = Line3::new ([0.0, 0.0, 0.0].into(), Unit3::axis_y());
919    assert_eq!(
920      continuous_line3_aabb3 (&line, &aabb).unwrap(),
921      ((-1.0, [0.0, -1.0,  0.0].into()), (1.0, [0.0, 1.0, 0.0].into())));
922    {
923      let line = Line3::new (
924        [0.0, 0.0, 0.0].into(),
925        Unit3::normalize ([1.0, 1.0, 1.0].into()));
926      let result = continuous_line3_aabb3 (&line, &aabb).unwrap();
927      assert_ulps_eq!((result.0).0, -f64::sqrt_3());
928      assert_ulps_eq!((result.1).0, f64::sqrt_3());
929      assert_eq!((result.0).1, [-1.0, -1.0, -1.0].into());
930      assert_eq!((result.1).1, [ 1.0,  1.0,  1.0].into());
931    }
932    {
933      let line = Line3::new (
934        [0.0, 0.0, 0.0].into(),
935        Unit3::normalize ([-1.0, -1.0, -1.0].into()));
936      let result = continuous_line3_aabb3 (&line, &aabb).unwrap();
937      assert_ulps_eq!((result.0).0, -f64::sqrt_3());
938      assert_ulps_eq!((result.1).0, f64::sqrt_3());
939      assert_eq!((result.0).1, [ 1.0,  1.0,  1.0].into());
940      assert_eq!((result.1).1, [-1.0, -1.0, -1.0].into());
941    }
942    let line = Line3::new (
943      [0.0, 0.0, 3.0].into(),
944      Unit3::normalize ([-1.0, -1.0, -1.0].into()));
945    assert!(continuous_line3_aabb3 (&line, &aabb).is_none());
946    let line = Line3::new (
947      [0.0, 0.0, -3.0].into(),
948      Unit3::normalize ([1.0, 1.0, 1.0].into()));
949    assert!(continuous_line3_aabb3 (&line, &aabb).is_none());
950  }
951
952  #[test]
953  fn segment3_sphere3() {
954    let sphere  = shape::Sphere::unit().sphere3 (Point3::origin());
955    let segment = Segment3::new ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
956    assert_eq!(
957      continuous_segment3_sphere3 (&segment, &sphere).unwrap(),
958      ((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
959    let segment = Segment3::new ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
960    assert_eq!(
961      continuous_segment3_sphere3 (&segment, &sphere).unwrap(),
962      ((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
963    let segment = Segment3::new ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 2.0].into());
964    assert_eq!(
965      continuous_segment3_sphere3 (&segment, &sphere).unwrap(),
966      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 1.0].into())));
967    let segment = Segment3::new ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
968    assert_eq!(
969      continuous_segment3_sphere3 (&segment, &sphere).unwrap(),
970      ((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
971  }
972
973  #[test]
974  fn segment3_cylinder3() {
975    let cylinder = shape::Cylinder::unit().cylinder3 (Point3::origin());
976    let segment = Segment3::new ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
977    assert_eq!(
978      continuous_segment3_cylinder3 (&segment, &cylinder).unwrap(),
979      ((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
980    let segment = Segment3::new ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
981    assert_eq!(
982      continuous_segment3_cylinder3 (&segment, &cylinder).unwrap(),
983      ((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
984    let segment = Segment3::new ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 2.0].into());
985    assert_eq!(
986      continuous_segment3_cylinder3 (&segment, &cylinder).unwrap(),
987      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 1.0].into())));
988    let segment = Segment3::new ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
989    assert_eq!(
990      continuous_segment3_cylinder3 (&segment, &cylinder).unwrap(),
991      ((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
992  }
993
994  #[test]
995  fn segment3_capsule3() {
996    let capsule = shape::Capsule::noisy (1.0, 1.0).capsule3 (Point3::origin());
997    let segment = Segment3::new ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
998    assert_eq!(
999      continuous_segment3_capsule3 (&segment, &capsule).unwrap(),
1000      ((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
1001    let segment = Segment3::new ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
1002    assert_eq!(
1003      continuous_segment3_capsule3 (&segment, &capsule).unwrap(),
1004      ((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
1005    let segment = Segment3::new ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 4.0].into());
1006    assert_eq!(
1007      continuous_segment3_capsule3 (&segment, &capsule).unwrap(),
1008      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 2.0].into())));
1009    let segment = Segment3::new ([0.0, 0.0, -4.0].into(), [0.0, 0.0, 0.0].into());
1010    assert_eq!(
1011      continuous_segment3_capsule3 (&segment, &capsule).unwrap(),
1012      ((0.5, [0.0, 0.0, -2.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
1013  }
1014
1015  #[test]
1016  fn segment3_aabb3() {
1017    let aabb    = Aabb3::with_minmax ([1.0, -0.5, 0.0].into(), [2.0, 0.5, 1.0].into());
1018    let segment = Segment3::new ([-1.0, 0.0, 0.5].into(), [2.0, 0.0, 0.5].into());
1019    assert_eq!(
1020      continuous_segment3_aabb3 (&segment, &aabb).unwrap(),
1021      ((2.0/3.0, [1.0, 0.0, 0.5].into()), (1.0, [2.0, 0.0, 0.5].into())));
1022  }
1023
1024} // end tests