Skip to main content

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::with_minmax ( 0.0, 1.0).unwrap();
18/// let b = Interval::with_minmax (-1.0, 0.0).unwrap();
19/// assert!(!discrete_interval (a, b));
20/// ```
21#[inline]
22pub fn discrete_interval <S> (a : Interval <S>, b : Interval <S>) -> bool where
23  S : Copy + PartialOrd
24{
25  let (min_a, max_a) = (a.min(), a.max());
26  let (min_b, max_b) = (b.min(), b.max());
27  // intersection if intervals overlap
28  max_a > min_b && min_a < max_b
29}
30
31/// Continuous intersection test of 1D axis-aligned bounding boxes (intervals).
32///
33/// AABBs that are merely touching return no intersection:
34///
35/// ```
36/// # use math_utils::geometry::*;
37/// # use math_utils::geometry::intersect::*;
38/// let a = Interval::with_minmax ( 0.0, 1.0).unwrap();
39/// let b = Interval::with_minmax (-1.0, 0.0).unwrap();
40/// assert!(continuous_interval (a, b).is_none());
41/// ```
42#[inline]
43pub fn continuous_interval <S> (a : Interval <S>, b : Interval <S>)
44  -> Option <Interval <S>>
45where S : MinMax + Copy + PartialOrd + std::fmt::Debug {
46  if discrete_interval (a, b) {
47    Some (Interval::with_minmax_unchecked (
48      S::max (a.min(), b.min()), S::min (a.max(), b.max()))
49    )
50  } else {
51    None
52  }
53}
54
55/// Discrete intersection test of 2D axis-aligned bounding boxes.
56///
57/// AABBs that are merely touching are not counted as intersecting:
58///
59/// ```
60/// # use math_utils::geometry::*;
61/// # use math_utils::geometry::intersect::*;
62/// let a = Aabb2::with_minmax ([ 0.0, 0.0].into(), [1.0, 1.0].into()).unwrap();
63/// let b = Aabb2::with_minmax ([-1.0, 0.0].into(), [0.0, 1.0].into()).unwrap();
64/// assert!(!discrete_aabb2_aabb2 (a, b));
65/// ```
66#[inline]
67pub fn discrete_aabb2_aabb2 <S> (a : Aabb2 <S>, b : Aabb2 <S>) -> bool where
68  S : Copy + PartialOrd
69{
70  let (min_a, max_a) = (a.min(), a.max());
71  let (min_b, max_b) = (b.min(), b.max());
72  // intersection if overlap exists on both axes
73  max_a.0.x > min_b.0.x && min_a.0.x < max_b.0.x &&
74  max_a.0.y > min_b.0.y && min_a.0.y < max_b.0.y
75}
76
77/// Continuous intersection test of 2D axis-aligned bounding boxes.
78///
79/// AABBs that are merely touching return no intersection:
80///
81/// ```
82/// # use math_utils::geometry::*;
83/// # use math_utils::geometry::intersect::*;
84/// let a = Aabb2::with_minmax ([ 0.0, 0.0].into(), [1.0, 1.0].into()).unwrap();
85/// let b = Aabb2::with_minmax ([-1.0, 0.0].into(), [0.0, 1.0].into()).unwrap();
86/// assert!(continuous_aabb2_aabb2 (a, b).is_none());
87/// ```
88#[inline]
89pub fn continuous_aabb2_aabb2 <S> (a : Aabb2 <S>, b : Aabb2 <S>) -> Option <Aabb2 <S>>
90  where S : Copy + PartialOrd + std::fmt::Debug
91{
92  if discrete_aabb2_aabb2 (a, b) {
93    Some (Aabb2::with_minmax_unchecked (
94      point2_max (a.min(), b.min()), point2_min (a.max(), b.max()))
95    )
96  } else {
97    None
98  }
99}
100
101/// Discrete intersection test of 3D axis-aligned bounding boxes.
102///
103/// AABBs that are merely touching are not counted as intersecting:
104///
105/// ```
106/// # use math_utils::geometry::*;
107/// # use math_utils::geometry::intersect::*;
108/// let a = Aabb3::with_minmax ([ 0.0, 0.0, 0.0].into(), [1.0, 1.0, 1.0].into())
109///   .unwrap();
110/// let b = Aabb3::with_minmax ([-1.0, 0.0, 0.0].into(), [0.0, 1.0, 1.0].into())
111///   .unwrap();
112/// assert!(!discrete_aabb3_aabb3 (a, b));
113/// ```
114#[inline]
115pub fn discrete_aabb3_aabb3 <S> (a : Aabb3 <S>, b : Aabb3 <S>) -> bool where
116  S : Copy + PartialOrd
117{
118  let (min_a, max_a) = (a.min(), a.max());
119  let (min_b, max_b) = (b.min(), b.max());
120  // intersection if overlap exists on all three axes
121  max_a.0.x > min_b.0.x && min_a.0.x < max_b.0.x &&
122  max_a.0.y > min_b.0.y && min_a.0.y < max_b.0.y &&
123  max_a.0.z > min_b.0.z && min_a.0.z < max_b.0.z
124}
125
126/// Continuous intersection test of 3D axis-aligned bounding boxes.
127///
128/// AABBs that are merely touching return no intersection:
129///
130/// ```
131/// # use math_utils::geometry::*;
132/// # use math_utils::geometry::intersect::*;
133/// let a = Aabb3::with_minmax ([ 0.0, 0.0, 0.0].into(), [1.0, 1.0, 1.0].into())
134///   .unwrap();
135/// let b = Aabb3::with_minmax ([-1.0, 0.0, 0.0].into(), [0.0, 1.0, 1.0].into())
136///   .unwrap();
137/// assert!(continuous_aabb3_aabb3 (a, b).is_none());
138/// ```
139#[inline]
140pub fn continuous_aabb3_aabb3 <S> (a : Aabb3 <S>, b : Aabb3 <S>) -> Option <Aabb3 <S>>
141  where S : Copy + PartialOrd + std::fmt::Debug
142{
143  if discrete_aabb3_aabb3 (a, b) {
144    Some (
145      Aabb3::with_minmax_unchecked (
146        point3_max (a.min(), b.min()), point3_min (a.max(), b.max()))
147    )
148  } else {
149    None
150  }
151}
152
153/// Compute the intersection of a 2D line with a 2D AABB (rectangle).
154///
155/// If the line and AABB intersect, the two intersection points are returned with the
156/// scalar parameter corresponding to that point along the line.
157///
158/// ```
159/// # use math_utils::*;
160/// # use math_utils::geometry::*;
161/// # use math_utils::geometry::intersect::*;
162/// let aabb = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into()).unwrap();
163/// let line = Line2::new  ([0.0, 0.0].into(), Unit2::axis_x());
164/// assert_eq!(
165///   continuous_line2_aabb2 (line, aabb).unwrap(),
166///   ( (-1.0, [-1.0, 0.0].into()),
167///     ( 1.0, [ 1.0, 0.0].into())
168///   )
169/// );
170/// ```
171///
172/// Returns `None` if the line and AABB are tangent:
173///
174/// ```
175/// # use math_utils::*;
176/// # use math_utils::geometry::*;
177/// # use math_utils::geometry::intersect::*;
178/// let aabb = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into()).unwrap();
179/// let line = Line2::new  ([0.0, 1.0].into(), Unit2::axis_x());
180/// assert_eq!(continuous_line2_aabb2 (line, aabb), None);
181/// ```
182pub fn continuous_line2_aabb2 <S> (line : Line2 <S>, aabb : Aabb2 <S>)
183  -> Option <(Line2Point <S>, Line2Point <S>)>
184where S : OrderedRing + std::fmt::Debug {
185  let aabb_min = aabb.min();
186  let aabb_max = aabb.max();
187  if line.direction.x == S::zero() {
188    // parallel x-axis
189    if aabb_min.0.x < line.base.0.x && line.base.0.x < aabb_max.0.x {
190      let out = if line.direction.y > S::zero() {
191        let (t0, t1) = (aabb_min.0.y - line.base.0.y, aabb_max.0.y - line.base.0.y);
192        ( (t0, [line.base.0.x, aabb_min.0.y].into()),
193          (t1, [line.base.0.x, aabb_max.0.y].into())
194        )
195      } else {
196        let (t0, t1) = (line.base.0.y - aabb_max.0.y, line.base.0.y - aabb_min.0.y);
197        ( (t0, [line.base.0.x, aabb_max.0.y].into()),
198          (t1, [line.base.0.x, aabb_min.0.y].into())
199        )
200      };
201      Some (out)
202    } else {
203      None
204    }
205  } else if line.direction.y == S::zero() {
206    // parallel y-axis
207    if aabb_min.0.y < line.base.0.y && line.base.0.y < aabb_max.0.y {
208      let out = if line.direction.x > S::zero() {
209        let (t0, t1) = (aabb_min.0.x - line.base.0.x, aabb_max.0.x - line.base.0.x);
210        ( (t0, [aabb_min.0.x, line.base.0.y].into()),
211          (t1, [aabb_max.0.x, line.base.0.y].into())
212        )
213      } else {
214        let (t0, t1) = (line.base.0.x - aabb_max.0.x, line.base.0.x - aabb_min.0.x);
215        ( (t0, [aabb_max.0.x, line.base.0.y].into()),
216          (t1, [aabb_min.0.x, line.base.0.y].into())
217        )
218      };
219      Some (out)
220    } else {
221      None
222    }
223  } else {
224    let dir_reciprocal = line.direction.map (|s| S::one() / s);
225    let (t0_x, t1_x)   = {
226      let (near_x, far_x) = if line.direction.x.is_positive() {
227        (aabb_min.0.x, aabb_max.0.x)
228      } else {
229        (aabb_max.0.x, aabb_min.0.x)
230      };
231      ( (near_x - line.base.0.x) * dir_reciprocal.x,
232        (far_x  - line.base.0.x) * dir_reciprocal.x
233      )
234    };
235    let (t0_y, t1_y) = {
236      let (near_y, far_y) = if line.direction.y.is_positive() {
237        (aabb_min.0.y, aabb_max.0.y)
238      } else {
239        (aabb_max.0.y, aabb_min.0.y)
240      };
241      ( (near_y - line.base.0.y) * dir_reciprocal.y,
242        (far_y  - line.base.0.y) * dir_reciprocal.y
243      )
244    };
245    let interval_x = Interval::with_minmax_unchecked (t0_x, t1_x);
246    let interval_y = Interval::with_minmax_unchecked (t0_y, t1_y);
247    continuous_interval (interval_x, interval_y).map (|interval|{
248      let start = line.point (interval.min());
249      let end   = line.point (interval.max());
250      ( (interval.min(), start), (interval.max(), end) )
251    })
252  }
253}
254
255pub fn continuous_ray2_aabb2 <S> (_ray : Ray2 <S>, _aabb : Aabb2 <S>)
256  -> Option <(Ray2Point <S>, Ray2Point <S>)>
257{
258  unimplemented!("TODO: ray2 aabb2 intersect")
259}
260
261/// Compute the intersection of a 2D line with a 2D sphere (circle).
262///
263/// If the line and circle intersect, the two intersection points are returned with the
264/// scalar parameter corresponding to that point along the line.
265///
266/// ```
267/// # use math_utils::*;
268/// # use math_utils::geometry::*;
269/// # use math_utils::geometry::intersect::*;
270/// let sphere = Sphere2::unit();
271/// let line   = Line2::new  ([0.0, 0.0].into(), Unit2::axis_x());
272/// assert_eq!(
273///   continuous_line2_sphere2 (line, sphere).unwrap(),
274///   ( (-1.0, [-1.0, 0.0].into()),
275///     ( 1.0, [ 1.0, 0.0].into())
276///   )
277/// );
278/// ```
279///
280/// Returns `None` if the line and circle are tangent:
281///
282/// ```
283/// # use math_utils::*;
284/// # use math_utils::geometry::*;
285/// # use math_utils::geometry::intersect::*;
286/// let sphere = Sphere2::unit();
287/// let line   = Line2::new  ([0.0, 1.0].into(), Unit2::axis_x());
288/// assert_eq!(continuous_line2_sphere2 (line, sphere), None);
289/// ```
290pub fn continuous_line2_sphere2 <S> (line : Line2 <S>, sphere : Sphere2 <S>)
291  -> Option <(Line2Point <S>, Line2Point <S>)>
292where S : OrderedField + Sqrt {
293  // intersect the line with the cylinder circle in the XY plane
294  let two  = S::two();
295  let four = S::four();
296  // defining intersection points by the equation at^2 + bt + c = 0, solve for t using
297  // quadratic formula:
298  //
299  //  t = (-b +- sqrt (b^2 -4ac)) / 2a
300  //
301  // where a, b, and c are defined in terms of points p1, p2 of the line segment and the
302  // sphere center p3, and sphere radius r
303  let p1   = line.base;
304  #[expect(clippy::no_effect_underscore_binding)]
305  let _p2   = line.base + *line.direction;
306  let p3   = sphere.center;
307  let r    = *sphere.radius;
308  let p1p2 = line.direction;
309  let p3p1 = p1 - p3;
310  let a    = S::one();
311  let b    = two * p1p2.dot (p3p1);
312  let c    = *p3p1.norm_squared() - r * r;
313  // this is the portion of the quadratic equation inside the square root that
314  // determines whether the intersection is none, a tangent point, or a segment
315  let discriminant = b * b - four * a * c;
316  if discriminant <= S::zero() {
317    None
318  } else {
319    let discriminant_sqrt = discriminant.sqrt();
320    let frac_2a           = S::one() / (two * a);
321    let t1                = (-b - discriminant_sqrt) * frac_2a;
322    let t2                = (-b + discriminant_sqrt) * frac_2a;
323    let first  = p1 + (*p1p2) * t1;
324    let second = p1 + (*p1p2) * t2;
325    Some (((t1, first), (t2, second)))
326  }
327}
328
329pub fn continuous_ray2_sphere2 <S> (_ray : Ray2 <S>, _sphere : Sphere2 <S>)
330  -> Option <(Ray2Point <S>, Ray2Point <S>)>
331{
332  unimplemented!("TODO: ray2 sphere2 intersect")
333}
334
335/// Compute the continuous intersection of a 3D line with a 3D plane.
336///
337/// Returns `None` if the line and plane are parallel:
338///
339/// ```
340/// # use math_utils::*;
341/// # use math_utils::geometry::*;
342/// # use math_utils::geometry::intersect::*;
343/// let plane = Plane3::new ([0.0, 0.0,  0.0].into(), Unit3::axis_z());
344/// let line  = Line3::new  (
345///   [0.0, 0.0, 0.0].into(),
346///   Unit3::normalize ([1.0, 1.0, 0.0].into()));
347/// assert_eq!(continuous_line3_plane3 (line, plane), None);
348/// ```
349pub fn continuous_line3_plane3 <S> (line : Line3 <S>, plane : Plane3 <S>)
350  -> Option <Line3Point <S>>
351where S : OrderedField + approx::RelativeEq {
352  let normal_dot_direction = plane.normal.dot (*line.direction);
353  if approx::relative_eq!(normal_dot_direction, S::zero()) {
354    None
355  } else {
356    let plane_to_line = line.base - plane.base;
357    let t     = -plane.normal.dot (plane_to_line) / normal_dot_direction;
358    let point = line.point (t);
359    Some ((t, point))
360  }
361}
362
363pub fn continuous_ray3_plane3 <S> (_ray : Ray3 <S>, _plane : Plane3 <S>)
364  -> Option <Ray3Point <S>>
365{
366  unimplemented!("TODO: ray3 plane3 intersect")
367}
368
369/// Compute continuous intersection of a line with a triangle
370pub fn continuous_line3_triangle3 <S> (line : Line3 <S>, triangle : Triangle3 <S>)
371  -> Option <Line3Point <S>>
372where
373  S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S>
374{
375  line_triangle (line.affine_line(), triangle)
376}
377
378/// Compute continuous intersection of a ray with a triangle
379pub fn continuous_ray3_triangle3 <S> (ray : Ray3 <S>, triangle : Triangle3 <S>)
380  -> Option <Ray3Point <S>>
381where
382  S : Real + num::real::Real + approx::AbsDiffEq <Epsilon = S>
383{
384  line_triangle (ray.affine_line(), triangle)
385    .and_then (|(s, p)| NonNegative::new (s).map (|s| (s, p)))
386}
387
388/// Compute the intersection of a 3D line with a 3D AABB.
389///
390/// If the line and AABB intersect, the two intersection points are returned with the
391/// scalar parameter corresponding to that point along the line.
392///
393/// ```
394/// # use math_utils::*;
395/// # use math_utils::geometry::*;
396/// # use math_utils::geometry::intersect::*;
397/// let aabb = Aabb3::with_minmax ([-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into())
398///   .unwrap();
399/// let line = Line3::new  ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
400/// assert_eq!(
401///   continuous_line3_aabb3 (line, aabb).unwrap(),
402///   ( (-1.0, [-1.0, 0.0, 0.0].into()),
403///     ( 1.0, [ 1.0, 0.0, 0.0].into())
404///   )
405/// );
406/// ```
407///
408/// Returns `None` if the line and AABB are tangent:
409///
410/// ```
411/// # use math_utils::*;
412/// # use math_utils::geometry::*;
413/// # use math_utils::geometry::intersect::*;
414/// let aabb = Aabb3::with_minmax ([-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into())
415///   .unwrap();
416/// let line = Line3::new  ([0.0, 1.0, 0.0].into(), Unit3::axis_x());
417/// assert_eq!(continuous_line3_aabb3 (line, aabb), None);
418/// ```
419pub fn continuous_line3_aabb3 <S> (line : Line3 <S>, aabb : Aabb3 <S>)
420  -> Option <(Line3Point <S>, Line3Point <S>)>
421where S : OrderedRing + num::real::Real + approx::RelativeEq <Epsilon=S> {
422  let aabb_min = aabb.min();
423  let aabb_max = aabb.max();
424  if line.direction.x == S::zero() {
425    if aabb_min.0.x < line.base.0.x && line.base.0.x < aabb_max.0.x {
426      let line2 = Line2::new (
427        [line.base.0.y, line.base.0.z].into(),
428        Unit2::unchecked_approx ([line.direction.y, line.direction.z].into()));
429      let aabb2 = Aabb2::with_minmax_unchecked (
430        [aabb_min.0.y, aabb_min.0.z].into(),
431        [aabb_max.0.y, aabb_max.0.z].into());
432      continuous_line2_aabb2 (line2, aabb2).map (|((t0, p0), (t1, p1))|
433        ( (t0, [line.base.0.x, p0.0.x, p0.0.y].into()),
434          (t1, [line.base.0.x, p1.0.x, p1.0.y].into())
435        )
436      )
437    } else {
438      None
439    }
440  } else if line.direction.y == S::zero() {
441    if aabb_min.0.y < line.base.0.y && line.base.0.y < aabb_max.0.y {
442      let line2 = Line2::new (
443        [line.base.0.x, line.base.0.z].into(),
444        Unit2::unchecked_approx ([line.direction.x, line.direction.z].into()));
445      let aabb2 = Aabb2::with_minmax_unchecked (
446        [aabb_min.0.x, aabb_min.0.z].into(),
447        [aabb_max.0.x, aabb_max.0.z].into());
448      continuous_line2_aabb2 (line2, aabb2).map (|((t0, p0), (t1, p1))|
449        ( (t0, [p0.0.x, line.base.0.y, p0.0.y].into()),
450          (t1, [p1.0.x, line.base.0.y, p1.0.y].into())
451        )
452      )
453    } else {
454      None
455    }
456  } else if line.direction.z == S::zero() {
457    if aabb_min.0.z < line.base.0.z && line.base.0.z < aabb_max.0.z {
458      let line2 = Line2::new (
459        [line.base.0.x, line.base.0.y].into(),
460        Unit2::unchecked_approx ([line.direction.x, line.direction.y].into()));
461      let aabb2 = Aabb2::with_minmax_unchecked (
462        [aabb_min.0.x, aabb_min.0.y].into(),
463        [aabb_max.0.x, aabb_max.0.y].into());
464      continuous_line2_aabb2 (line2, aabb2).map (|((t0, p0), (t1, p1))|
465        ( (t0, [p0.0.x, p0.0.y, line.base.0.z].into()),
466          (t1, [p1.0.x, p1.0.y, line.base.0.z].into())
467        )
468      )
469    } else {
470      None
471    }
472  } else {
473    let dir_reciprocal = line.direction.map (|s| S::one() / s);
474    let (t0_x, t1_x)   = {
475      let (near_x, far_x) = if line.direction.x.is_positive() {
476        (aabb_min.0.x, aabb_max.0.x)
477      } else {
478        (aabb_max.0.x, aabb_min.0.x)
479      };
480      ( (near_x - line.base.0.x) * dir_reciprocal.x,
481        (far_x  - line.base.0.x) * dir_reciprocal.x
482      )
483    };
484    let (t0_y, t1_y) = {
485      let (near_y, far_y) = if line.direction.y.is_positive() {
486        (aabb_min.0.y, aabb_max.0.y)
487      } else {
488        (aabb_max.0.y, aabb_min.0.y)
489      };
490      ( (near_y - line.base.0.y) * dir_reciprocal.y,
491        (far_y  - line.base.0.y) * dir_reciprocal.y
492      )
493    };
494    let (t0_z, t1_z) = {
495      let (near_z, far_z) = if line.direction.z.is_positive() {
496        (aabb_min.0.z, aabb_max.0.z)
497      } else {
498        (aabb_max.0.z, aabb_min.0.z)
499      };
500      ( (near_z - line.base.0.z) * dir_reciprocal.z,
501        (far_z  - line.base.0.z) * dir_reciprocal.z
502      )
503    };
504    let interval_x = Interval::with_minmax_unchecked (t0_x, t1_x);
505    let interval_y = Interval::with_minmax_unchecked (t0_y, t1_y);
506    continuous_interval (interval_x, interval_y).and_then (|interval| {
507      let interval_z = Interval::with_minmax_unchecked (t0_z, t1_z);
508      continuous_interval (interval, interval_z).map (|interval|{
509        let start = line.point (interval.min());
510        let end   = line.point (interval.max());
511        ( (interval.min(), start), (interval.max(), end) )
512      })
513    })
514  }
515}
516
517pub fn continuous_ray3_aabb3 <S> (_ray : Ray3 <S>, _aabb : Aabb3 <S>)
518  -> Option <(Ray3Point <S>, Ray3Point <S>)>
519{
520  unimplemented!("TODO: ray3 aabb3 intersect")
521}
522
523/// Compute the continuous intersection of a 3D line with a 3D sphere.
524///
525/// If the line and sphere intersect, the two intersection points are returned with the
526/// scalar parameter corresponding to that point along the line.
527///
528/// ```
529/// # use math_utils::*;
530/// # use math_utils::geometry::*;
531/// # use math_utils::geometry::intersect::*;
532/// let sphere = Sphere3::unit();
533/// let line = Line3::new  ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
534/// assert_eq!(
535///   continuous_line3_sphere3 (line, sphere).unwrap(),
536///   ( (-1.0, [-1.0, 0.0, 0.0].into()),
537///     ( 1.0, [ 1.0, 0.0, 0.0].into())
538///   )
539/// );
540/// ```
541///
542/// Returns `None` if the line and sphere are tangent:
543///
544/// ```
545/// # use math_utils::*;
546/// # use math_utils::geometry::*;
547/// # use math_utils::geometry::intersect::*;
548/// let sphere = Sphere3::unit();
549/// let line   = Line3::new  ([0.0, 0.0, 1.0].into(), Unit3::axis_x());
550/// assert_eq!(continuous_line3_sphere3 (line, sphere), None);
551/// ```
552pub fn continuous_line3_sphere3 <S> (line : Line3 <S>, sphere : Sphere3 <S>)
553  -> Option <(Line3Point <S>, Line3Point <S>)>
554where S : OrderedField + Sqrt {
555  let two  = S::two();
556  let four = S::four();
557  // defining intersection points by the equation at^2 + bt + c = 0, solve for t using
558  // quadratic formula:
559  //
560  //  t = (-b +- sqrt (b^2 -4ac)) / 2a
561  //
562  // where a, b, and c are defined in terms of points p1, p2 of the line segment and the
563  // sphere center p3, and sphere radius r
564  let p1   = line.base;
565  #[expect(clippy::no_effect_underscore_binding)]
566  let _p2   = line.base + *line.direction;
567  let p3   = sphere.center;
568  let r    = *sphere.radius;
569  let p1p2 = &line.direction;
570  let p3p1 = p1 - p3;
571  let a    = S::one();
572  let b    = two * p1p2.dot (p3p1);
573  let c    = *p3p1.norm_squared() - r * r;
574  // this is the portion of the quadratic equation inside the square root that
575  // determines whether the intersection is none, a tangent point, or a segment
576  let discriminant = b * b - four * a * c;
577  if discriminant <= S::zero() {
578    None
579  } else {
580    let discriminant_sqrt = discriminant.sqrt();
581    let frac_2a           = S::one() / (two * a);
582    let t1                = (-b - discriminant_sqrt) * frac_2a;
583    let t2                = (-b + discriminant_sqrt) * frac_2a;
584    let first  = p1 + (**p1p2) * t1;
585    let second = p1 + (**p1p2) * t2;
586    Some (((t1, first), (t2, second)))
587  }
588}
589
590pub fn continuous_ray3_sphere3 <S> (_ray : Ray3 <S>, _sphere : Sphere3 <S>)
591  -> Option <(Ray3Point <S>, Ray3Point <S>)>
592{
593  unimplemented!("TODO: ray3 sphere3 intersect")
594}
595
596/// Compute continuous intersection of a 2D line segment with a 2D AABB (rectangle).
597///
598/// The points are returned in the order given by the ordering of the points in the
599/// segment.
600///
601/// Note that a segment that is tangent to the AABB returns no intersection.
602pub fn continuous_segment2_aabb2 <S> (segment : Segment2 <S>, aabb : Aabb2 <S>)
603  -> Option <(Segment2Point <S>, Segment2Point <S>)>
604where S : Real {
605  let vector = segment.point_b() - segment.point_a();
606  let length = *vector.norm();
607  let line   = Line2::new (segment.point_a(), Unit2::unchecked (vector / length));
608  if let Some (((t0, _p0), (t1, _p1))) = continuous_line2_aabb2 (line, aabb) {
609    let interval = Interval::with_minmax_unchecked (S::zero(), length);
610    continuous_interval (interval, Interval::with_minmax_unchecked (t0, t1)).map (
611      |interval|
612      ( (Normalized::unchecked (interval.min() / length), line.point (interval.min())),
613        (Normalized::unchecked (interval.max() / length), line.point (interval.max()))
614      )
615    )
616  } else {
617    None
618  }
619}
620
621/// Compute continuous intersection of a 2D line segment with a 2D sphere (circle).
622///
623/// The points are returned in the order given by the ordering of the points in the
624/// segment.
625///
626/// Note that a segment that is tangent to the surface of the circle returns no
627/// intersection.
628pub fn continuous_segment2_sphere2 <S> (segment : Segment2 <S>, sphere : Sphere2 <S>)
629  -> Option <(Segment2Point <S>, Segment2Point <S>)>
630where S : Real {
631  let vector = segment.point_b() - segment.point_a();
632  let length = *vector.norm();
633  let line   = Line2::new (segment.point_a(), Unit2::unchecked (vector / length));
634  if let Some (((t0, _p0), (t1, _p1))) = continuous_line2_sphere2 (line, sphere) {
635    let interval = Interval::with_minmax_unchecked (S::zero(), length);
636    continuous_interval (interval, Interval::with_minmax_unchecked (t0, t1))
637      .map (|interval|
638        ( (Normalized::unchecked (interval.min() / length), line.point (interval.min())),
639          (Normalized::unchecked (interval.max() / length), line.point (interval.max()))
640        )
641      )
642  } else {
643    None
644  }
645}
646
647/// Compute continuous intersection of a 3D line segment with an AABB.
648///
649/// The points are returned in the order given by the ordering of the points in the
650/// segment.
651///
652/// Note that a segment that is tangent to the AABB returns no intersection.
653pub fn continuous_segment3_aabb3 <S> (segment : Segment3 <S>, aabb : Aabb3 <S>)
654  -> Option <(Segment3Point <S>, Segment3Point <S>)>
655where S : Real + num::Float + approx::RelativeEq <Epsilon=S> {
656  let vector = segment.point_b() - segment.point_a();
657  let length = *vector.norm();
658  let line = Line3::new (segment.point_a(), Unit3::unchecked_approx (vector / length));
659  if let Some (((t0, _p0), (t1, _p1))) = continuous_line3_aabb3 (line, aabb) {
660    let interval = Interval::with_minmax_unchecked (S::zero(), length);
661    continuous_interval (interval, Interval::with_minmax_unchecked (t0, t1))
662      .map (|interval|
663        ( (Normalized::unchecked (interval.min() / length), line.point (interval.min())),
664          (Normalized::unchecked (interval.max() / length), line.point (interval.max()))
665        )
666      )
667  } else {
668    None
669  }
670}
671
672/// Compute continuous intersection of a 3D line segment with a sphere.
673///
674/// The points are returned in the order given by the ordering of the points in the
675/// segment.
676///
677/// Note that a segment that is tangent to the surface of the sphere returns no
678/// intersection.
679pub fn continuous_segment3_sphere3 <S> (segment : Segment3 <S>, sphere : Sphere3 <S>)
680  -> Option <(Segment3Point <S>, Segment3Point <S>)>
681where S : OrderedField + Sqrt {
682  let two  = S::two();
683  let four = S::four();
684  // defining intersection points by the equation at^2 + bt + c = 0, solve for t using
685  // quadratic formula:
686  //
687  //  t = (-b +- sqrt (b^2 -4ac)) / 2a
688  //
689  // where a, b, and c are defined in terms of points p1, p2 of the line segment and the
690  // sphere center p3, and sphere radius r
691  let p1   = segment.point_a();
692  let p2   = segment.point_b();
693  let p3   = sphere.center;
694  let r    = *sphere.radius;
695  let p1p2 = p2 - p1;
696  let p3p1 = p1 - p3;
697  let a    = *p1p2.norm_squared();
698  let b    = two * p1p2.dot (p3p1);
699  let c    = *p3p1.norm_squared() - r * r;
700  // this is the portion of the quadratic equation inside the square root that
701  // determines whether the intersection is none, a tangent point, or a segment
702  let discriminant = b * b - four * a * c;
703  if discriminant <= S::zero() {
704    None
705  } else {
706    let discriminant_sqrt = discriminant.sqrt();
707    let frac_2a           = S::one() / (two * a);
708    let t1                = S::max ((-b - discriminant_sqrt) * frac_2a, S::zero());
709    let t2                = S::min ((-b + discriminant_sqrt) * frac_2a, S::one());
710    if t2 <= S::zero() || S::one() <= t1 {
711      None
712    } else {
713      let first  = p1 + p1p2 * t1;
714      let second = p1 + p1p2 * t2;
715      Some ((
716        (Normalized::unchecked (t1), first),
717        (Normalized::unchecked (t2), second)
718      ))
719    }
720  }
721}
722
723/// Compute continuous intersection of a 3D line segment with an axis-aligned cylinder.
724///
725/// The points are returned in the order given by the ordering of the points in the
726/// segment.
727///
728/// Note that a segment that is tangent to the surface of the cylinder returns no
729/// intersection.
730pub fn continuous_segment3_cylinder3 <S : Real> (
731  segment : Segment3 <S>, cylinder : Cylinder3 <S>
732) -> Option <(Segment3Point <S>, Segment3Point <S>)> {
733  let segment_aabb  = segment.aabb3();
734  let cylinder_aabb = cylinder.aabb3();
735  if !discrete_aabb3_aabb3 (segment_aabb, cylinder_aabb) {
736    None
737  } else {
738    let p1       = segment.point_a();
739    let p2       = segment.point_b();
740    let p3       = cylinder.center;
741    let r        = cylinder.radius;
742    let r2       = r * r;
743    let p1p2     = p2 - p1;
744    let p1_xy    = Point2::from ([p1.0.x, p1.0.y]);
745    let p2_xy    = Point2::from ([p2.0.x, p2.0.y]);
746    let p3_xy    = Point2::from ([p3.0.x, p3.0.y]);
747    let p3_z_max = cylinder_aabb.max().0.z;
748    let p3_z_min = cylinder_aabb.min().0.z;
749    if p1_xy == p2_xy {   // segment is aligned vertically (Z axis)
750      let d2 = (p1_xy - p3_xy).norm_squared();
751      if *d2 >= *r2 {
752        None
753      } else {
754        let (t1, begin_z) = if p1.0.z >= p3_z_max {
755          ((p3_z_max - p1.0.z) / p1p2.z, p3_z_max)
756        } else if p1.0.z <= p3_z_min {
757          ((p3_z_min - p1.0.z) / p1p2.z, p3_z_min)
758        } else {
759          (S::zero(), p1.0.z)
760        };
761        let (t2, end_z)   = if p2.0.z >= p3_z_max {
762          ((p3_z_max - p1.0.z) / p1p2.z, p3_z_max)
763        } else if p2.0.z <= p3_z_min {
764          ((p3_z_min - p1.0.z) / p1p2.z, p3_z_min)
765        } else {
766          (S::one(), p2.0.z)
767        };
768        let begin = [p1_xy.0.x, p1_xy.0.y, begin_z].into();
769        let end   = [p1_xy.0.x, p1_xy.0.y, end_z  ].into();
770        Some ((
771          (Normalized::unchecked (t1), begin),
772          (Normalized::unchecked (t2), end)
773        ))
774      }
775    } else {    // segment is not aligned vertically
776      // intersect the line with the cylinder circle in the XY plane
777      let two     = S::two();
778      let four    = S::four();
779      let p1p2_xy = p1p2.xy();
780      let p3p1_xy = p1_xy - p3_xy;
781      let a       = p1p2_xy.norm_squared();
782      let b       = two * p1p2_xy.dot (p3p1_xy);
783      let c       = *p3p1_xy.norm_squared() - *r2;
784      // this is the portion of the quadratic equation inside the square root that
785      // determines whether the intersection is none, a tangent point, or a segment
786      let discriminant = b * b - four * *a * c;
787      if discriminant <= S::zero() {
788        None
789      } else {
790        let discriminant_sqrt = discriminant.sqrt();
791        let frac_2a           = S::one() / (two * *a);
792        let t1_xy             = S::max ((-b - discriminant_sqrt) * frac_2a, S::zero());
793        let t2_xy             = S::min ((-b + discriminant_sqrt) * frac_2a, S::one());
794        if t2_xy <= S::zero() || S::one() <= t1_xy {
795          None
796        } else if let Some ((t1, t2)) = if p1.0.z == p2.0.z {
797          // segment aligned horizontally
798          Some ((t1_xy, t2_xy))
799        } else {
800          // segment not aligned horizontally;
801          // intersect the line with the top and bottom of the cylinder
802          let p1p3_z_max = p3_z_max - p1.0.z;
803          let p1p3_z_min = p3_z_min - p1.0.z;
804          let t_z_max    = S::max (S::min (p1p3_z_max / p1p2.z, S::one()), S::zero());
805          let t_z_min    = S::max (S::min (p1p3_z_min / p1p2.z, S::one()), S::zero());
806          let t1_z       = S::min (t_z_max, t_z_min);
807          let t2_z       = S::max (t_z_max, t_z_min);
808          let aabb_xy    = Interval::with_minmax_unchecked (t1_xy, t2_xy);
809          let aabb_z     = Interval::with_minmax_unchecked (t1_z,  t2_z);
810          if !aabb_xy.intersects (aabb_z) {
811            None
812          } else {
813            Some ((S::max (t1_xy, t1_z), S::min (t2_xy, t2_z)))
814          }
815        } /*then*/ {
816          debug_assert!(t1 < t2);
817          debug_assert!(t1 >= S::zero());
818          debug_assert!(t1 <  S::one());
819          debug_assert!(t2 >  S::zero());
820          debug_assert!(t2 <= S::one());
821          let first  = p1 + p1p2 * t1;
822          let second = p1 + p1p2 * t2;
823          Some ((
824            (Normalized::unchecked (t1), first),
825            (Normalized::unchecked (t2), second)
826          ))
827        } else {
828          None
829        }
830      }
831    }
832  }
833}
834
835/// Compute continuous intersection of a line segment with an axis-aligned capsule.
836///
837/// The points are returned in the order given by the ordering of the points in the
838/// segment.
839///
840/// Note that a line that is tangent to the surface of the capsule returns no
841/// intersection.
842pub fn continuous_segment3_capsule3 <S : Real> (
843  segment : Segment3 <S>, capsule : Capsule3 <S>
844) -> Option <(Segment3Point <S>, Segment3Point <S>)> {
845  let segment_aabb = segment.aabb3();
846  let capsule_aabb = capsule.aabb3();
847  if !discrete_aabb3_aabb3 (segment_aabb, capsule_aabb) {
848    None
849  } else {
850    // decompose the capsule into spheres and cylinder
851    let (upper_sphere, cylinder, lower_sphere) = capsule.decompose();
852    let cylinder_result = cylinder
853      .and_then (|cylinder| segment.intersect_cylinder (cylinder));
854    let upper_result = segment.intersect_sphere (upper_sphere);
855    let lower_result = segment.intersect_sphere (lower_sphere);
856    match (upper_result, cylinder_result, lower_result) {
857      (None, None, None) => None,
858      (one,  None, None) | (None,  one, None) | (None, None,  one) => one,
859      (Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2))), None) |
860      (Some (((t1,p1), (t2,p2))), None, Some (((u1,q1), (u2,q2)))) |
861      (None, Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2)))) => {
862        let first = if t1 < u1 {
863          (t1,p1)
864        } else {
865          (u1,q1)
866        };
867        let second = if t2 > u2 {
868          (t2,p2)
869        } else {
870          (u2,q2)
871        };
872        Some ((first, second))
873      }
874      ( Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2))), Some (((v1,r1), (v2,r2)))
875      ) => {
876        let min1 = Normalized::min (Normalized::min (t1, u1), v1);
877        let max2 = Normalized::max (Normalized::max (t2, u2), v2);
878        let first = if min1 == t1 {
879          (t1,p1)
880        } else if min1 == u1 {
881          (u1,q1)
882        } else {
883          debug_assert_eq!(min1, v1);
884          (v1,r1)
885        };
886        let second = if max2 == t2 {
887          (t2,p2)
888        } else if max2 == u2 {
889          (u2,q2)
890        } else {
891          debug_assert_eq!(max2, v2);
892          (v2,r2)
893        };
894        Some ((first, second))
895      }
896    }
897  }
898}
899
900/// Compute continuous intersection of a line segment with a convex hull.
901///
902/// The points are returned in the order given by the ordering of the points in the
903/// segment.
904///
905/// Note that a line that is tangent to the surface of the convex hull returns no
906/// intersection.
907pub fn continuous_segment3_hull3 <S> (
908  segment : Segment3 <S>, hull : &Hull3 <S>, mesh : &mesh::VertexEdgeTriangleMesh
909) -> Option <(Segment3Point <S>, Segment3Point <S>)> where
910  S : OrderedField + Sqrt + approx::RelativeEq <Epsilon = S>
911{
912  use num::One;
913  let line = segment.affine_line();
914  let mut start = None;
915  let mut end   = None;
916  for triangle in mesh.triangles().values() {
917    let face = hull.triangle (triangle);
918    if let Some ((t, p)) = line_triangle (line, face) {
919      if let Some ((s, q)) = start {
920        if t < s {
921          end   = Some ((s, q));
922          start = Some ((t, p));
923        } else {
924          if let Some ((s, _q)) = end {
925            if t > s {
926              end = Some ((t, p));
927            }
928          } else {
929            end = Some ((t, p));
930          }
931        }
932        // if the line intersects an edge it may intersect another triangle, but the
933        // distance should be the same
934        if t != s {
935          break
936        }
937      } else {
938        start = Some ((t, p));
939      }
940    }
941  }
942  match (start, end) {
943    (Some ((t, p)), Some ((s, q))) => {
944      debug_assert!(t < s, "t: {t:?}, s: {s:?}");
945      if t < S::zero() && s > S::one() {
946        Some ((
947          (Normalized::zero(), segment.point_a()),
948          (Normalized::one(), segment.point_b())
949        ))
950      } else if t >= S::one() {
951        debug_assert!(s > S::one());
952        None
953      } else if s <= S::zero() {
954        None
955      } else {
956        let (t, p) = if t < S::zero() {
957          (Normalized::zero(), segment.point_a())
958        } else {
959          (Normalized::unchecked (t), p)
960        };
961        let (s, q) = if s > S::one() {
962          (Normalized::one(), segment.point_b())
963        } else {
964          (Normalized::unchecked (s), q)
965        };
966        Some (((t, p), (s, q)))
967      }
968    }
969    (Some(_), None) | (None, Some(_)) => unreachable!(),
970    (None, None) => None
971  }
972}
973
974/// Compute continuous intersection of a line segment with a triangle
975pub fn continuous_triangle3_segment3 <S> (
976  triangle : Triangle3 <S>, segment : Segment3 <S>
977) -> Option <Segment3Point <S>> where
978  S : OrderedField + approx::AbsDiffEq <Epsilon = S>
979{
980  line_triangle (segment.affine_line(), triangle)
981    .and_then (|(s, p)| Normalized::new (s).map (|s| (s, p)))
982}
983
984/// Compute discrete intersection of a line segment with a triangle
985#[inline]
986pub fn discrete_triangle3_segment3 <S> (
987  triangle : Triangle3 <S>, segment : Segment3 <S>
988) -> bool where
989  S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S>
990{
991  continuous_triangle3_segment3 (triangle, segment).is_some()
992}
993
994/// Compute continuous intersection of a line with a triangle
995pub fn discrete_triangle3 <S> (triangle_a : Triangle3 <S>, triangle_b : Triangle3 <S>)
996  -> bool
997where S : Real + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
998  // at least one edge must intersect in one triangle
999  for edge in triangle_a.edges() {
1000    if discrete_triangle3_segment3 (triangle_b, edge) {
1001      return true
1002    }
1003  }
1004  for edge in triangle_b.edges() {
1005    if discrete_triangle3_segment3 (triangle_a, edge) {
1006      return true
1007    }
1008  }
1009  false
1010}
1011
1012
1013/// Discrete intersection test of 2D spheres.
1014///
1015/// Spheres that are merely touching are not counted as intersecting:
1016///
1017/// ```
1018/// # use math_utils::num::One;
1019/// # use math_utils::*;
1020/// # use math_utils::geometry::*;
1021/// # use math_utils::geometry::intersect::*;
1022/// let r = Positive::one();
1023/// let a = Sphere2 { center: [ 1.0, 0.0].into(), radius: r };
1024/// let b = Sphere2 { center: [-1.0, 0.0].into(), radius: r };
1025/// assert!(!discrete_sphere2_sphere2 (a, b));
1026/// ```
1027#[inline]
1028pub fn discrete_sphere2_sphere2 <S : OrderedRing> (a : Sphere2 <S>, b : Sphere2 <S>)
1029  -> bool
1030{
1031  let r_ab = a.radius + b.radius;
1032  (b.center - a.center).self_dot() < (r_ab * r_ab).into()
1033}
1034
1035/// Discrete intersection test of 3D spheres.
1036///
1037/// Spheres that are merely touching are not counted as intersecting:
1038///
1039/// ```
1040/// # use math_utils::num::One;
1041/// # use math_utils::*;
1042/// # use math_utils::geometry::*;
1043/// # use math_utils::geometry::intersect::*;
1044/// let r = Positive::one();
1045/// let a = Sphere3 { center: [ 1.0, 0.0,  0.0].into(), radius: r };
1046/// let b = Sphere3 { center: [-1.0, 0.0,  0.0].into(), radius: r };
1047/// assert!(!discrete_sphere3_sphere3 (a, b));
1048/// ```
1049#[inline]
1050pub fn discrete_sphere3_sphere3 <S : OrderedRing> (a : Sphere3 <S>, b : Sphere3 <S>)
1051  -> bool
1052{
1053  let r_ab = a.radius + b.radius;
1054  (b.center - a.center).self_dot() < (r_ab * r_ab).into()
1055}
1056
1057/// Given an infinitely extended line defined by segment endpoints (a, b), check to see
1058/// if line intersects a given triangle, returning parameterized position along the line
1059/// $a + t (b - a)$.
1060///
1061/// ```
1062/// # use math_utils::*;
1063/// # use math_utils::geometry::*;
1064/// # use math_utils::geometry::intersect::*;
1065/// let line = frame::Line3::from (Segment3::noisy (
1066///   [0.0, 0.0, -1.0].into(), [0.0, 0.0, 1.0].into()
1067/// ));
1068/// let triangle = Triangle3::noisy (
1069///   [0.0, 1.0, 0.0].into(), [-1.0, -1.0, 0.0].into(), [ 1.0, -1.0, 0.0].into());
1070/// assert_eq!(
1071///   line_triangle (line, triangle).unwrap(),
1072///   (0.5, [0.0, 0.0, 0.0].into()));
1073/// let triangle = Triangle3::noisy (
1074///   [0.0, 1.0, -2.0].into(), [-1.0, -1.0, -2.0].into(), [ 1.0, -1.0, -2.0].into());
1075/// assert_eq!(
1076///   line_triangle (line, triangle).unwrap(),
1077///   (-0.5, [0.0, 0.0, -2.0].into()));
1078/// ```
1079pub fn line_triangle <S> (line : frame::Line3 <S>, triangle : Triangle3 <S>)
1080  -> Option <Line3Point <S>>
1081where S : OrderedField + approx::AbsDiffEq <Epsilon = S> {
1082  // M\"oller-Tumbore algorithm from wikipedia
1083  let r = line.basis;
1084  let edge1 = triangle.point_b() - triangle.point_a();
1085  let edge2 = triangle.point_c() - triangle.point_a();
1086  // rejection test 1
1087  let p = r.cross (edge2);
1088  let a = edge1.dot (p);
1089  if approx::abs_diff_eq!(a, S::zero()) {
1090    return None
1091  }
1092  // rejection test 2
1093  let f = S::one() / a;
1094  let s = line.origin - triangle.point_a();
1095  let u = f * s.dot (p);
1096  if u < S::zero() || u > S::one() {
1097    return None
1098  }
1099  // rejection test 3
1100  let q = s.cross (edge1);
1101  let v = f * r.dot (q);
1102  if v < S::zero() || u + v > S::one() {
1103    return None
1104  }
1105  // intersection
1106  let t = f * edge2.dot (q);
1107  Some ((t, line.origin + *r * t))
1108}
1109
1110#[cfg(test)]
1111mod tests {
1112  extern crate test;
1113
1114  use super::*;
1115
1116  #[expect(clippy::cast_possible_truncation)]
1117  static RAY_TRIANGLE_BENCH_SEED : std::sync::LazyLock <u64> = std::sync::LazyLock::new (
1118    || std::time::SystemTime::UNIX_EPOCH.elapsed().unwrap().as_nanos() as u64);
1119
1120  #[test]
1121  fn line2_aabb2() {
1122    use std::f64::consts::SQRT_2;
1123    let aabb = Aabb2::with_minmax_unchecked ([-1.0, -1.0].into(), [1.0, 1.0].into());
1124    let line = Line2::new ([0.0, 0.0].into(), Unit2::axis_y());
1125    assert_eq!(
1126      continuous_line2_aabb2 (line, aabb).unwrap(),
1127      ((-1.0, [ 0.0, -1.0].into()), (1.0, [ 0.0, 1.0].into())));
1128    let line = Line2::new ([0.0, 0.0].into(), Unit2::axis_x());
1129    assert_eq!(
1130      continuous_line2_aabb2 (line, aabb).unwrap(),
1131      ((-1.0, [-1.0,  0.0].into()), (1.0, [ 1.0, 0.0].into())));
1132    let line = Line2::new ([0.0, 0.0].into(), Unit2::normalize ([1.0, 1.0].into()));
1133    assert_eq!(
1134      continuous_line2_aabb2 (line, aabb).unwrap(),
1135      ((-SQRT_2, [-1.0, -1.0].into()), (SQRT_2, [ 1.0,  1.0].into())));
1136    let line = Line2::new ([0.0, 0.0].into(), Unit2::normalize ([-1.0, -1.0].into()));
1137    assert_eq!(
1138      continuous_line2_aabb2 (line, aabb).unwrap(),
1139      ((-SQRT_2, [1.0,  1.0].into()), (SQRT_2, [-1.0, -1.0].into())));
1140    let line = Line2::new ([0.0, 3.0].into(), Unit2::normalize ([-1.0, -1.0].into()));
1141    assert!(continuous_line2_aabb2 (line, aabb).is_none());
1142    let line = Line2::new ([0.0, -3.0].into(), Unit2::normalize ([1.0,  1.0].into()));
1143    assert!(continuous_line2_aabb2 (line, aabb).is_none());
1144  }
1145
1146  #[test]
1147  fn line3_aabb3() {
1148    use approx::assert_ulps_eq;
1149    let aabb = Aabb3::with_minmax_unchecked (
1150      [-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into());
1151    let line = Line3::new ([0.0, 0.0, 0.0].into(), Unit3::axis_z());
1152    assert_eq!(
1153      continuous_line3_aabb3 (line, aabb).unwrap(),
1154      ((-1.0, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 1.0].into())));
1155    let line = Line3::new ([0.0, 0.0, 0.0].into(), Unit3::axis_y());
1156    assert_eq!(
1157      continuous_line3_aabb3 (line, aabb).unwrap(),
1158      ((-1.0, [0.0, -1.0,  0.0].into()), (1.0, [0.0, 1.0, 0.0].into())));
1159    {
1160      let line = Line3::new (
1161        [0.0, 0.0, 0.0].into(),
1162        Unit3::normalize ([1.0, 1.0, 1.0].into()));
1163      let result = continuous_line3_aabb3 (line, aabb).unwrap();
1164      assert_ulps_eq!((result.0).0, -f64::sqrt_3());
1165      assert_ulps_eq!((result.1).0, f64::sqrt_3());
1166      assert_eq!((result.0).1, [-1.0, -1.0, -1.0].into());
1167      assert_eq!((result.1).1, [ 1.0,  1.0,  1.0].into());
1168    }
1169    {
1170      let line = Line3::new (
1171        [0.0, 0.0, 0.0].into(),
1172        Unit3::normalize ([-1.0, -1.0, -1.0].into()));
1173      let result = continuous_line3_aabb3 (line, aabb).unwrap();
1174      assert_ulps_eq!((result.0).0, -f64::sqrt_3());
1175      assert_ulps_eq!((result.1).0, f64::sqrt_3());
1176      assert_eq!((result.0).1, [ 1.0,  1.0,  1.0].into());
1177      assert_eq!((result.1).1, [-1.0, -1.0, -1.0].into());
1178    }
1179    let line = Line3::new (
1180      [0.0, 0.0, 3.0].into(),
1181      Unit3::normalize ([-1.0, -1.0, -1.0].into()));
1182    assert!(continuous_line3_aabb3 (line, aabb).is_none());
1183    let line = Line3::new (
1184      [0.0, 0.0, -3.0].into(),
1185      Unit3::normalize ([1.0, 1.0, 1.0].into()));
1186    assert!(continuous_line3_aabb3 (line, aabb).is_none());
1187  }
1188
1189  #[test]
1190  fn segment3_sphere3() {
1191    let sphere  = shape::Sphere::unit().sphere3 (Point3::origin());
1192    let segment = Segment3::noisy ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
1193    assert_eq!(
1194      continuous_segment3_sphere3 (segment, sphere)
1195        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1196      ((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
1197    let segment = Segment3::noisy ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
1198    assert_eq!(
1199      continuous_segment3_sphere3 (segment, sphere)
1200        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1201      ((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
1202    let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 2.0].into());
1203    assert_eq!(
1204      continuous_segment3_sphere3 (segment, sphere)
1205        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1206      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 1.0].into())));
1207    let segment = Segment3::noisy ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
1208    assert_eq!(
1209      continuous_segment3_sphere3 (segment, sphere)
1210        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1211      ((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
1212  }
1213
1214  #[test]
1215  fn segment3_cylinder3() {
1216    let cylinder = shape::Cylinder::unit().cylinder3 (Point3::origin());
1217    let segment = Segment3::noisy ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
1218    assert_eq!(
1219      continuous_segment3_cylinder3 (segment, cylinder)
1220        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1221      ((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
1222    let segment = Segment3::noisy ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
1223    assert_eq!(
1224      continuous_segment3_cylinder3 (segment, cylinder)
1225        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1226      ((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
1227    let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 2.0].into());
1228    assert_eq!(
1229      continuous_segment3_cylinder3 (segment, cylinder)
1230        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1231      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 1.0].into())));
1232    let segment = Segment3::noisy ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
1233    assert_eq!(
1234      continuous_segment3_cylinder3 (segment, cylinder)
1235        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1236      ((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
1237  }
1238
1239  #[test]
1240  fn segment3_capsule3() {
1241    let capsule = shape::Capsule::noisy (1.0, 1.0).capsule3 (Point3::origin());
1242    let segment = Segment3::noisy ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
1243    assert_eq!(
1244      continuous_segment3_capsule3 (segment, capsule)
1245        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1246      ((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
1247    let segment = Segment3::noisy ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
1248    assert_eq!(
1249      continuous_segment3_capsule3 (segment, capsule)
1250        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1251      ((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
1252    let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 4.0].into());
1253    assert_eq!(
1254      continuous_segment3_capsule3 (segment, capsule)
1255        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1256      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 2.0].into())));
1257    let segment = Segment3::noisy ([0.0, 0.0, -4.0].into(), [0.0, 0.0, 0.0].into());
1258    assert_eq!(
1259      continuous_segment3_capsule3 (segment, capsule)
1260        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1261      ((0.5, [0.0, 0.0, -2.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
1262  }
1263
1264  #[test]
1265  fn segment3_aabb3() {
1266    let aabb =
1267      Aabb3::with_minmax_unchecked ([1.0, -0.5, 0.0].into(), [2.0, 0.5, 1.0].into());
1268    let segment = Segment3::noisy ([-1.0, 0.0, 0.5].into(), [2.0, 0.0, 0.5].into());
1269    assert_eq!(
1270      continuous_segment3_aabb3 (segment, aabb)
1271        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1272      ((2.0/3.0, [1.0, 0.0, 0.5].into()), (1.0, [2.0, 0.0, 0.5].into())));
1273  }
1274
1275  #[test]
1276  fn segment3_hull3() {
1277    let (hull, mesh) = Hull3::from_points_with_mesh (&[
1278      [  0.0,  0.0,  1.0],
1279      [ -1.0, -1.0, -1.0],
1280      [  1.0, -1.0, -1.0],
1281      [  0.0,  1.0, -1.0]
1282    ].map (Point3::from)).unwrap();
1283    let segment = Segment3::noisy ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
1284    assert_eq!(
1285      continuous_segment3_hull3 (segment, &hull, &mesh)
1286        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1287      ((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
1288    let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, -2.0].into());
1289    assert_eq!(
1290      continuous_segment3_hull3 (segment, &hull, &mesh)
1291        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1292      ((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, -1.0].into())));
1293    let segment = Segment3::noisy ([0.0, 0.0, -0.5].into(), [0.0, 0.0, 0.5].into());
1294    assert_eq!(
1295      continuous_segment3_hull3 (segment, &hull, &mesh)
1296        .map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
1297      ((0.0, [0.0, 0.0, -0.5].into()), (1.0, [0.0, 0.0, 0.5].into())));
1298  }
1299
1300  #[test]
1301  fn line_triangle_intersect() {
1302    let triangle = Triangle3::noisy (
1303      [0.0, 1.0, 1.0].into(), [-1.0, -1.0, 1.0].into(), [ 1.0, -1.0, 1.0].into());
1304    let line = Segment3::noisy ([0.0, 0.0, -1.0].into(), [0.0, 0.0, 1.0].into());
1305    assert_eq!(
1306      line_triangle (line.into(), triangle).unwrap(),
1307      (1.0, [0.0, 0.0, 1.0].into()));
1308    let line = Segment3::noisy ([0.0, 0.0, 1.0].into(), [0.0, 0.0, 2.0].into());
1309    assert_eq!(
1310      line_triangle (line.into(), triangle).unwrap(),
1311      (0.0, [0.0, 0.0, 1.0].into()));
1312    let line = Segment3::noisy ([0.0, 0.0, 2.0].into(), [0.0, 0.0, 3.0].into());
1313    assert_eq!(
1314      line_triangle (line.into(), triangle).unwrap(),
1315      (-1.0, [0.0, 0.0, 1.0].into()));
1316    let line = Segment3::noisy ([5.0, 5.0, 1.0].into(), [5.0, 5.0, 2.0].into());
1317    assert!(line_triangle (line.into(), triangle).is_none());
1318  }
1319
1320  #[bench]
1321  fn bench_line_triangle (b : &mut test::Bencher) {
1322    use rand::SeedableRng;
1323    let aabb = Aabb3::with_minmax_unchecked (
1324      [-10.0, -10.0, -10.0].into(),
1325      [ 10.0,  10.0,  10.0].into());
1326    let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (*RAY_TRIANGLE_BENCH_SEED);
1327    let mut iter = 0;
1328    let mut num_intersections = 0;
1329    b.iter(||{
1330      let line = Segment3::noisy (
1331        aabb.rand_point (&mut rng), aabb.rand_point (&mut rng));
1332      let triangle = Triangle3::noisy (
1333        aabb.rand_point (&mut rng), aabb.rand_point (&mut rng),
1334        aabb.rand_point (&mut rng));
1335      if line_triangle (line.into(), triangle).is_some() && iter <= 1_000_000 {
1336        num_intersections += 1;
1337      }
1338      iter += 1;
1339    });
1340    let n = Ord::min (iter, 1_000_000);
1341    println!("{num_intersections}/{n} intersections");
1342  }
1343
1344} // end tests