Skip to main content

math_utils/geometry/
distance.rs

1//! Nearest point and distance routines
2
3use crate::*;
4use super::*;
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7#[expect(clippy::module_name_repetitions)]
8pub struct Distance3 <S> {
9  distance_squared : NonNegative <S>,
10  distance         : Option <NonNegative <S>>,
11  nearest_a        : Point3 <S>,
12  nearest_b        : Point3 <S>
13}
14
15//
16//  2D
17//
18
19/// Returns the nearest 2D point on 2D triangle together with barycentric coordinates in
20/// relation to the triangle edges $ab$ and $ac$
21pub fn nearest_triangle2_point2 <S : OrderedField> (
22  _triangle : Triangle2 <S>, _point : Point2 <S>
23) -> Triangle2Point <S> {
24  unimplemented!("TODO: nearest triangle2 point2")
25}
26
27/// Nearest 2D point on a 2D line defined by a line segment
28pub fn nearest_line2_point2 <S> (line : frame::Line2 <S>, point : Point2 <S>)
29  -> Line2Point <S>
30where S : OrderedRing {
31  let ab        = line.basis;
32  let av        = point - line.origin;
33  let ab2       = ab.self_dot();
34  let ab_dot_av = ab.dot (av);
35  let t         = ab_dot_av / *ab2;
36  let tab       = *ab * t;
37  let at        = line.origin + tab;
38  (t, at)
39}
40
41/// Returns the nearest point on a 2D segment
42pub fn nearest_segment2_point2 <S> (segment : Segment2 <S>, point : Point2 <S>)
43  -> Segment2Point <S>
44where S : OrderedField {
45  use num::One;
46  let (t, point) = nearest_line2_point2 (segment.into(), point);
47  if t < S::zero() {
48    (Normalized::zero(), segment.point_a())
49  } else if t > S::one() {
50    (Normalized::one(), segment.point_b())
51  } else {
52    (Normalized::unchecked (t), point)
53  }
54}
55
56//
57//  3D
58//
59
60/// Returns the nearest 3D points two 3D triangles together with barycentric coordinates
61/// in relation to the triangle edges $ab$ and $ac$
62pub fn nearest_triangle3_triangle3 <S> (
63  _triangle_a : Triangle3 <S>, _triangle_b : Triangle3 <S>
64) -> (Triangle3Point <S>, Triangle3Point <S>) {
65  unimplemented!("TODO: nearest triangle3 triangle3")
66}
67
68/// Returns the nearest 3D point on 3D triangle together with barycentric coordinates in
69/// relation to the triangle edges $ab$ and $ac$, and the nearest point in the 3D line
70/// defined by the segment endpoints together with parameter
71pub fn nearest_triangle3_line3 <S> (triangle : Triangle3 <S>, line : Segment3 <S>)
72  -> (Triangle3Point <S>, Line3Point <S>)
73where S : Real + approx::RelativeEq <Epsilon=S> {
74  // method from Eberly GeometricTools:
75  // <https://github.com/davideberly/GeometricTools/blob/a9a2b149485857273ea1f1cfa5416b392f495882/GTE/Mathematics/DistLine3Triangle3.h>
76  let edge1     = triangle.point_b() - triangle.point_a();
77  let edge2     = triangle.point_c() - triangle.point_a();
78  let n         = edge1.cross (edge2);
79  let dir       = line.vector();
80  let n_dot_dir = n.dot (*dir);
81  if n_dot_dir.abs() > S::zero() {
82    // line and triangle are not parallel
83    let diff             = line.point_a() - triangle.point_a();
84    let n_dot_diff       = n.dot (diff);
85    let intersect        = -n_dot_diff / n_dot_dir;
86    let y                = line.point_a() + *dir * intersect;
87    let tri0_to_y        = y - triangle.point_a();
88    let e1_dot_e1        = edge1.dot (edge1);
89    let e1_dot_e2        = edge1.dot (edge2);
90    let e2_dot_e2        = edge2.dot (edge2);
91    let e1_dot_tri0_to_y = edge1.dot (tri0_to_y);
92    let e2_dot_tri0_to_y = edge2.dot (tri0_to_y);
93    let det = e1_dot_e1 * e2_dot_e2 - e1_dot_e2 * e1_dot_e2;
94    let b1  = (e2_dot_e2 * e1_dot_tri0_to_y - e1_dot_e2 * e2_dot_tri0_to_y) / det;
95    let b2  = (e1_dot_e1 * e2_dot_tri0_to_y - e1_dot_e2 * e1_dot_tri0_to_y) / det;
96    let b0  = S::one() - b1 - b2;
97    if b0 >= S::zero() && b1 >= S::zero() && b2 >= S::zero() {
98      // line and triangle intersect
99      if cfg!(debug_assertions) {
100        approx::assert_relative_eq!(triangle.point_a() + edge1 * b1 + edge2 * b2, y,
101          epsilon = S::default_epsilon() * S::two().powi (36),
102          max_relative = S::default_max_relative() * S::two().powi (36));
103      }
104      return (
105        ([b1, b2].map (Normalized::unchecked), y),
106        (intersect, y) )
107    }
108  }
109  // either parallel or intersection outside triangle: nearest point is on an edge
110  let mut i0 = 2;
111  let mut i1 = 0;
112  let mut i2 = 1;
113  let mut lowest_dist_sq = None;
114  let mut p0 = S::zero();
115  let mut barycentric = [Normalized::zero(), Normalized::zero(), Normalized::zero()];
116  let triangle_points = triangle.points();
117  while i1 < 3 {
118    let segment = Segment3::noisy (triangle_points[i0], triangle_points[i1]);
119    let ((s0, p_line), (s1, p_seg)) = nearest_line3_segment3 (line, segment);
120    let dist_sq = (p_seg - p_line).self_dot();
121    if lowest_dist_sq.is_none_or (|lowest| dist_sq < lowest) {
122      lowest_dist_sq  = Some (dist_sq);
123      p0 = s0;
124      // NOTE: in the reference implementation these indices are in a different order,
125      // but the following gives the correct results in tests
126      barycentric[i0] = s1;
127      barycentric[i1] = Normalized::zero();
128      barycentric[i2] = Normalized::unchecked (S::one() - *s1);
129    }
130    i2 = i0;
131    i0 = i1;
132    i1 += 1;
133  }
134  let point_tri = triangle.point_a() + edge1 * *barycentric[0] + edge2 * *barycentric[1];
135  let point_line = line.point_a() + *dir * p0;
136  ( ([barycentric[0], barycentric[1]], point_tri),
137    (p0, point_line))
138}
139
140/// Returns the nearest 3D point on 3D triangle together with barycentric coordinates in
141/// relation to the triangle edges $ab$ and $ac$, and the nearest point in the 3D
142/// segment
143pub fn nearest_triangle3_segment3 <S> (triangle : Triangle3 <S>, segment : Segment3 <S>)
144  -> (Triangle3Point <S>, Segment3Point <S>)
145where S : Real + approx::RelativeEq <Epsilon=S> {
146  use num::One;
147  let (out_tri, (r, near_line)) = nearest_triangle3_line3 (triangle, segment);
148  if r < S::zero() {
149    let point_a = segment.point_a();
150    let out_tri = nearest_triangle3_point3 (triangle, point_a);
151    (out_tri, (Normalized::zero(), point_a))
152  } else if r > S::one() {
153    let point_b = segment.point_b();
154    let out_tri = nearest_triangle3_point3 (triangle, point_b);
155    (out_tri, (Normalized::one(), point_b))
156  } else {
157    (out_tri, (Normalized::unchecked (r), near_line))
158  }
159}
160
161/// Returns the nearest 3D point on 3D triangle together with barycentric coordinates in
162/// relation to the triangle edges $ab$ and $ac$
163pub fn nearest_triangle3_point3 <S> (triangle : Triangle3 <S>, point : Point3 <S>)
164  -> Triangle3Point <S>
165where S : OrderedField {
166  // method from Eberly GeometricTools:
167  // <https://github.com/davideberly/GeometricTools/blob/87e5d3924200515fd49812844812acf232da26aa/GTE/Mathematics/DistPointTriangle.h>
168  let d = triangle.point_a() - point;
169  let edge0 = triangle.point_b() - triangle.point_a();
170  let edge1 = triangle.point_c() - triangle.point_a();
171  let e00 = edge0.magnitude_squared();
172  let e01 = edge1.dot (edge0);
173  let e11 = edge1.magnitude_squared();
174  let de0 = d.dot (edge0);
175  let de1 = d.dot (edge1);
176  let det = (e00 * e11 - e01 * e01).abs();
177  let mut s = e01 * de1 - e11 * de0;
178  let mut t = e01 * de0 - e00 * de1;
179  // NOTE: in a previous version of this function this was a strict inequality
180  if s + t <= det {
181    if s < S::zero() {
182      if t < S::zero() {
183        // region 4
184        if de0 < S::zero() {
185          t = S::zero();
186          if -de0 > e00 {
187            s = S::one()
188          } else {
189            s = -de0 / e00
190          }
191        } else {
192          s = S::zero();
193          if de1 > S::zero() {
194            t = S::zero()
195          } else if -de1 >= e11 {
196            t = S::one()
197          } else {
198            t = -de1 / e11
199          }
200        }
201      } else {
202        // region 3
203        s = S::zero();
204        if de1 > S::zero() {
205          t = S::zero()
206        } else if -de1 >= e11 {
207          t = S::one()
208        } else {
209          t = -de1 / e11
210        }
211      }
212    } else if t < S::zero() {
213      // region 5
214      t = S::zero();
215      if de0 >= S::zero() {
216        s = S::zero()
217      } else if -de0 >= e00 {
218        s = S::one()
219      } else {
220        s = -de0 / e00
221      }
222    } else {
223      // region 0
224      // minimum at interior point
225      let idet = S::one() / det;
226      s *= idet;
227      t *= idet;
228    }
229  } else if s < S::zero() {
230    // region 2
231    let tmp0 = e01 + de0;
232    let tmp1 = e11 + de1;
233    if tmp1 > tmp0 {
234      let numer = tmp1 - tmp0;
235      let denom = e00 - S::two() * e01 + e11;
236      if numer >= denom {
237        s = S::one();
238        t = S::zero();
239      } else {
240        s = numer / denom;
241        t = S::one() - s;
242      }
243    } else {
244      s = S::zero();
245      if tmp1 <= S::zero() {
246        t = S::one()
247      } else if de1 >= S::zero() {
248        t = S::zero()
249      } else {
250        t = -de1 / e11
251      }
252    }
253  } else if t < S::zero() {
254    // region 6
255    let tmp0 = e01 + de1;
256    let tmp1 = e00 + de0;
257    if tmp1 > tmp0 {
258      let numer = tmp1 - tmp0;
259      let denom = e00 - S::two() * e01 + e11;
260      if numer >= denom {
261        t = S::one();
262        s = S::zero();
263      } else {
264        t = numer / denom;
265        s = S::one() - t;
266      }
267    } else {
268      t = S::zero();
269      if tmp1 <= S::zero() {
270        s = S::one()
271      } else if de0 >= S::zero() {
272        s = S::zero()
273      } else {
274        s = -de0 / e00
275      }
276    }
277  } else {
278    // region 1
279    let numer = e11 + de1 - e01 - de0;
280    if numer <= S::zero() {
281      s = S::zero();
282      t = S::one();
283    } else {
284      let denom = e00 - S::two() * e01 + e11;
285      if numer >= denom {
286        s = S::one();
287        t = S::zero();
288      } else {
289        s = numer / denom;
290        t = S::one() - s;
291      }
292    }
293  }
294  let nearest = triangle.point_a() + edge0 * s + edge1 * t;
295  ([s, t].map (Normalized::unchecked), nearest)
296}
297
298/// Returns the nearest points on given 3D lines
299pub fn nearest_line3_line3 <S> (line_a : Segment3 <S>, line_b : Segment3 <S>)
300  -> (Line3Point <S>, Line3Point <S>)
301where S : OrderedField {
302  // method from Eberly GeometricTools:
303  // <https://github.com/davideberly/GeometricTools/blob/e095b84018766274f1546a7baa12c257fd18f7d8/GTE/Mathematics/DistLineLine.h>
304  let line_a_vec = line_a.vector();
305  let line_b_vec = line_b.vector();
306  let diff       = line_a.point_a() - line_b.point_a();
307  let a00        = line_a_vec.magnitude_squared();
308  let a01        = -line_a_vec.dot (*line_b_vec);
309  let a11        = line_b_vec.magnitude_squared();
310  let b0         = line_a_vec.dot (diff);
311  let det        = S::max (a00 * a11 - a01 * a01, S::zero());
312  let s0;
313  let s1;
314  if det > S::zero() {
315    let b1 = -line_b_vec.dot (diff);
316    s0 = (a01 * b1 - a11 * b0) / det;
317    s1 = (a01 * b0 - a00 * b1) / det;
318  } else {
319    s0 = -b0 / a00;
320    s1 = S::zero();
321  }
322  let nearest_a = line_a.point_a() + *line_a_vec * s0;
323  let nearest_b = line_b.point_a() + *line_b_vec * s1;
324  ((s0, nearest_a), (s1, nearest_b))
325}
326
327/// Returns the nearest points in given 3D line and segment
328pub fn nearest_line3_segment3 <S> (line : Segment3 <S>, segment : Segment3 <S>)
329  -> (Line3Point <S>, Segment3Point <S>)
330where S : OrderedField {
331  // method from Eberly GeometricTools:
332  // <https://github.com/davideberly/GeometricTools/blob/2339413089df7c2b21c2ec403729d9d72f75f2de/GTE/Mathematics/DistLineSegment.h>
333  let line_dir = line.vector();
334  let seg_dir  = segment.vector();
335  let diff     = line.point_a() - segment.point_a();
336  let a00      = line_dir.magnitude_squared();
337  let a01      = -line_dir.dot (*seg_dir);
338  let a11      = seg_dir.magnitude_squared();
339  let b0       = line_dir.dot (diff);
340  let det      = S::max (a00 * a11 - a01 * a01, S::zero());
341  let s0;
342  let mut s1;
343  if det > S::zero() {
344    // line and segment are not parallel
345    let b1 = -seg_dir.dot (diff);
346    s1 = a01 * b0 - a00 * b1;
347    if s1 >= S::zero() {
348      if s1 <= det {
349        s0 = (a01 * b1 - a11 * b0) / det;
350        s1 /= det;
351      } else {
352        s0 = -(a01 + b0) / a00;
353        s1 = S::one();
354      }
355    } else {
356      s0 = -b0 / a00;
357      s1 = S::zero();
358    }
359  } else {
360    // line and segment are parallel
361    s0 = -b0 / a00;
362    s1 = S::zero();
363  }
364  let p_line = line.point_a() + *line_dir * s0;
365  let p_seg  = segment.point_a() + *seg_dir * s1;
366  ((s0, p_line), (Normalized::unchecked (s1), p_seg))
367}
368
369/// Nearest 3D point on a 3D line defined by a line segment
370pub fn nearest_line3_point3 <S> (line : Segment3 <S>, point : Point3 <S>)
371  -> Line3Point <S>
372where S : OrderedRing {
373  let ab = line.vector();
374  let av = point.0 - line.point_a().0;
375  let ab2 = ab.magnitude_squared();
376  let ab_dot_av = ab.dot (av);
377  let t = ab_dot_av / ab2;
378  let tab = *ab * t;
379  let at = line.point_a() + tab;
380  (t, at)
381}
382
383/// Returns the nearest points on given 3D segments
384pub fn nearest_segment3_segment3 <S : Real> (
385  _segment_a : Segment3 <S>, _segment_b : Segment3 <S>
386) -> (Segment3Point <S>, Segment3Point <S>) {
387  unimplemented!("TODO: nearest segment3 segment3")
388}
389
390/// Returns the nearest point on a 3D segment
391pub fn nearest_segment3_point3 <S> (segment : Segment3 <S>, point : Point3 <S>)
392  -> Segment3Point <S>
393where S : OrderedField {
394  use num::One;
395  let (t, point) = nearest_line3_point3 (segment, point);
396  if t < S::zero() {
397    (Normalized::zero(), segment.point_a())
398  } else if t > S::one() {
399    (Normalized::one(), segment.point_b())
400  } else {
401    (Normalized::unchecked (t), point)
402  }
403}
404
405impl <S : Ring> Distance3 <S> {
406  pub const fn nearest_a (&self) -> Point3 <S> {
407    self.nearest_a
408  }
409  pub const fn nearest_b (&self) -> Point3 <S> {
410    self.nearest_b
411  }
412  pub const fn distance_squared (&self) -> NonNegative <S> {
413    self.distance_squared
414  }
415  pub fn distance (&mut self) -> NonNegative <S> where S : Sqrt {
416    self.distance.unwrap_or_else (|| {
417      let distance = self.distance_squared.sqrt();
418      self.distance = Some (distance);
419      distance
420    })
421  }
422
423  pub fn triangle_point (triangle : Triangle3 <S>, point : Point3 <S>) -> Self
424    where S : OrderedField
425  {
426    let (_, nearest_a) = nearest_triangle3_point3 (triangle, point);
427    let distance_squared = (point - nearest_a).norm_squared();
428    Distance3 { distance_squared, distance: None, nearest_a, nearest_b: point }
429  }
430}
431
432#[cfg(test)]
433mod tests {
434  use super::*;
435
436  #[test]
437  fn nearest_3d_line_segment() {
438    let line = Segment3::noisy (
439      [0.0, 0.0, 0.0].into(),
440      [1.0, 1.0, 0.0].into());
441    let segment = Segment3::noisy (
442      [0.0, -1.0, 0.0].into(),
443      [1.0, -1.0, 0.0].into());
444    let ((s0, p_line), (s1, p_seg)) = nearest_line3_segment3 (line, segment);
445    assert_eq!(s0, -0.5);
446    assert_eq!(*s1, 0.0);
447    assert_eq!(p_line, [-0.5, -0.5, 0.0].into());
448    assert_eq!(p_seg, [0.0, -1.0, 0.0].into());
449    let segment = Segment3::noisy (
450      [0.0, -1.0, 0.0].into(),
451      [1.0,  0.0, 0.0].into());
452    let ((s0, p_line), (s1, p_seg)) = nearest_line3_segment3 (line, segment);
453    assert_eq!(s0, -0.5);
454    assert_eq!(*s1, 0.0);
455    assert_eq!(p_line, [-0.5, -0.5, 0.0].into());
456    assert_eq!(p_seg, [0.0, -1.0, 0.0].into());
457  }
458
459  #[test]
460  fn nearest_3d_triangle_line() {
461    let triangle = Triangle3::noisy (
462      [-1.0, -1.0, 0.0].into(),
463      [ 1.0, -1.0, 0.0].into(),
464      [ 0.0,  1.0, 0.0].into());
465    let line = Segment3::noisy (
466      [-2.0, -2.0, 0.0].into(),
467      [-2.0, -2.0, 1.0].into());
468    let (([t0, t1], p_tri), (s0, p_line)) =
469      nearest_triangle3_line3 (triangle, line);
470    assert_eq!((*t0, *t1), (0.0, 0.0));
471    assert_eq!(p_tri, [-1.0, -1.0, 0.0].into());
472    assert_eq!(s0, 0.0);
473    assert_eq!(p_line, [-2.0, -2.0, 0.0].into());
474    let line = Segment3::noisy (
475      [ 2.0, -2.0, 0.0].into(),
476      [ 2.0, -2.0, 1.0].into());
477    let (([t0, t1], p_tri), (s0, p_line)) =
478      nearest_triangle3_line3 (triangle, line);
479    assert_eq!((*t0, *t1), (1.0, 0.0));
480    assert_eq!(p_tri, [1.0, -1.0, 0.0].into());
481    assert_eq!(s0, 0.0);
482    assert_eq!(p_line, [2.0, -2.0, 0.0].into());
483    let line = Segment3::noisy (
484      [0.0, 2.0, 0.0].into(),
485      [0.0, 2.0, 1.0].into());
486    let (([t0, t1], p_tri), (s0, p_line)) =
487      nearest_triangle3_line3 (triangle, line);
488    assert_eq!((*t0, *t1), (0.0, 1.0));
489    assert_eq!(p_tri, [0.0, 1.0, 0.0].into());
490    assert_eq!(s0, 0.0);
491    assert_eq!(p_line, [0.0, 2.0, 0.0].into());
492    let line = Segment3::noisy (
493      [0.0, -2.0, 0.0].into(),
494      [0.0, -2.0, 1.0].into());
495    let (([t0, t1], p_tri), (s0, p_line)) =
496      nearest_triangle3_line3 (triangle, line);
497    assert_eq!((*t0, *t1), (0.5, 0.0));
498    assert_eq!(p_tri, [0.0, -1.0, 0.0].into());
499    assert_eq!(s0, 0.0);
500    assert_eq!(p_line, [0.0, -2.0, 0.0].into());
501  }
502}