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