math_utils/geometry/primitive/
simplex.rs

1pub use self::simplex2::Simplex2;
2pub use self::simplex3::Simplex3;
3
4/// 2D simplex types
5pub mod simplex2 {
6  #[cfg(feature = "derive_serdes")]
7  use serde::{Deserialize, Serialize};
8
9  use crate::*;
10  use crate::geometry::*;
11
12  /// A $n<3$-simplex in 2-dimensional space.
13  ///
14  /// Individual simplex variants should fail to construct in debug builds when points
15  /// are degenerate.
16  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
17  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
18  pub enum Simplex2 <S> {
19    Segment  (Segment  <S>),
20    Triangle (Triangle <S>)
21  }
22
23  /// A 1-simplex or line segment in 2D space.
24  ///
25  /// Creation methods should fail with a debug assertion if the points are identical.
26  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
27  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
28  pub struct Segment <S> {
29    a : Point2 <S>,
30    b : Point2 <S>
31  }
32
33  /// A 2-simplex or triangle in 2D space
34  ///
35  /// Creation methods should fail with a debug assertion if the points are colinear.
36  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
37  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
38  pub struct Triangle <S> {
39    a : Point2 <S>,
40    b : Point2 <S>,
41    c : Point2 <S>
42  }
43
44  impl <S : Ring> Segment <S> {
45    /// Panics if the points are identical:
46    ///
47    /// ```should_panic
48    /// # use math_utils::geometry::simplex2::Segment;
49    /// let s = Segment::new ([0.0, 0.0].into(), [0.0, 0.0].into());
50    /// ```
51    pub fn new (a : Point2 <S>, b : Point2 <S>) -> Self where S : std::fmt::Debug {
52      assert_ne!(a, b);
53      Segment { a, b }
54    }
55    /// Debug panic if the points are identical:
56    ///
57    /// ```should_panic
58    /// # use math_utils::geometry::simplex2::Segment;
59    /// let s = Segment::unchecked ([0.0, 0.0].into(), [0.0, 0.0].into());
60    /// ```
61    pub fn unchecked (a : Point2 <S>, b : Point2 <S>) -> Self where S : std::fmt::Debug {
62      debug_assert_ne!(a, b);
63      Segment { a, b }
64    }
65    #[inline]
66    pub fn numcast <T> (self) -> Option <Segment <T>> where
67      S : num_traits::NumCast,
68      T : num_traits::NumCast
69    {
70      Some (Segment {
71        a: self.a.numcast()?,
72        b: self.b.numcast()?
73      })
74    }
75    #[inline]
76    pub const fn point_a (&self) -> &Point2 <S> {
77      &self.a
78    }
79    #[inline]
80    pub const fn point_b (&self) -> &Point2 <S> {
81      &self.b
82    }
83    #[inline]
84    pub fn length (&self) -> S where S : Field + Sqrt {
85      self.vector().norm()
86    }
87    #[inline]
88    pub fn length2 (&self) -> S {
89      self.vector().self_dot()
90    }
91    /// Returns `point_b() - point_a()`
92    #[inline]
93    pub fn vector (&self) -> Vector2 <S> {
94      self.b - self.a
95    }
96    #[inline]
97    pub fn aabb2 (&self) -> Aabb2 <S> where S : std::fmt::Debug {
98      Aabb2::from_points (self.a, self.b)
99    }
100    #[inline]
101    pub fn line2 (&self) -> Line2 <S> where S : Real {
102      Line2::new (self.a, Unit2::normalize (self.vector()))
103    }
104    #[inline]
105    #[expect(clippy::type_complexity)]
106    pub fn intersect_aabb (&self, aabb : &Aabb2 <S>)
107      -> Option <((S, Point2 <S>), (S, Point2 <S>))>
108    where S : Real + std::fmt::Debug {
109      intersect::continuous_segment2_aabb2 (self, aabb)
110    }
111    #[inline]
112    #[expect(clippy::type_complexity)]
113    pub fn intersect_sphere (&self, sphere : &Sphere2 <S>)
114      -> Option <((S, Point2 <S>), (S, Point2 <S>))>
115    where S : Real + std::fmt::Debug {
116      intersect::continuous_segment2_sphere2 (self, sphere)
117    }
118  }
119  impl <S : Field> Default for Segment <S> {
120    /// A default simplex is arbitrarily chosen to be the simplex from -1.0 to 1.0 lying
121    /// on the X axis:
122    ///
123    /// ```
124    /// # use math_utils::geometry::simplex2::Segment;
125    /// assert_eq!(
126    ///   Segment::default(),
127    ///   Segment::new ([-1.0, 0.0].into(), [1.0, 0.0].into())
128    /// );
129    /// ```
130    fn default() -> Self {
131      Segment {
132        a: [-S::one(), S::zero()].into(),
133        b: [ S::one(), S::zero()].into()
134      }
135    }
136  }
137
138  impl <S : Field> Triangle <S> {
139    /// Panics if the points are colinear:
140    ///
141    /// ```should_panic
142    /// # use math_utils::geometry::simplex2::Triangle;
143    /// let s = Triangle::new (
144    ///   [-1.0, -1.0].into(),
145    ///   [ 0.0,  0.0].into(),
146    ///   [ 1.0,  1.0].into());
147    /// ```
148    pub fn new (a : Point2 <S>, b : Point2 <S>, c : Point2 <S>) -> Self where
149      S : approx::RelativeEq + std::fmt::Debug
150    {
151      debug_assert_ne!(a, b);
152      debug_assert_ne!(a, c);
153      debug_assert_ne!(b, c);
154      assert!(!colinear_2d (&a, &b, &c));
155      Triangle { a, b, c }
156    }
157    /// Debug panic if the points are colinear:
158    ///
159    /// ```should_panic
160    /// # use math_utils::geometry::simplex2::Triangle;
161    /// let s = Triangle::unchecked (
162    ///   [-1.0, -1.0].into(),
163    ///   [ 0.0,  0.0].into(),
164    ///   [ 1.0,  1.0].into());
165    /// ```
166    pub fn unchecked (a : Point2 <S>, b : Point2 <S>, c : Point2 <S>) -> Self where
167      S : approx::RelativeEq + std::fmt::Debug
168    {
169      debug_assert_ne!(a, b);
170      debug_assert_ne!(a, c);
171      debug_assert_ne!(b, c);
172      debug_assert!(!colinear_2d (&a, &b, &c));
173      Triangle { a, b, c }
174    }
175    #[inline]
176    pub const fn point_a (&self) -> &Point2 <S> {
177      &self.a
178    }
179    #[inline]
180    pub const fn point_b (&self) -> &Point2 <S> {
181      &self.b
182    }
183    #[inline]
184    pub const fn point_c (&self) -> &Point2 <S> {
185      &self.c
186    }
187  }
188  impl <S : Field + Sqrt> Default for Triangle <S> {
189    /// A default triangle is arbitrarily chosen to be the equilateral triangle with
190    /// point at 1.0 on the Y axis:
191    ///
192    /// ```
193    /// # use math_utils::approx;
194    /// # use math_utils::geometry::simplex2::Triangle;
195    /// use std::f64::consts::FRAC_1_SQRT_2;
196    /// let s = Triangle::default();
197    /// let t = Triangle::new (
198    ///   [           0.0,            1.0].into(),
199    ///   [-FRAC_1_SQRT_2, -FRAC_1_SQRT_2].into(),
200    ///   [ FRAC_1_SQRT_2, -FRAC_1_SQRT_2].into()
201    /// );
202    /// approx::assert_relative_eq!(s.point_a(), t.point_a());
203    /// approx::assert_relative_eq!(s.point_b(), t.point_b());
204    /// approx::assert_relative_eq!(s.point_c(), t.point_c());
205    /// ```
206    fn default() -> Self {
207      let frac_1_sqrt_2 = S::one() / (S::one() + S::one()).sqrt();
208      Triangle {
209        a: [     S::zero(),       S::one()].into(),
210        b: [-frac_1_sqrt_2, -frac_1_sqrt_2].into(),
211        c: [ frac_1_sqrt_2, -frac_1_sqrt_2].into()
212      }
213    }
214  }
215}
216
217/// 3D simplex types
218pub mod simplex3 {
219  #[cfg(feature = "derive_serdes")]
220  use serde::{Deserialize, Serialize};
221
222  use crate::*;
223  use crate::geometry::*;
224  /// A $n<4$-simplex in 3-dimensional space.
225  ///
226  /// Individual simplex variants should fail to construct in debug builds when points
227  /// are degenerate.
228  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
229  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
230  pub enum Simplex3 <S> {
231    Segment     (Segment     <S>),
232    Triangle    (Triangle    <S>),
233    Tetrahedron (Tetrahedron <S>)
234  }
235
236  /// A 1-simplex or line segment in 3D space.
237  ///
238  /// Creation methods should fail with a debug assertion if the points are identical.
239  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
240  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
241  pub struct Segment <S> {
242    a : Point3 <S>,
243    b : Point3 <S>
244  }
245
246  /// A 2-simplex or triangle in 3D space
247  ///
248  /// Creation methods should fail with a debug assertion if the points are colinear.
249  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
250  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
251  pub struct Triangle <S> {
252    a : Point3 <S>,
253    b : Point3 <S>,
254    c : Point3 <S>
255  }
256
257  /// A 3-simplex or tetrahedron in 3D space
258  ///
259  /// Creation methods will fail with a debug assertion if the points are coplanar.
260  #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
261  #[derive(Clone, Copy, Debug, Eq, PartialEq)]
262  pub struct Tetrahedron <S> {
263    a : Point3 <S>,
264    b : Point3 <S>,
265    c : Point3 <S>,
266    d : Point3 <S>
267  }
268
269  impl <S : Ring> Segment <S> {
270    /// Panics if the points are identical:
271    ///
272    /// ```should_panic
273    /// # use math_utils::geometry::simplex3::Segment;
274    /// let s = Segment::new ([0.0, 0.0, 1.0].into(), [0.0, 0.0, 1.0].into());
275    /// ```
276    pub fn new (a : Point3 <S>, b : Point3 <S>) -> Self where S : std::fmt::Debug {
277      assert_ne!(a, b);
278      Segment { a, b }
279    }
280    /// Debug panic if the points are identical:
281    ///
282    /// ```should_panic
283    /// # use math_utils::geometry::simplex3::Segment;
284    /// let s = Segment::unchecked ([0.0, 0.0, 1.0].into(), [0.0, 0.0, 1.0].into());
285    /// ```
286    pub fn unchecked (a : Point3 <S>, b : Point3 <S>) -> Self where S : std::fmt::Debug {
287      debug_assert_ne!(a, b);
288      Segment { a, b }
289    }
290    #[inline]
291    pub fn numcast <T> (self) -> Option <Segment <T>> where
292      S : num_traits::NumCast,
293      T : num_traits::NumCast
294    {
295      Some (Segment {
296        a: self.a.numcast()?,
297        b: self.b.numcast()?
298      })
299    }
300    #[inline]
301    pub const fn point_a (&self) -> &Point3 <S> {
302      &self.a
303    }
304    #[inline]
305    pub const fn point_b (&self) -> &Point3 <S> {
306      &self.b
307    }
308    #[inline]
309    pub fn length (&self) -> S where S : Field + Sqrt {
310      self.vector().norm()
311    }
312    #[inline]
313    pub fn length2 (&self) -> S {
314      self.vector().self_dot()
315    }
316    /// Returns `point_b() - point_a()`
317    #[inline]
318    pub fn vector (&self) -> Vector3 <S> {
319      self.b - self.a
320    }
321    #[inline]
322    pub fn aabb3 (&self) -> Aabb3 <S> where S : std::fmt::Debug {
323      Aabb3::from_points (self.a, self.b)
324    }
325    #[inline]
326    pub fn line3 (&self) -> Line3 <S> where S : Real {
327      Line3::new (self.a, Unit3::normalize (self.vector()))
328    }
329    #[inline]
330    #[expect(clippy::type_complexity)]
331    pub fn intersect_aabb (&self, aabb : &Aabb3 <S>)
332      -> Option <((S, Point3 <S>), (S, Point3 <S>))>
333    where S : Real + num_traits::Float + approx::RelativeEq <Epsilon=S> +
334      std::fmt::Debug
335    {
336      intersect::continuous_segment3_aabb3 (self, aabb)
337    }
338    #[inline]
339    #[expect(clippy::type_complexity)]
340    pub fn intersect_sphere (&self, sphere : &Sphere3 <S>)
341      -> Option <((S, Point3 <S>), (S, Point3 <S>))>
342    where S : Field + Sqrt {
343      intersect::continuous_segment3_sphere3 (self, sphere)
344    }
345    #[inline]
346    #[expect(clippy::type_complexity)]
347    pub fn intersect_cylinder (&self, sphere : &Cylinder3 <S>)
348      -> Option <((S, Point3 <S>), (S, Point3 <S>))>
349    where S : Real + std::fmt::Debug {
350      intersect::continuous_segment3_cylinder3 (self, sphere)
351    }
352    #[inline]
353    #[expect(clippy::type_complexity)]
354    pub fn intersect_capsule (&self, capsule : &Capsule3 <S>)
355      -> Option <((S, Point3 <S>), (S, Point3 <S>))>
356    where S : Real + std::fmt::Debug {
357      intersect::continuous_segment3_capsule3 (self, capsule)
358    }
359  }
360  impl <S : Field> Default for Segment <S> {
361    /// A default simplex is arbitrarily chosen to be the simplex from -1.0 to 1.0 lying
362    /// on the X axis:
363    ///
364    /// ```
365    /// # use math_utils::geometry::simplex3::Segment;
366    /// assert_eq!(
367    ///   Segment::default(),
368    ///   Segment::new ([-1.0, 0.0, 0.0].into(), [1.0, 0.0, 0.0].into())
369    /// );
370    /// ```
371    fn default() -> Self {
372      Segment {
373        a: [-S::one(), S::zero(), S::zero()].into(),
374        b: [ S::one(), S::zero(), S::zero()].into()
375      }
376    }
377  }
378
379  impl <S : Ring> Triangle <S> {
380    /// Panics if the points are colinear:
381    ///
382    /// ```should_panic
383    /// # use math_utils::geometry::simplex3::Triangle;
384    /// let s = Triangle::new (
385    ///   [-1.0, -1.0, -1.0].into(),
386    ///   [ 0.0,  0.0,  0.0].into(),
387    ///   [ 1.0,  1.0,  1.0].into());
388    /// ```
389    pub fn new (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>) -> Self where
390      S : Field + Sqrt + approx::RelativeEq + std::fmt::Debug
391    {
392      debug_assert_ne!(a, b);
393      debug_assert_ne!(a, c);
394      debug_assert_ne!(b, c);
395      assert!(!colinear_3d (&a, &b, &c));
396      Triangle { a, b, c }
397    }
398    /// Debug panic if the points are colinear:
399    ///
400    /// ```should_panic
401    /// # use math_utils::geometry::simplex3::Triangle;
402    /// let s = Triangle::unchecked (
403    ///   [-1.0, -1.0, -1.0].into(),
404    ///   [ 0.0,  0.0,  0.0].into(),
405    ///   [ 1.0,  1.0,  1.0].into());
406    /// ```
407    pub fn unchecked (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>) -> Self where
408      S : Field + Sqrt + approx::RelativeEq + std::fmt::Debug
409    {
410      debug_assert_ne!(a, b);
411      debug_assert_ne!(a, c);
412      debug_assert_ne!(b, c);
413      debug_assert!(!colinear_3d (&a, &b, &c));
414      Triangle { a, b, c }
415    }
416    #[inline]
417    pub const fn point_a (&self) -> &Point3 <S> {
418      &self.a
419    }
420    #[inline]
421    pub const fn point_b (&self) -> &Point3 <S> {
422      &self.b
423    }
424    #[inline]
425    pub const fn point_c (&self) -> &Point3 <S> {
426      &self.c
427    }
428  }
429  impl <S : Field + Sqrt> Default for Triangle <S> {
430    /// A default simplex is arbitrarily chosen to be the simplex on the XY plane formed
431    /// by an equilateral triangle with point at 1.0 on the Y axis:
432    ///
433    /// ```
434    /// # use math_utils::approx;
435    /// # use math_utils::geometry::simplex3::Triangle;
436    /// # use math_utils::*;
437    /// use std::f64::consts::FRAC_1_SQRT_2;
438    /// let s = Triangle::default();
439    /// let t = Triangle::new (
440    ///   [           0.0,            1.0, 0.0].into(),
441    ///   [-FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0].into(),
442    ///   [ FRAC_1_SQRT_2, -FRAC_1_SQRT_2, 0.0].into()
443    /// );
444    /// approx::assert_relative_eq!(
445    ///   Matrix3::from_col_arrays ([
446    ///     s.point_a().0.into_array(),
447    ///     s.point_b().0.into_array(),
448    ///     s.point_c().0.into_array()
449    ///   ]),
450    ///   Matrix3::from_col_arrays ([
451    ///     t.point_a().0.into_array(),
452    ///     t.point_b().0.into_array(),
453    ///     t.point_c().0.into_array()
454    ///   ]));
455    /// ```
456    fn default() -> Self {
457      let frac_1_sqrt_2 = S::one() / (S::one() + S::one()).sqrt();
458      Triangle {
459        a: [     S::zero(),       S::one(), S::zero()].into(),
460        b: [-frac_1_sqrt_2, -frac_1_sqrt_2, S::zero()].into(),
461        c: [ frac_1_sqrt_2, -frac_1_sqrt_2, S::zero()].into()
462      }
463    }
464  }
465
466  impl <S : Ring> Tetrahedron <S> {
467    /// Panics if the points are coplanar:
468    ///
469    /// ```should_panic
470    /// # use math_utils::geometry::simplex3::Tetrahedron;
471    /// let s = Tetrahedron::new (
472    ///   [-1.0, -1.0, -1.0].into(),
473    ///   [ 1.0,  1.0,  1.0].into(),
474    ///   [-1.0,  1.0,  0.0].into(),
475    ///   [ 1.0, -1.0,  0.0].into());
476    /// ```
477    pub fn new (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>)
478      -> Self
479    where
480      S : approx::RelativeEq
481    {
482      assert!(!coplanar_3d (&a, &b, &c, &d));
483      Tetrahedron { a, b, c, d }
484    }
485    /// Debug panic if the points are coplanar:
486    ///
487    /// ```should_panic
488    /// # use math_utils::geometry::simplex3::Tetrahedron;
489    /// let s = Tetrahedron::unchecked (
490    ///   [-1.0, -1.0, -1.0].into(),
491    ///   [ 1.0,  1.0,  1.0].into(),
492    ///   [-1.0,  1.0,  0.0].into(),
493    ///   [ 1.0, -1.0,  0.0].into());
494    /// ```
495    pub fn unchecked (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>)
496      -> Self
497    where
498      S : approx::RelativeEq
499    {
500      debug_assert!(!coplanar_3d (&a, &b, &c, &d));
501      Tetrahedron { a, b, c, d }
502    }
503    #[inline]
504    pub const fn point_a (&self) -> &Point3 <S> {
505      &self.a
506    }
507    #[inline]
508    pub const fn point_b (&self) -> &Point3 <S> {
509      &self.b
510    }
511    #[inline]
512    pub const fn point_c (&self) -> &Point3 <S> {
513      &self.c
514    }
515    #[inline]
516    pub const fn point_d (&self) -> &Point3 <S> {
517      &self.d
518    }
519  }
520  impl <S : Field + Sqrt> Default for Tetrahedron <S> {
521    /// A default simplex is arbitrarily chosen to be the simplex with vertices at unit
522    /// distance from the origin with A at `[0.0, 0.0, 1.0]` and B at
523    /// `[0.0, sqrt(8.0/9.0), -1.0/3.0]`, and points C and D at
524    /// `[ sqrt(2.0/3.0), -sqrt(2.0/9.0), -1.0/3.0]` and
525    /// `[-sqrt(2.0/3.0), -sqrt(2.0/9.0), -1.0/3.0]`.
526    ///
527    /// ```
528    /// # use math_utils::approx;
529    /// # use math_utils::geometry::simplex3::Tetrahedron;
530    /// # use math_utils::*;
531    /// let s = Tetrahedron::default();
532    /// let t = Tetrahedron::new (
533    ///   [                0.0,                 0.0,      1.0].into(),
534    ///   [                0.0,  f64::sqrt(8.0/9.0), -1.0/3.0].into(),
535    ///   [ f64::sqrt(2.0/3.0), -f64::sqrt(2.0/9.0), -1.0/3.0].into(),
536    ///   [-f64::sqrt(2.0/3.0), -f64::sqrt(2.0/9.0), -1.0/3.0].into());
537    /// approx::assert_relative_eq!(
538    ///   Matrix4::from_col_arrays ([
539    ///     s.point_a().0.with_w (0.0).into_array(),
540    ///     s.point_b().0.with_w (0.0).into_array(),
541    ///     s.point_c().0.with_w (0.0).into_array(),
542    ///     s.point_d().0.with_w (0.0).into_array()
543    ///   ]),
544    ///   Matrix4::from_col_arrays ([
545    ///     t.point_a().0.with_w (0.0).into_array(),
546    ///     t.point_b().0.with_w (0.0).into_array(),
547    ///     t.point_c().0.with_w (0.0).into_array(),
548    ///     t.point_d().0.with_w (0.0).into_array()
549    ///   ]));
550    /// ```
551    fn default() -> Self {
552      let frac_1_3 = S::one()    / S::three();
553      let sqrt_2_3 = (S::two()   / S::three()).sqrt();
554      let sqrt_8_9 = (S::eight() / S::nine()).sqrt();
555      let sqrt_2_9 = (S::two()   / S::nine()).sqrt();
556      Tetrahedron {
557        a: [S::zero(), S::zero(),  S::one()].into(),
558        b: [S::zero(),  sqrt_8_9, -frac_1_3].into(),
559        c: [ sqrt_2_3, -sqrt_2_9, -frac_1_3].into(),
560        d: [-sqrt_2_3, -sqrt_2_9, -frac_1_3].into()
561      }
562    }
563  }
564}