math_utils/geometry/intersect/
mod.rs

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