math_utils/geometry/intersect/
mod.rs

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