math_utils/geometry/primitive/
mod.rs

1//! Affine, convex, and combinatorial spaces
2
3use approx;
4use num_traits as num;
5use rand;
6
7use crate::*;
8use super::{intersect, shape};
9use super::mesh::VertexEdgeTriangleMesh;
10
11#[cfg(feature = "derive_serdes")]
12use serde::{Deserialize, Serialize};
13
14/// Primitives over signed integers
15pub mod integer;
16
17mod hull;
18mod simplex;
19
20pub use self::hull::*;
21pub use self::simplex::{simplex2, simplex3, Simplex2, Simplex3};
22pub use simplex2::Segment     as Segment2;
23pub use simplex3::Segment     as Segment3;
24pub use simplex2::Triangle    as Triangle2;
25pub use simplex3::Triangle    as Triangle3;
26pub use simplex3::Tetrahedron as Tetrahedron3;
27
28pub trait Primitive <S, V> where
29  S : Field,
30  V : VectorSpace <S>
31{
32  fn translate (self, displacement : V) -> Self;
33  fn scale     (self, scale : NonZero <S>) -> Self;
34}
35
36////////////////////////////////////////////////////////////////////////////////////////
37//  structs                                                                           //
38////////////////////////////////////////////////////////////////////////////////////////
39
40/// 1D interval
41#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub struct Interval <S> {
44  min : S,
45  max : S
46}
47
48/// 2D axis-aligned bounding box.
49///
50/// Min/max can be co-linear:
51/// ```
52/// # use math_utils::geometry::Aabb2;
53/// let x = Aabb2::with_minmax (
54///   [0.0, 0.0].into(),
55///   [0.0, 1.0].into()
56/// );
57/// ```
58/// But not identical:
59/// ```should_panic
60/// # use math_utils::geometry::Aabb2;
61/// // panic!
62/// let x = Aabb2::with_minmax (
63///   [0.0, 0.0].into(),
64///   [0.0, 0.0].into()
65/// );
66/// ```
67#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
69pub struct Aabb2 <S> {
70  min : Point2 <S>,
71  max : Point2 <S>
72}
73
74/// 3D axis-aligned bounding box.
75///
76/// See also `shape::Aabb` trait for primitive and shape types that can compute a 3D
77/// AABB.
78///
79/// Min/max can be co-planar or co-linear:
80/// ```
81/// # use math_utils::geometry::Aabb3;
82/// let x = Aabb3::with_minmax (
83///   [0.0, 0.0, 0.0].into(),
84///   [0.0, 1.0, 1.0].into()
85/// );
86/// let y = Aabb3::with_minmax (
87///   [0.0, 0.0, 0.0].into(),
88///   [0.0, 1.0, 0.0].into()
89/// );
90/// ```
91/// But not identical:
92/// ```should_panic
93/// # use math_utils::geometry::Aabb3;
94/// let x = Aabb3::with_minmax (
95///   [0.0, 0.0, 0.0].into(),
96///   [0.0, 0.0, 0.0].into()
97/// );
98/// ```
99#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101pub struct Aabb3 <S> {
102  min : Point3 <S>,
103  max : Point3 <S>
104}
105
106/// 2D oriented bounding box
107#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub struct Obb2 <S> {
110  pub center        : Point2 <S>,
111  pub half_extent_x : Positive <S>,
112  pub half_extent_y : Positive <S>,
113  pub orientation   : AngleWrapped <S>
114}
115
116/// 3D oriented bounding box
117#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub struct Obb3 <S> {
120  pub center        : Point3 <S>,
121  pub half_extent_x : Positive <S>,
122  pub half_extent_y : Positive <S>,
123  pub half_extent_z : Positive <S>,
124  pub orientation   : Angles3 <S>
125}
126
127/// 3D Z-axis-aligned cylinder
128#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
129#[derive(Clone, Copy, Debug, Eq, PartialEq)]
130pub struct Cylinder3 <S> {
131  pub center      : Point3   <S>,
132  pub half_height : Positive <S>,
133  pub radius      : Positive <S>
134}
135
136/// 3D Z-axis-aligned capsule
137#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
138#[derive(Clone, Copy, Debug, Eq, PartialEq)]
139pub struct Capsule3 <S> {
140  pub center      : Point3   <S>,
141  pub half_height : Positive <S>,
142  pub radius      : Positive <S>
143}
144
145/// An infinitely extended line in 2D space defined by a base point and normalized
146/// direction
147#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
149pub struct Line2 <S> {
150  pub base      : Point2 <S>,
151  pub direction : Unit2  <S>
152}
153
154/// An infinitely extended line in 3D space defined by a base point and normalized
155/// direction
156#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
157#[derive(Clone, Copy, Debug, Eq, PartialEq)]
158pub struct Line3 <S> {
159  pub base      : Point3 <S>,
160  pub direction : Unit3  <S>
161}
162
163/// A plane in 3D space defined by a base point and (unit) normal vector
164#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
166pub struct Plane3 <S> {
167  pub base   : Point3 <S>,
168  pub normal : Unit3  <S>
169}
170
171/// Sphere in 2D space (a circle)
172#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub struct Sphere2 <S> {
175  pub center : Point2   <S>,
176  pub radius : Positive <S>
177}
178
179/// Sphere in 3D space
180#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182pub struct Sphere3 <S> {
183  pub center : Point3   <S>,
184  pub radius : Positive <S>
185}
186
187/// Describes points of a 2D convex hull that support a rectangle and the area of the
188/// rectangle
189#[derive(Clone, Debug)]
190struct Rect <S> {
191  pub edge    : Vector2 <S>,
192  pub perp    : Vector2 <S>,
193  /// bottom, right, top, left
194  pub indices : [u32; 4],
195  pub area    : S,
196  pub edge_len_squared : S
197}
198
199////////////////////////////////////////////////////////////////////////////////////////
200//  functions                                                                         //
201////////////////////////////////////////////////////////////////////////////////////////
202
203// TODO: should the following functions take values instead of references ?
204
205/// Returns true when three points lie on the same line in 2D space.
206///
207/// ```
208/// # use math_utils::geometry::colinear_2d;
209/// assert!(colinear_2d (
210///   &[-1.0, -1.0].into(),
211///   &[ 0.0,  0.0].into(),
212///   &[ 1.0,  1.0].into())
213/// );
214/// assert!(!colinear_2d (
215///   &[-1.0, -1.0].into(),
216///   &[ 0.0,  1.0].into(),
217///   &[ 1.0, -1.0].into())
218/// );
219/// ```
220pub fn colinear_2d <S> (a : &Point2 <S>, b : &Point2 <S>, c : &Point2 <S>) -> bool where
221  S : Field + approx::RelativeEq
222{
223  // early exit: a pair of points are equal
224  if a == b || a == c || b == c {
225    true
226  } else {
227    let a = Vector3::from_point_2d (a.0).into();
228    let b = Vector3::from_point_2d (b.0).into();
229    let c = Vector3::from_point_2d (c.0).into();
230    let determinant = determinant_3d (&a, &b, &c);
231    // a zero determinant indicates that the points are colinear
232    approx::relative_eq!(S::zero(), determinant)
233  }
234}
235
236/// Returns true when three points lie on the same line in 3D space.
237///
238/// ```
239/// # use math_utils::geometry::colinear_3d;
240/// assert!(colinear_3d (
241///   &[-1.0, -1.0, -1.0].into(),
242///   &[ 0.0,  0.0,  0.0].into(),
243///   &[ 1.0,  1.0,  1.0].into())
244/// );
245/// ```
246pub fn colinear_3d <S> (a : &Point3 <S>, b : &Point3 <S>, c : &Point3 <S>) -> bool where
247  S : OrderedField + Sqrt + approx::RelativeEq
248{
249  // early exit: a pair of points are equal
250  if a == b || a == c || b == c {
251    true
252  } else {
253    let determinant = determinant_3d (a, b, c);
254    // a zero determinant indicates that the points are coplanar
255    if approx::relative_eq!(S::zero(), determinant) {
256      approx::relative_eq!(S::zero(), triangle_3d_area2 (a, b, c))
257    } else {
258      false
259    }
260  }
261}
262
263/// Returns true when four points lie on the same plane in 3D space.
264///
265/// ```
266/// # use math_utils::geometry::coplanar_3d;
267/// assert!(coplanar_3d (
268///   &[-1.0, -1.0, -1.0].into(),
269///   &[ 1.0,  1.0,  1.0].into(),
270///   &[-1.0,  1.0,  0.0].into(),
271///   &[ 1.0, -1.0,  0.0].into()
272/// ));
273/// ```
274pub fn coplanar_3d <S> (
275  a : &Point3 <S>, b : &Point3 <S>, c : &Point3 <S>, d : &Point3 <S>
276) -> bool where
277  S : Ring + approx::RelativeEq
278{
279  // early exit: a pair of points are equal
280  if a == b || a == c || a == d || b == c || b == d || c == d {
281    true
282  } else {
283    let ab_cross_ac_dot_ad = (*b-a).cross (*c-a).dot (*d-a);
284    approx::relative_eq!(S::zero(), ab_cross_ac_dot_ad)
285  }
286}
287
288/// Computes the determinant of a matrix formed by the three points as columns
289#[inline]
290pub fn determinant_3d <S : Ring> (a : &Point3 <S>, b : &Point3 <S>, c : &Point3 <S>)
291  -> S
292{
293  Matrix3::from_col_arrays ([a.0.into_array(), b.0.into_array(), c.0.into_array()])
294    .determinant()
295}
296
297/// Create an orthonormal basis given the first basis component
298pub fn orthonormal_basis <S> (e1 : Unit3 <S>) -> [Unit3 <S>; 3] where S : Real {
299  use num::Zero;
300  let u = Unit3::axis_x();
301  let e2 = {
302    let mut e2 = (*e1).cross (*u);
303    e2.z += if e2.is_zero() {
304      S::one()
305    } else {
306      S::zero()
307    };
308    Unit3::new (e2).unwrap()
309  };
310  let e3 = Unit3::new ((*e1).cross (*e2)).unwrap();
311  [e1, e2, e3]
312}
313
314/// Coordinate-wise min
315#[inline]
316pub fn point2_min <S : OrderedRing> (a : &Point2 <S>, b : &Point2 <S>) -> Point2 <S> {
317  Vector2::partial_min (a.0, b.0).into()
318}
319
320/// Coordinate-wise max
321#[inline]
322pub fn point2_max <S : OrderedRing> (a : &Point2 <S>, b : &Point2 <S>) -> Point2 <S> {
323  Vector2::partial_max (a.0, b.0).into()
324}
325
326/// Coordinate-wise min
327#[inline]
328pub fn point3_min <S : OrderedRing> (a : &Point3 <S>, b : &Point3 <S>) -> Point3 <S> {
329  Vector3::partial_min (a.0, b.0).into()
330}
331
332/// Coordinate-wise max
333#[inline]
334pub fn point3_max <S : OrderedRing> (a : &Point3 <S>, b : &Point3 <S>) -> Point3 <S> {
335  Vector3::partial_max (a.0, b.0).into()
336}
337
338/// Given a 2D point and a 2D line, returns the nearest point on the line to the given
339/// point
340#[inline]
341pub fn project_2d_point_on_line <S : Real> (point : &Point2 <S>, line : &Line2 <S>)
342  -> Point2 <S>
343{
344  let dot_dir = line.direction.dot (*point - line.base);
345  line.point (dot_dir)
346}
347
348/// Given a 3D point and a 3D line, returns the nearest point on the line to the given
349/// point
350#[inline]
351pub fn project_3d_point_on_line <S : Real> (point : &Point3 <S>, line : &Line3 <S>)
352  -> Point3 <S>
353{
354  let dot_dir = line.direction.dot (*point - line.base);
355  line.point (dot_dir)
356}
357
358/// Given a 3D point and a 3D plane, returns the nearest point on the plane to the given
359/// point
360#[inline]
361pub fn project_3d_point_on_plane <S : Real> (point : &Point3 <S>, plane : &Plane3 <S>)
362  -> Point3 <S>
363{
364  let v = point - plane.base;
365  let distance = (*plane.normal).dot (v);
366  point - *plane.normal * distance
367}
368
369#[inline]
370pub fn support2 <S> (points : &[Point2 <S>], direction : Vector2 <S>) -> (Point2 <S>, S)
371  where S : Ring + PartialOrd
372{
373  debug_assert!(!points.is_empty());
374  points.iter()
375    .map (|point| (*point, direction.dot (point.0)))
376    .max_by (|(_, dot_a), (_, dot_b)| dot_a.partial_cmp (dot_b).unwrap()).unwrap()
377}
378
379#[inline]
380pub fn support3 <S> (points : &[Point3 <S>], direction : Vector3 <S>) -> (Point3 <S>, S)
381  where S : Ring + PartialOrd
382{
383  debug_assert!(!points.is_empty());
384  points.iter()
385    .map (|point| (*point, direction.dot (point.0)))
386    .max_by (|(_, dot_a), (_, dot_b)| dot_a.partial_cmp (dot_b).unwrap()).unwrap()
387}
388
389/// Square area of three points in 3D space.
390///
391/// Uses a numerically stable Heron's formula:
392/// <https://en.wikipedia.org/wiki/Heron%27s_formula#Numerical_stability>
393///
394/// ```
395/// # use math_utils::approx::assert_relative_eq;
396/// # use math_utils::geometry::triangle_3d_area2;
397/// assert_relative_eq!(
398///   3.0/4.0,
399///   triangle_3d_area2 (
400///     &[-1.0,  0.0,  0.0].into(),
401///     &[ 0.0,  0.0,  1.0].into(),
402///     &[ 0.0,  1.0,  0.0].into())
403/// );
404/// ```
405///
406/// If the area squared is zero then the points are colinear:
407///
408/// ```
409/// # use math_utils::geometry::triangle_3d_area2;
410/// assert_eq!(
411///   0.0,
412///   triangle_3d_area2 (
413///     &[-1.0, -1.0, -1.0].into(),
414///     &[ 0.0,  0.0,  0.0].into(),
415///     &[ 1.0,  1.0,  1.0].into())
416/// );
417/// ```
418pub fn triangle_3d_area2 <S> (a : &Point3 <S>, b : &Point3 <S>, c : &Point3 <S>) -> S
419  where S : OrderedField + Sqrt
420{
421  if a == b || a == c || b == c {
422    return S::zero()
423  }
424  // compute the length of each side
425  let ab_mag = (*b-a).norm();
426  let ac_mag = (*c-a).norm();
427  let bc_mag = (*c-b).norm();
428  // order as max >= mid >= min
429  let max = S::max (S::max (ab_mag, ac_mag), bc_mag);
430  let min = S::min (S::min (ab_mag, ac_mag), bc_mag);
431  let mid = if min <= ab_mag && ab_mag <= max {
432    ab_mag
433  } else if min <= ac_mag && ac_mag <= max {
434    ac_mag
435  } else {
436    bc_mag
437  };
438  let two = S::one() + S::one();
439  let sixteen = two * two * two * two;
440  // 1.0/16.0
441  let frac = S::one() / sixteen;
442  frac
443    * (max + (mid + min))
444    * (min - (max - mid))
445    * (min + (max - mid))
446    * (max + (mid - min))
447}
448
449// private
450
451/// Smallest rectangle for given edge indices
452fn smallest_rect <S : Real> (i0 : u32, i1 : u32, hull : &Hull2 <S>) -> Rect <S> {
453  // NOTE: the following algorithm assumes that the points in the hull are
454  // counter-clockwise sorted and that the hull does not contain any colinear points.
455  // The construction of the hull should ensure these conditions.
456  let points = hull.points();
457  let edge             = points[i1 as usize] - points[i0 as usize];
458  let perp             = vector2 (-edge.y, edge.x);
459  let edge_len_squared = edge.norm_squared();
460  let origin           = points[i1 as usize];
461  let mut support      = [Point2::<S>::origin(); 4];
462  let mut rect_index   = [i1, i1, i1, i1];
463  #[expect(clippy::cast_possible_truncation)]
464  for (i, point) in points.iter().enumerate() {
465    let diff = point - origin;
466    let v    = point2 (edge.dot (diff), perp.dot (diff));
467    if v.0.x > support[1].0.x || v.0.x == support[1].0.x && v.0.y > support[1].0.y {
468      rect_index[1] = i as u32;
469      support[1]    = v;
470    }
471    if v.0.y > support[2].0.y || v.0.y == support[2].0.y && v.0.x < support[2].0.x {
472      rect_index[2] = i as u32;
473      support[2]    = v;
474    }
475    if v.0.x < support[3].0.x || v.0.x == support[3].0.x && v.0.y < support[3].0.y {
476      rect_index[3] = i as u32;
477      support[3]    = v;
478    }
479  }
480  let scaled_width  = support[1].0.x - support[3].0.x;
481  let scaled_height = support[2].0.y;
482  let area = scaled_width * scaled_height / edge_len_squared;
483
484  Rect {
485    edge, perp, area, edge_len_squared,
486    indices: [rect_index[0], rect_index[1], rect_index[2], rect_index[3]]
487  }
488}
489
490////////////////////////////////////////////////////////////////////////////////
491//  impls                                                                     //
492////////////////////////////////////////////////////////////////////////////////
493
494impl <S : OrderedRing> Interval <S> {
495  /// Construct a new AABB with given min and max points.
496  ///
497  /// Debug panic if points are not min/max:
498  ///
499  /// ```should_panic
500  /// # use math_utils::geometry::*;
501  /// let b = Interval::with_minmax (1.0, 0.0);  // panic!
502  /// ```
503  ///
504  /// Debug panic if points are identical:
505  ///
506  /// ```should_panic
507  /// # use math_utils::geometry::*;
508  /// let b = Interval::with_minmax (0.0, 0.0);  // panic!
509  /// ```
510  #[inline]
511  pub fn with_minmax (min : S, max : S) -> Self where S : std::fmt::Debug {
512    debug_assert_ne!(min, max);
513    debug_assert_eq!(min, S::min (min, max));
514    debug_assert_eq!(max, S::max (min, max));
515    Interval { min, max }
516  }
517  /// Construct a new AABB using the two given points to determine min/max.
518  ///
519  /// Panic if points are identical:
520  ///
521  /// ```should_panic
522  /// # use math_utils::geometry::*;
523  /// let b = Interval::from_points (0.0, 0.0);  // panic!
524  /// ```
525  #[inline]
526  pub fn from_points (a : S, b : S) -> Self where S : std::fmt::Debug {
527    debug_assert_ne!(a, b);
528    let min = S::min (a, b);
529    let max = S::max (a, b);
530    Interval { min, max }
531  }
532  /// Construct the minimum AABB containing the given set of points.
533  ///
534  /// Debug panic if fewer than 2 points are given:
535  ///
536  /// ```should_panic
537  /// # use math_utils::geometry::*;
538  /// let b = Interval::<f32>::containing (&[]);  // panic!
539  /// ```
540  ///
541  /// ```should_panic
542  /// # use math_utils::geometry::*;
543  /// let b = Interval::containing (&[0.0]);  // panic!
544  /// ```
545  #[inline]
546  pub fn containing (points : &[S]) -> Self where S : std::fmt::Debug {
547    debug_assert!(points.len() >= 2);
548    let mut min = S::min (points[0], points[1]);
549    let mut max = S::max (points[0], points[1]);
550    for point in points.iter().skip (2) {
551      min = S::min (min, *point);
552      max = S::max (max, *point);
553    }
554    Interval::with_minmax (min, max)
555  }
556  #[inline]
557  pub fn numcast <T> (self) -> Option <Interval <T>> where
558    S : num::ToPrimitive,
559    T : num::NumCast
560  {
561    Some (Interval {
562      min: T::from (self.min)?,
563      max: T::from (self.max)?
564    })
565  }
566  /// Create a new AABB that is the union of the two input AABBs
567  #[inline]
568  pub fn union (a : &Interval <S>, b : &Interval <S>) -> Self where
569    S : std::fmt::Debug
570  {
571    Interval::with_minmax (S::min (a.min(), b.min()), S::max (a.max(), b.max()))
572  }
573  #[inline]
574  pub const fn min (&self) -> S {
575    self.min
576  }
577  #[inline]
578  pub const fn max (&self) -> S {
579    self.max
580  }
581  #[inline]
582  pub fn length (&self) -> NonNegative <S> {
583    self.width()
584  }
585  #[inline]
586  pub fn width (&self) -> NonNegative <S> {
587    NonNegative::unchecked (self.max - self.min)
588  }
589  #[inline]
590  pub fn contains (&self, point : &S) -> bool {
591    self.min < *point && *point < self.max
592  }
593  /// Clamp a given point to the AABB interval.
594  ///
595  /// ```
596  /// # use math_utils::geometry::*;
597  /// let b = Interval::from_points (-1.0, 1.0);
598  /// assert_eq!(b.clamp (&-2.0), -1.0);
599  /// assert_eq!(b.clamp ( &2.0),  1.0);
600  /// assert_eq!(b.clamp ( &0.0),  0.0);
601  /// ```
602  pub fn clamp (&self, point : &S) -> S {
603    S::max (S::min (self.max, *point), self.min)
604  }
605  /// Generate a random point contained in the AABB
606  ///
607  /// ```
608  /// # use rand;
609  /// # use rand_xorshift;
610  /// # use math_utils::geometry::*;
611  /// # fn main () {
612  /// use rand_xorshift;
613  /// use rand::SeedableRng;
614  /// // random sequence will be the same each time this is run
615  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
616  /// let aabb = Interval::<f32>::with_minmax (-10.0, 10.0);
617  /// let point = aabb.rand_point (&mut rng);
618  /// assert!(aabb.contains (&point));
619  /// let point = aabb.rand_point (&mut rng);
620  /// assert!(aabb.contains (&point));
621  /// let point = aabb.rand_point (&mut rng);
622  /// assert!(aabb.contains (&point));
623  /// let point = aabb.rand_point (&mut rng);
624  /// assert!(aabb.contains (&point));
625  /// let point = aabb.rand_point (&mut rng);
626  /// assert!(aabb.contains (&point));
627  /// # }
628  /// ```
629  #[inline]
630  pub fn rand_point <R> (&self, rng : &mut R) -> S where
631    S : rand::distr::uniform::SampleUniform,
632    R : rand::Rng
633  {
634    rng.random_range (self.min..self.max)
635  }
636  #[inline]
637  pub fn intersects (&self, other : &Interval <S>) -> bool {
638    intersect::discrete_interval (self, other)
639  }
640  #[inline]
641  pub fn intersection (self, other : Interval <S>) -> Option <Interval <S>>
642    where S : std::fmt::Debug
643  {
644    intersect::continuous_interval (&self, &other)
645  }
646
647  /// Increase or decrease each endpoint by the given amount.
648  ///
649  /// Debug panic if the interval width is less than or equal to twice the given amount:
650  /// ```should_panic
651  /// # use math_utils::geometry::*;
652  /// let x = Interval::<f32>::with_minmax (-1.0, 1.0);
653  /// let y = x.dilate (-1.0); // panic!
654  /// ```
655  pub fn dilate (mut self, amount : S) -> Self {
656    self.min -= amount;
657    self.max += amount;
658    debug_assert!(self.min < self.max);
659    self
660  }
661}
662
663impl <S> Primitive <S, S> for Interval <S> where S : OrderedField + VectorSpace <S> {
664  fn translate (mut self, displacement : S) -> Self {
665    self.min += displacement;
666    self.max += displacement;
667    self
668  }
669  fn scale (mut self, scale : NonZero <S>) -> Self {
670    let old_width = *self.width();
671    let new_width = old_width * *scale;
672    let half_difference = (new_width - old_width) / S::two();
673    self.min -= half_difference;
674    self.max += half_difference;
675    self
676  }
677}
678
679impl <S> std::ops::Add <S> for Interval <S> where
680  S    : Field + VectorSpace <S>,
681  Self : Primitive <S, S>
682{
683  type Output = Self;
684  #[expect(clippy::renamed_function_params)]
685  fn add (self, displacement : S) -> Self {
686    self.translate (displacement)
687  }
688}
689
690impl <S> std::ops::Sub <S> for Interval <S> where
691  S    : Field + VectorSpace <S>,
692  Self : Primitive <S, S>
693{
694  type Output = Self;
695  #[expect(clippy::renamed_function_params)]
696  fn sub (self, displacement : S) -> Self {
697    self.translate (-displacement)
698  }
699}
700
701impl_numcast_fields!(Aabb2, min, max);
702impl <S : OrderedRing> Aabb2 <S> {
703  /// Construct a new AABB with given min and max points.
704  ///
705  /// Debug panic if points are not min/max:
706  ///
707  /// ```should_panic
708  /// # use math_utils::geometry::*;
709  /// let b = Aabb2::with_minmax ([1.0, 1.0].into(), [0.0, 0.0].into());  // panic!
710  /// ```
711  ///
712  /// Debug panic if points are identical:
713  ///
714  /// ```should_panic
715  /// # use math_utils::geometry::*;
716  /// let b = Aabb2::with_minmax ([0.0, 0.0].into(), [0.0, 0.0].into());  // panic!
717  /// ```
718  #[inline]
719  pub fn with_minmax (min : Point2 <S>, max : Point2 <S>) -> Self where
720    S : std::fmt::Debug
721  {
722    debug_assert_ne!(min, max);
723    debug_assert_eq!(min, point2_min (&min, &max));
724    debug_assert_eq!(max, point2_max (&min, &max));
725    Aabb2 { min, max }
726  }
727  /// Construct a new AABB using the two given points to determine min/max.
728  ///
729  /// Debug panic if points are identical:
730  ///
731  /// ```should_panic
732  /// # use math_utils::geometry::*;
733  /// let b = Aabb2::from_points ([0.0, 0.0].into(), [0.0, 0.0].into());  // panic!
734  /// ```
735  #[inline]
736  pub fn from_points (a : Point2 <S>, b : Point2 <S>) -> Self where
737    S : std::fmt::Debug
738  {
739    debug_assert_ne!(a, b);
740    let min = point2_min (&a, &b);
741    let max = point2_max (&a, &b);
742    Aabb2 { min, max }
743  }
744  /// Construct the minimum AABB containing the given set of points.
745  ///
746  /// Debug panic if fewer than 2 points are given:
747  ///
748  /// ```should_panic
749  /// # use math_utils::geometry::*;
750  /// let b = Aabb2::<f32>::containing (&[]);  // panic!
751  /// ```
752  ///
753  /// ```should_panic
754  /// # use math_utils::geometry::*;
755  /// let b = Aabb2::containing (&[[0.0, 0.0].into()]);  // panic!
756  /// ```
757  #[inline]
758  pub fn containing (points : &[Point2 <S>]) -> Self where S : std::fmt::Debug {
759    debug_assert!(points.len() >= 2);
760    let mut min = point2_min (&points[0], &points[1]);
761    let mut max = point2_max (&points[0], &points[1]);
762    for point in points.iter().skip (2) {
763      min = point2_min (&min, point);
764      max = point2_max (&max, point);
765    }
766    Aabb2::with_minmax (min, max)
767  }
768  /// Create a new AABB that is the union of the two input AABBs
769  #[inline]
770  pub fn union (a : &Aabb2 <S>, b : &Aabb2 <S>) -> Self where S : std::fmt::Debug {
771    Aabb2::with_minmax (point2_min (a.min(), b.min()), point2_max (a.max(), b.max()))
772  }
773  #[inline]
774  pub const fn min (&self) -> &Point2 <S> {
775    &self.min
776  }
777  #[inline]
778  pub const fn max (&self) -> &Point2 <S> {
779    &self.max
780  }
781  #[inline]
782  pub fn center (&self) -> Point2 <S> where S : Field {
783    Point2 ((self.min.0 + self.max.0) / S::two())
784  }
785  #[inline]
786  pub fn dimensions (&self) -> Vector2 <S> {
787    self.max.0 - self.min.0
788  }
789  #[inline]
790  pub fn extents (&self) -> Vector2 <S> where S : Field {
791    self.dimensions() * S::half()
792  }
793  /// X dimension
794  #[inline]
795  pub fn width (&self) -> NonNegative <S> {
796    NonNegative::unchecked (self.max.0.x - self.min.0.x)
797  }
798  /// Y dimension
799  #[inline]
800  pub fn height (&self) -> NonNegative <S> {
801    NonNegative::unchecked (self.max.0.y - self.min.0.y)
802  }
803  #[inline]
804  pub fn area (&self) -> NonNegative <S> {
805    self.width() * self.height()
806  }
807  #[inline]
808  pub fn contains (&self, point : &Point2 <S>) -> bool {
809    self.min.0.x < point.0.x && point.0.x < self.max.0.x &&
810    self.min.0.y < point.0.y && point.0.y < self.max.0.y
811  }
812  /// Clamp a given point to the AABB.
813  ///
814  /// ```
815  /// # use math_utils::geometry::*;
816  /// let b = Aabb2::from_points ([-1.0, -1.0].into(), [1.0, 1.0].into());
817  /// assert_eq!(b.clamp (&[-2.0, 0.0].into()), [-1.0, 0.0].into());
818  /// assert_eq!(b.clamp (&[ 2.0, 2.0].into()), [ 1.0, 1.0].into());
819  /// assert_eq!(b.clamp (&[-0.5, 0.5].into()), [-0.5, 0.5].into());
820  /// ```
821  pub fn clamp (&self, point : &Point2 <S>) -> Point2 <S> {
822    [ S::max (S::min (self.max.0.x, point.0.x), self.min.0.x),
823      S::max (S::min (self.max.0.y, point.0.y), self.min.0.y),
824    ].into()
825  }
826  /// Generate a random point contained in the AABB
827  ///
828  /// ```
829  /// # use rand;
830  /// # use rand_xorshift;
831  /// # use math_utils::geometry::*;
832  /// # fn main () {
833  /// use rand_xorshift;
834  /// use rand::SeedableRng;
835  /// // random sequence will be the same each time this is run
836  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
837  /// let aabb = Aabb2::<f32>::with_minmax (
838  ///   [-10.0, -10.0].into(),
839  ///   [ 10.0,  10.0].into());
840  /// let point = aabb.rand_point (&mut rng);
841  /// assert!(aabb.contains (&point));
842  /// let point = aabb.rand_point (&mut rng);
843  /// assert!(aabb.contains (&point));
844  /// let point = aabb.rand_point (&mut rng);
845  /// assert!(aabb.contains (&point));
846  /// let point = aabb.rand_point (&mut rng);
847  /// assert!(aabb.contains (&point));
848  /// let point = aabb.rand_point (&mut rng);
849  /// assert!(aabb.contains (&point));
850  /// # }
851  /// ```
852  #[inline]
853  pub fn rand_point <R> (&self, rng : &mut R) -> Point2 <S> where
854    S : rand::distr::uniform::SampleUniform,
855    R : rand::Rng
856  {
857    let x_range = self.min.0.x..self.max.0.x;
858    let y_range = self.min.0.y..self.max.0.y;
859    let x = if x_range.is_empty() {
860      self.min.0.x
861    } else {
862      rng.random_range (x_range)
863    };
864    let y = if y_range.is_empty() {
865      self.min.0.y
866    } else {
867      rng.random_range (y_range)
868    };
869    [x, y].into()
870  }
871  #[inline]
872  pub fn intersects (&self, other : &Aabb2 <S>) -> bool {
873    intersect::discrete_aabb2_aabb2 (self, other)
874  }
875  #[inline]
876  pub fn intersection (self, other : Aabb2 <S>) -> Option <Aabb2 <S>> where
877    S : std::fmt::Debug
878  {
879    intersect::continuous_aabb2_aabb2 (&self, &other)
880  }
881
882  pub fn corner (&self, quadrant : Quadrant) -> Point2 <S> {
883    match quadrant {
884      // upper-right
885      Quadrant::PosPos => self.max,
886      // lower-right
887      Quadrant::PosNeg => [self.max.0.x, self.min.0.y].into(),
888      // upper-left
889      Quadrant::NegPos => [self.min.0.x, self.max.0.y].into(),
890      // lower-left
891      Quadrant::NegNeg => self.min
892    }
893  }
894
895  /// Corner points clockwise from min
896  pub fn corners (&self) -> [Point2 <S>; 4] {
897    [ self.min, [self.min.0.x, self.max.0.y].into(),
898      self.max, [self.max.0.x, self.min.0.y].into()
899    ]
900  }
901
902  pub fn edge (&self, direction : SignedAxis2) -> Self where S : std::fmt::Debug {
903    let (min, max) = match direction {
904      SignedAxis2::PosX => (
905        self.corner (Quadrant::PosNeg),
906        self.corner (Quadrant::PosPos) ),
907      SignedAxis2::NegX => (
908        self.corner (Quadrant::NegNeg),
909        self.corner (Quadrant::NegPos) ),
910      SignedAxis2::PosY => (
911        self.corner (Quadrant::NegPos),
912        self.corner (Quadrant::PosPos) ),
913      SignedAxis2::NegY => (
914        self.corner (Quadrant::NegNeg),
915        self.corner (Quadrant::PosNeg) )
916    };
917    Aabb2::with_minmax (min, max)
918  }
919
920  pub fn extrude (&self, axis : SignedAxis2, amount : Positive <S>) -> Self {
921    let (min, max) = match axis {
922      SignedAxis2::PosX => (
923        self.min + Vector2::zero().with_x (*self.width()),
924        self.max + Vector2::zero().with_x (*amount)
925      ),
926      SignedAxis2::NegX => (
927        self.min - Vector2::zero().with_x (*amount),
928        self.max - Vector2::zero().with_x (*self.height())
929      ),
930      SignedAxis2::PosY => (
931        self.min + Vector2::zero().with_y (*self.height()),
932        self.max + Vector2::zero().with_y (*amount)
933      ),
934      SignedAxis2::NegY => (
935        self.min - Vector2::zero().with_y (*amount),
936        self.max - Vector2::zero().with_y (*self.height())
937      )
938    };
939    Aabb2 { min, max }
940  }
941
942  pub fn extend (&mut self, axis : SignedAxis2, amount : Positive <S>) {
943    match axis {
944      SignedAxis2::PosX => self.max.0.x += *amount,
945      SignedAxis2::NegX => self.min.0.x -= *amount,
946      SignedAxis2::PosY => self.max.0.y += *amount,
947      SignedAxis2::NegY => self.min.0.y -= *amount
948    }
949  }
950
951  pub fn with_z (self, z : Interval <S>) -> Aabb3 <S> where S : std::fmt::Debug {
952    Aabb3::with_minmax (
953      self.min.0.with_z (z.min()).into(),
954      self.max.0.with_z (z.max()).into()
955    )
956  }
957
958  /// Increase or decrease each dimension by the given amount.
959  ///
960  /// Debug panic if any dimension would become negative:
961  /// ```should_panic
962  /// # use math_utils::geometry::*;
963  /// let x = Aabb2::with_minmax ([0.0, 0.0].into(), [1.0, 2.0].into());
964  /// let y = x.dilate (-1.0); // panic!
965  /// ```
966  pub fn dilate (mut self, amount : S) -> Self where S : std::fmt::Debug {
967    self.min -= Vector2::broadcast (amount);
968    self.max += Vector2::broadcast (amount);
969    debug_assert_ne!(self.min, self.max);
970    debug_assert_eq!(self.min, point2_min (&self.min, &self.max));
971    debug_assert_eq!(self.max, point2_max (&self.min, &self.max));
972    self
973  }
974
975  /// Project *along* the given axis.
976  ///
977  /// For example, projecting *along* the X-axis projects *onto* the Y-axis.
978  pub fn project (self, axis : Axis2) -> Interval <S> where S : std::fmt::Debug {
979    let (min, max) = match axis {
980      Axis2::X => (self.min.0.y, self.max.0.y),
981      Axis2::Y => (self.min.0.x, self.max.0.x)
982    };
983    Interval::with_minmax (min, max)
984  }
985}
986
987impl <S : OrderedField> Primitive <S, Vector2 <S>> for Aabb2 <S> {
988  fn translate (mut self, displacement : Vector2 <S>) -> Self {
989    self.min.0 += displacement;
990    self.max.0 += displacement;
991    self
992  }
993  fn scale (mut self, scale : NonZero <S>) -> Self {
994    let center = self.center();
995    self = self.translate (-center.0);
996    self.min.0 *= *scale;
997    self.max.0 *= *scale;
998    self.translate (center.0)
999  }
1000}
1001
1002impl <S> std::ops::Add <Vector2 <S>> for Aabb2 <S> where
1003  S    : Field,
1004  Self : Primitive <S, Vector2 <S>>
1005{
1006  type Output = Self;
1007  #[expect(clippy::renamed_function_params)]
1008  fn add (self, displacement : Vector2 <S>) -> Self {
1009    self.translate (displacement)
1010  }
1011}
1012
1013impl <S> std::ops::Sub <Vector2 <S>> for Aabb2 <S> where
1014  S    : Field,
1015  Self : Primitive <S, Vector2 <S>>
1016{
1017  type Output = Self;
1018  #[expect(clippy::renamed_function_params)]
1019  fn sub (self, displacement : Vector2 <S>) -> Self {
1020    self.translate (-displacement)
1021  }
1022}
1023
1024impl_numcast_fields!(Aabb3, min, max);
1025impl <S : OrderedRing> Aabb3 <S> {
1026  /// Construct a new AABB with given min and max points.
1027  ///
1028  /// Debug panic if points are not min/max:
1029  ///
1030  /// ```should_panic
1031  /// # use math_utils::geometry::*;
1032  /// let b = Aabb3::with_minmax ([1.0, 1.0, 1.0].into(), [0.0, 0.0, 0.0].into());  // panic!
1033  /// ```
1034  ///
1035  /// Debug panic if points are identical:
1036  ///
1037  /// ```should_panic
1038  /// # use math_utils::geometry::*;
1039  /// let b = Aabb3::with_minmax ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 0.0].into());  // panic!
1040  /// ```
1041  #[inline]
1042  pub fn with_minmax (min : Point3 <S>, max : Point3 <S>) -> Self where
1043    S : std::fmt::Debug
1044  {
1045    debug_assert_ne!(min, max);
1046    debug_assert_eq!(min, point3_min (&min, &max));
1047    debug_assert_eq!(max, point3_max (&min, &max));
1048    Aabb3 { min, max }
1049  }
1050  /// Construct a new AABB using the two given points to determine min/max.
1051  ///
1052  /// Panic if points are identical:
1053  ///
1054  /// ```should_panic
1055  /// # use math_utils::geometry::*;
1056  /// let b = Aabb3::from_points (
1057  ///   [0.0, 0.0, 0.0].into(),
1058  ///   [0.0, 0.0, 0.0].into());  // panic!
1059  /// ```
1060  #[inline]
1061  pub fn from_points (a : Point3 <S>, b : Point3 <S>) -> Self where S : std::fmt::Debug {
1062    debug_assert_ne!(a, b);
1063    let min = point3_min (&a, &b);
1064    let max = point3_max (&a, &b);
1065    Aabb3 { min, max }
1066  }
1067  /// Construct the minimum AABB containing the given set of points.
1068  ///
1069  /// Debug panic if fewer than 2 points are given:
1070  ///
1071  /// ```should_panic
1072  /// # use math_utils::geometry::*;
1073  /// let b = Aabb3::<f32>::containing (&[]);  // panic!
1074  /// ```
1075  ///
1076  /// ```should_panic
1077  /// # use math_utils::geometry::*;
1078  /// let b = Aabb3::containing (&[[0.0, 0.0, 0.0].into()]);  // panic!
1079  /// ```
1080  #[inline]
1081  pub fn containing (points : &[Point3 <S>]) -> Self where S : std::fmt::Debug {
1082    debug_assert!(points.len() >= 2);
1083    let mut min = point3_min (&points[0], &points[1]);
1084    let mut max = point3_max (&points[0], &points[1]);
1085    for point in points.iter().skip (2) {
1086      min = point3_min (&min, point);
1087      max = point3_max (&max, point);
1088    }
1089    Aabb3::with_minmax (min, max)
1090  }
1091  /// Create a new AABB that is the union of the two input AABBs
1092  #[inline]
1093  pub fn union (a : &Aabb3 <S>, b : &Aabb3 <S>) -> Self where S : std::fmt::Debug {
1094    Aabb3::with_minmax (point3_min (a.min(), b.min()), point3_max (a.max(), b.max()))
1095  }
1096  #[inline]
1097  pub const fn min (&self) -> &Point3 <S> {
1098    &self.min
1099  }
1100  #[inline]
1101  pub const fn max (&self) -> &Point3 <S> {
1102    &self.max
1103  }
1104  #[inline]
1105  pub fn center (&self) -> Point3 <S> where S : Field {
1106    Point3 ((self.min.0 + self.max.0) / S::two())
1107  }
1108  #[inline]
1109  pub fn dimensions (&self) -> Vector3 <S> {
1110    self.max.0 - self.min.0
1111  }
1112  #[inline]
1113  pub fn extents (&self) -> Vector3 <S> where S : Field {
1114    self.dimensions() * S::half()
1115  }
1116  /// X dimension
1117  #[inline]
1118  pub fn width (&self) -> NonNegative <S> {
1119    NonNegative::unchecked (self.max.0.x - self.min.0.x)
1120  }
1121  /// Y dimension
1122  #[inline]
1123  pub fn depth (&self) -> NonNegative <S> {
1124    NonNegative::unchecked (self.max.0.y - self.min.0.y)
1125  }
1126  /// Z dimension
1127  #[inline]
1128  pub fn height (&self) -> NonNegative <S> {
1129    NonNegative::unchecked (self.max.0.z - self.min.0.z)
1130  }
1131  #[inline]
1132  pub fn volume (&self) -> NonNegative <S> {
1133    self.width() * self.depth() * self.height()
1134  }
1135  #[inline]
1136  pub fn contains (&self, point : &Point3 <S>) -> bool {
1137    self.min.0.x < point.0.x && point.0.x < self.max.0.x &&
1138    self.min.0.y < point.0.y && point.0.y < self.max.0.y &&
1139    self.min.0.z < point.0.z && point.0.z < self.max.0.z
1140  }
1141  /// Clamp a given point to the AABB.
1142  ///
1143  /// ```
1144  /// # use math_utils::geometry::*;
1145  /// let b = Aabb3::with_minmax ([-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into());
1146  /// assert_eq!(b.clamp (&[-2.0, 0.0, 0.0].into()), [-1.0, 0.0, 0.0].into());
1147  /// assert_eq!(b.clamp (&[ 2.0, 2.0, 0.0].into()), [ 1.0, 1.0, 0.0].into());
1148  /// assert_eq!(b.clamp (&[-1.0, 2.0, 3.0].into()), [-1.0, 1.0, 1.0].into());
1149  /// assert_eq!(b.clamp (&[-0.5, 0.5, 0.0].into()), [-0.5, 0.5, 0.0].into());
1150  /// ```
1151  pub fn clamp (&self, point : &Point3 <S>) -> Point3 <S> {
1152    [ S::max (S::min (self.max.0.x, point.0.x), self.min.0.x),
1153      S::max (S::min (self.max.0.y, point.0.y), self.min.0.y),
1154      S::max (S::min (self.max.0.z, point.0.z), self.min.0.z)
1155    ].into()
1156  }
1157  /// Generate a random point contained in the AABB
1158  ///
1159  /// ```
1160  /// # use rand;
1161  /// # use rand_xorshift;
1162  /// # use math_utils::geometry::*;
1163  /// # fn main () {
1164  /// use rand_xorshift;
1165  /// use rand::SeedableRng;
1166  /// // random sequence will be the same each time this is run
1167  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
1168  /// let aabb = Aabb3::<f32>::with_minmax (
1169  ///   [-10.0, -10.0, -10.0].into(),
1170  ///   [ 10.0,  10.0,  10.0].into());
1171  /// let point = aabb.rand_point (&mut rng);
1172  /// assert!(aabb.contains (&point));
1173  /// let point = aabb.rand_point (&mut rng);
1174  /// assert!(aabb.contains (&point));
1175  /// let point = aabb.rand_point (&mut rng);
1176  /// assert!(aabb.contains (&point));
1177  /// let point = aabb.rand_point (&mut rng);
1178  /// assert!(aabb.contains (&point));
1179  /// let point = aabb.rand_point (&mut rng);
1180  /// assert!(aabb.contains (&point));
1181  /// # }
1182  /// ```
1183  #[inline]
1184  pub fn rand_point <R> (&self, rng : &mut R) -> Point3 <S> where
1185    S : rand::distr::uniform::SampleUniform,
1186    R : rand::Rng
1187  {
1188    let x_range = self.min.0.x..self.max.0.x;
1189    let y_range = self.min.0.y..self.max.0.y;
1190    let z_range = self.min.0.z..self.max.0.z;
1191    let x = if x_range.is_empty() {
1192      self.min.0.x
1193    } else {
1194      rng.random_range (x_range)
1195    };
1196    let y = if y_range.is_empty() {
1197      self.min.0.y
1198    } else {
1199      rng.random_range (y_range)
1200    };
1201    let z = if z_range.is_empty() {
1202      self.min.0.z
1203    } else {
1204      rng.random_range (z_range)
1205    };
1206    [x, y, z].into()
1207  }
1208  #[inline]
1209  pub fn intersects (&self, other : &Aabb3 <S>) -> bool {
1210    intersect::discrete_aabb3_aabb3 (self, other)
1211  }
1212  #[inline]
1213  pub fn intersection (self, other : Aabb3 <S>) -> Option <Aabb3 <S>> where
1214    S : std::fmt::Debug
1215  {
1216    intersect::continuous_aabb3_aabb3 (&self, &other)
1217  }
1218
1219  pub fn corner (&self, octant : Octant) -> Point3 <S> {
1220    match octant {
1221      Octant::PosPosPos => self.max,
1222      Octant::NegPosPos => [self.min.0.x, self.max.0.y, self.max.0.z].into(),
1223      Octant::PosNegPos => [self.max.0.x, self.min.0.y, self.max.0.z].into(),
1224      Octant::NegNegPos => [self.min.0.x, self.min.0.y, self.max.0.z].into(),
1225      Octant::PosPosNeg => [self.max.0.x, self.max.0.y, self.min.0.z].into(),
1226      Octant::NegPosNeg => [self.min.0.x, self.max.0.y, self.min.0.z].into(),
1227      Octant::PosNegNeg => [self.max.0.x, self.min.0.y, self.min.0.z].into(),
1228      Octant::NegNegNeg => self.min
1229    }
1230  }
1231
1232  pub fn corners (&self) -> [Point3 <S>; 8] {
1233    [ self.min,
1234      [self.min.0.x, self.max.0.y, self.max.0.z].into(),
1235      [self.max.0.x, self.min.0.y, self.max.0.z].into(),
1236      [self.min.0.x, self.min.0.y, self.max.0.z].into(),
1237      [self.max.0.x, self.max.0.y, self.min.0.z].into(),
1238      [self.min.0.x, self.max.0.y, self.min.0.z].into(),
1239      [self.max.0.x, self.min.0.y, self.min.0.z].into(),
1240      self.max
1241    ]
1242  }
1243
1244  pub fn face (&self, direction : SignedAxis3) -> Self where S : std::fmt::Debug {
1245    let (min, max) = match direction {
1246      SignedAxis3::PosX => (
1247        self.corner (Octant::PosNegNeg),
1248        self.corner (Octant::PosPosPos) ),
1249      SignedAxis3::NegX => (
1250        self.corner (Octant::NegNegNeg),
1251        self.corner (Octant::NegPosPos) ),
1252      SignedAxis3::PosY => (
1253        self.corner (Octant::NegPosNeg),
1254        self.corner (Octant::PosPosPos) ),
1255      SignedAxis3::NegY => (
1256        self.corner (Octant::NegNegNeg),
1257        self.corner (Octant::PosNegPos) ),
1258      SignedAxis3::PosZ => (
1259        self.corner (Octant::NegNegPos),
1260        self.corner (Octant::PosPosPos) ),
1261      SignedAxis3::NegZ => (
1262        self.corner (Octant::NegNegNeg),
1263        self.corner (Octant::PosPosNeg) )
1264    };
1265    Aabb3::with_minmax (min, max)
1266  }
1267
1268  pub fn extrude (&self, axis : SignedAxis3, amount : Positive <S>) -> Self {
1269    let (min, max) = match axis {
1270      SignedAxis3::PosX => (
1271        self.min + Vector3::zero().with_x (*self.width()),
1272        self.max + Vector3::zero().with_x (*amount)
1273      ),
1274      SignedAxis3::NegX => (
1275        self.min - Vector3::zero().with_x (*amount),
1276        self.max - Vector3::zero().with_x (*self.width())
1277      ),
1278      SignedAxis3::PosY => (
1279        self.min + Vector3::zero().with_y (*self.depth()),
1280        self.max + Vector3::zero().with_y (*amount)
1281      ),
1282      SignedAxis3::NegY => (
1283        self.min - Vector3::zero().with_y (*amount),
1284        self.max - Vector3::zero().with_y (*self.depth())
1285      ),
1286      SignedAxis3::PosZ => (
1287        self.min + Vector3::zero().with_z (*self.height()),
1288        self.max + Vector3::zero().with_z (*amount)
1289      ),
1290      SignedAxis3::NegZ => (
1291        self.min - Vector3::zero().with_z (*amount),
1292        self.max - Vector3::zero().with_z (*self.height())
1293      )
1294    };
1295    Aabb3 { min, max }
1296  }
1297
1298  pub fn extend (&mut self, axis : SignedAxis3, amount : Positive <S>) {
1299    match axis {
1300      SignedAxis3::PosX => self.max.0.x += *amount,
1301      SignedAxis3::NegX => self.min.0.x -= *amount,
1302      SignedAxis3::PosY => self.max.0.y += *amount,
1303      SignedAxis3::NegY => self.min.0.y -= *amount,
1304      SignedAxis3::PosZ => self.max.0.z += *amount,
1305      SignedAxis3::NegZ => self.min.0.z -= *amount
1306    }
1307  }
1308
1309  /// Increase or decrease each dimension by the given amount.
1310  ///
1311  /// Debug panic if any dimension would become negative:
1312  /// ```should_panic
1313  /// # use math_utils::geometry::*;
1314  /// let x = Aabb3::with_minmax ([0.0, 0.0, 0.0].into(), [1.0, 2.0, 3.0].into());
1315  /// let y = x.dilate (-1.0); // panic!
1316  /// ```
1317  pub fn dilate (mut self, amount : S) -> Self where S : std::fmt::Debug {
1318    self.min -= Vector3::broadcast (amount);
1319    self.max += Vector3::broadcast (amount);
1320    debug_assert_ne!(self.min, self.max);
1321    debug_assert_eq!(self.min, point3_min (&self.min, &self.max));
1322    debug_assert_eq!(self.max, point3_max (&self.min, &self.max));
1323    self
1324  }
1325
1326  /// Project *along* the given axis.
1327  ///
1328  /// For example, projecting *along* the X-axis projects *onto* the YZ-plane.
1329  pub fn project (self, axis : Axis3) -> Aabb2 <S> where S : std::fmt::Debug {
1330    let (min, max) = match axis {
1331      Axis3::X => ([self.min.0.y, self.min.0.z], [self.max.0.y, self.max.0.z]),
1332      Axis3::Y => ([self.min.0.x, self.min.0.z], [self.max.0.x, self.max.0.z]),
1333      Axis3::Z => ([self.min.0.x, self.min.0.y], [self.max.0.x, self.max.0.y]),
1334    };
1335    Aabb2::with_minmax (min.into(), max.into())
1336  }
1337}
1338
1339impl <S : OrderedField> Primitive <S, Vector3 <S>> for Aabb3 <S> {
1340  fn translate (mut self, displacement : Vector3 <S>) -> Self {
1341    self.min.0 += displacement;
1342    self.max.0 += displacement;
1343    self
1344  }
1345  fn scale (mut self, scale : NonZero <S>) -> Self {
1346    let center = self.center();
1347    self = self.translate (-center.0);
1348    self.min.0 *= *scale;
1349    self.max.0 *= *scale;
1350    self.translate (center.0)
1351  }
1352}
1353
1354impl <S> std::ops::Add <Vector3 <S>> for Aabb3 <S> where
1355  S    : Field,
1356  Self : Primitive <S, Vector3 <S>>
1357{
1358  type Output = Self;
1359  #[expect(clippy::renamed_function_params)]
1360  fn add (self, displacement : Vector3 <S>) -> Self {
1361    self.translate (displacement)
1362  }
1363}
1364
1365impl <S> std::ops::Sub <Vector3 <S>> for Aabb3 <S> where
1366  S    : Field,
1367  Self : Primitive <S, Vector3 <S>>
1368{
1369  type Output = Self;
1370  #[expect(clippy::renamed_function_params)]
1371  fn sub (self, displacement : Vector3 <S>) -> Self {
1372    self.translate (-displacement)
1373  }
1374}
1375
1376impl_numcast_fields!(Obb2, center, half_extent_x, half_extent_y, orientation);
1377impl <S : Ring> Obb2 <S> {
1378  /// Construct a new OBB
1379  #[inline]
1380  pub const fn new (
1381    center        : Point2 <S>,
1382    half_extent_x : Positive <S>,
1383    half_extent_y : Positive <S>,
1384    orientation   : AngleWrapped <S>
1385  ) -> Self {
1386    Obb2 { center, half_extent_x, half_extent_y, orientation }
1387  }
1388  /// Compute the OBB of a given orientation
1389  pub fn containing_points_with_orientation (
1390    points : &[Point2 <S>], orientation : AngleWrapped <S>
1391  ) -> Self where
1392    S : Real + PartialOrd + num::Float + MaybeSerDes
1393  {
1394    let [x, y] = Rotation2::from_angle (orientation.angle()).into_col_arrays()
1395      .map (Vector2::from);
1396    let min_dot_x = -support2 (points, -x).1;
1397    let max_dot_x =  support2 (points,  x).1;
1398    let min_dot_y = -support2 (points, -y).1;
1399    let max_dot_y =  support2 (points,  y).1;
1400    let center = Point2 (
1401      (x * min_dot_x + x * max_dot_x) * S::half() +
1402      (y * min_dot_y + y * max_dot_y) * S::half());
1403    let half_extent_x = Positive::unchecked (S::half() * (max_dot_x - min_dot_x));
1404    let half_extent_y = Positive::unchecked (S::half() * (max_dot_y - min_dot_y));
1405    Obb2 { center, half_extent_x, half_extent_y, orientation }
1406  }
1407  /// Construct the minimum OBB containing the convex hull of a given set of points
1408  /// using rotating calipers algorithm. To construct the OBB containing a convex hull
1409  /// directoy use the `Obb2::containing` method.
1410  ///
1411  /// Returns None if fewer than 3 points are given or if points span an empty set:
1412  ///
1413  /// ```
1414  /// # use math_utils::*;
1415  /// # use math_utils::geometry::*;
1416  /// assert!(Obb2::containing_points (
1417  ///   &[[0.0, 1.0], [1.0, 0.0]].map (Point2::from)
1418  /// ).is_none());
1419  ///
1420  /// assert!(Obb2::containing_points (
1421  ///   &[[1.0, 1.0], [0.0, 0.0], [-1.0, -1.0]].map (Point2::from)
1422  /// ).is_none());
1423  /// ```
1424  pub fn containing_points (points : &[Point2 <S>]) -> Option <Self> where S : Real {
1425    let hull = Hull2::from_points (points)?;
1426    Self::containing (&hull)
1427  }
1428  /// Construct the minimum OBB containing the given convex hull of points using
1429  /// rotating calipers algorithm.
1430  ///
1431  /// Returns None if fewer than 3 points are given or if points span an empty set:
1432  ///
1433  /// ```
1434  /// # use math_utils::*;
1435  /// # use math_utils::geometry::*;
1436  /// let hull = Hull2::from_points (
1437  ///   &[[0.0, 1.0], [1.0, 0.0]].map (Point2::from)).unwrap();
1438  /// assert!(Obb2::containing (&hull).is_none());
1439  ///
1440  /// let hull = Hull2::from_points (
1441  ///   &[[1.0, 1.0], [0.0, 0.0], [-1.0, -1.0]].map (Point2::from)).unwrap();
1442  /// assert!(Obb2::containing (&hull).is_none());
1443  /// ```
1444  // code follows GeometricTools:
1445  // <https://github.com/davideberly/GeometricTools/blob/f78dd0b65bc3a0a4723586de6991dd2c339b08ad/GTE/Mathematics/MinimumAreaBox2.h>
1446  pub fn containing (hull : &Hull2 <S>) -> Option <Self> where S : Real {
1447    let points = hull.points();
1448    if points.len() < 3 {
1449      return None
1450    }
1451    let mut visited  = vec![false; points.len()];
1452    #[expect(clippy::cast_possible_truncation)]
1453    let mut min_rect = smallest_rect (points.len() as u32 - 1, 0, hull);
1454    visited[min_rect.indices[0] as usize] = true;
1455    // rotating calipers
1456    let mut rect = min_rect.clone();
1457    for _ in 0..points.len() {
1458      let mut angles  = [(S::zero(), u32::MAX); 4];
1459      let mut nangles = u32::MAX;
1460      if !compute_angles (hull, &rect, &mut angles, &mut nangles) {
1461        break
1462      }
1463      let sorted = sort_angles (&angles, nangles);
1464      if !update_support (&angles, nangles, &sorted, hull, &mut visited, &mut rect) {
1465        break
1466      }
1467      if rect.area < min_rect.area {
1468        min_rect = rect.clone();
1469      }
1470    }
1471    // construct obb from min rect
1472    let sum = [
1473      points[min_rect.indices[1] as usize].0 + points[min_rect.indices[3] as usize].0,
1474      points[min_rect.indices[2] as usize].0 + points[min_rect.indices[0] as usize].0
1475    ];
1476    let diff = [
1477      points[min_rect.indices[1] as usize].0 - points[min_rect.indices[3] as usize].0,
1478      points[min_rect.indices[2] as usize].0 - points[min_rect.indices[0] as usize].0
1479    ];
1480    let center = Point2::from ((min_rect.edge * min_rect.edge.dot (sum[0]) +
1481      min_rect.perp * min_rect.perp.dot (sum[1]))
1482      * S::half() / min_rect.edge_len_squared);
1483    let half_extent_x = Positive::unchecked (S::sqrt (
1484      (S::half() * min_rect.edge.dot (diff[0])).squared()
1485      / min_rect.edge_len_squared));
1486    let half_extent_y = Positive::unchecked (S::sqrt (
1487      (S::half() * min_rect.perp.dot (diff[1])).squared()
1488      / min_rect.edge_len_squared));
1489    let orientation = AngleWrapped::wrap (Rad (min_rect.edge.y.atan2 (min_rect.edge.x)));
1490    let obb = Obb2 { center, half_extent_x, half_extent_y, orientation };
1491    fn compute_angles <S : Real> (
1492      hull    : &Hull2 <S>,
1493      rect    : &Rect <S>,
1494      angles  : &mut [(S, u32); 4],
1495      nangles : &mut u32
1496    ) -> bool {
1497      *nangles = 0;
1498      let points = hull.points();
1499      let mut k0 = 3;
1500      let mut k1 = 0;
1501      while k1 < 4 {
1502        if rect.indices[k0] != rect.indices[k1] {
1503          let d = {
1504            let mut d = if k0 & 1 != 0 {
1505              rect.perp
1506            } else {
1507              rect.edge
1508            };
1509            if k0 & 2 != 0 {
1510              d = -d;
1511            }
1512            d
1513          };
1514          let j0 = rect.indices[k0];
1515          let mut j1 = j0 + 1;
1516          #[expect(clippy::cast_possible_truncation)]
1517          if j1 == points.len() as u32 {
1518            j1 = 0;
1519          }
1520          let e = points[j1 as usize] - points[j0 as usize];
1521          let dp = d.dot ([e.y, -e.x].into());
1522          let e_lensq = e.norm_squared();
1523          let sin_theta_squared = (dp * dp) / e_lensq;
1524          angles[*nangles as usize] =
1525            #[expect(clippy::cast_possible_truncation)]
1526            (sin_theta_squared, k0 as u32);
1527          *nangles += 1;
1528        }
1529        k0 = k1;
1530        k1 += 1;
1531      }
1532      *nangles > 0
1533    }
1534    fn sort_angles <S : PartialOrd> (angles : &[(S, u32); 4], nangles : u32)
1535      -> [u32; 4]
1536    {
1537      let mut sorted = [0u32, 1, 2, 3];
1538      match nangles {
1539        0 | 1 => {}
1540        2 => if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
1541          sorted.swap (0, 1)
1542        }
1543        3 => {
1544          if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
1545            sorted.swap (0, 1)
1546          }
1547          if angles[sorted[0] as usize].0 > angles[sorted[2] as usize].0 {
1548            sorted.swap (0, 2)
1549          }
1550          if angles[sorted[1] as usize].0 > angles[sorted[2] as usize].0 {
1551            sorted.swap (1, 2)
1552          }
1553        }
1554        4 => {
1555          if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
1556            sorted.swap (0, 1)
1557          }
1558          if angles[sorted[2] as usize].0 > angles[sorted[3] as usize].0 {
1559            sorted.swap (2, 3)
1560          }
1561          if angles[sorted[0] as usize].0 > angles[sorted[2] as usize].0 {
1562            sorted.swap (0, 2)
1563          }
1564          if angles[sorted[1] as usize].0 > angles[sorted[3] as usize].0 {
1565            sorted.swap (1, 3)
1566          }
1567          if angles[sorted[1] as usize].0 > angles[sorted[2] as usize].0 {
1568            sorted.swap (1, 2)
1569          }
1570        }
1571        _ => unreachable!()
1572      }
1573      sorted
1574    }
1575    fn update_support <S : Real> (
1576      angles  : &[(S, u32); 4],
1577      nangles : u32,
1578      sorted  : &[u32; 4],
1579      hull    : &Hull2 <S>,
1580      visited : &mut [bool],
1581      rect    : &mut Rect <S>
1582    ) -> bool {
1583      let points = hull.points();
1584      let min_angle = angles[sorted[0] as usize];
1585      for k in 0..nangles as usize {
1586        let (angle, index) = angles[sorted[k] as usize];
1587        if angle == min_angle.0 {
1588          rect.indices[index as usize] += 1;
1589          #[expect(clippy::cast_possible_truncation)]
1590          if rect.indices[index as usize] == points.len() as u32 {
1591            rect.indices[index as usize] = 0;
1592          }
1593        }
1594      }
1595      let bottom = rect.indices[min_angle.1 as usize];
1596      if visited[bottom as usize] {
1597        return false
1598      }
1599      visited[bottom as usize] = true;
1600      let mut next_index = [u32::MAX; 4];
1601      for k in 0u32..4 {
1602        next_index[k as usize] = rect.indices[((min_angle.1 + k) % 4) as usize];
1603      }
1604      rect.indices = next_index;
1605      let j1 = rect.indices[0] as usize;
1606      let j0 = if j1 == 0 {
1607        points.len() - 1
1608      } else {
1609        j1 - 1
1610      };
1611      rect.edge = points[j1] - points[j0];
1612      rect.perp = vector2 (-rect.edge.y, rect.edge.x);
1613      let edge_len_squared = rect.edge.norm_squared();
1614      let diff = [
1615        points[rect.indices[1] as usize] - points[rect.indices[3] as usize],
1616        points[rect.indices[2] as usize] - points[rect.indices[0] as usize]
1617      ];
1618      rect.area = rect.edge.dot (diff[0]) * rect.perp.dot (diff[1]) / edge_len_squared;
1619      true
1620    }
1621    Some (obb)
1622  }
1623  #[inline]
1624  pub const fn center (&self) -> Point2 <S> {
1625    self.center
1626  }
1627  #[inline]
1628  pub const fn half_extent_x (&self) -> Positive <S> {
1629    self.half_extent_x
1630  }
1631  #[inline]
1632  pub const fn half_extent_y (&self) -> Positive <S> {
1633    self.half_extent_y
1634  }
1635  #[inline]
1636  pub const fn orientation (&self) -> AngleWrapped <S> {
1637    self.orientation
1638  }
1639  pub fn dimensions (&self) -> Vector2 <S> where S : Field {
1640    self.extents() * S::two()
1641  }
1642  pub fn extents (&self) -> Vector2 <S> {
1643    [*self.half_extent_x, *self.half_extent_y].into()
1644  }
1645  /// X dimension
1646  #[inline]
1647  pub fn width (&self) -> Positive <S> where S : OrderedField {
1648    self.half_extent_x * Positive::unchecked (S::two())
1649  }
1650  /// Y dimension
1651  #[inline]
1652  pub fn height (&self) -> Positive <S> where S : OrderedField {
1653    self.half_extent_y * Positive::unchecked (S::two())
1654  }
1655  #[inline]
1656  pub fn area (&self) -> Positive <S> where S : OrderedField {
1657    self.width() * self.height()
1658  }
1659  #[inline]
1660  pub fn corners (&self) -> [Point2 <S>; 4] where
1661    S : Real + num::real::Real + MaybeSerDes
1662  {
1663    let extents = self.extents();
1664    let mut points = [Point2::origin(); 4];
1665    for i in 0..2 {
1666      for j in 0..2 {
1667        let sign_x = if i % 2 == 0 { -S::one() } else { S::one() };
1668        let sign_y = if j % 2 == 0 { -S::one() } else { S::one() };
1669        points[i * 2 + j] = [extents.x * sign_x, extents.y * sign_y].into();
1670      }
1671    }
1672    let rotation = Rotation2::from_angle (self.orientation.angle());
1673    points.map (|point| rotation.rotate (point) + self.center.0)
1674  }
1675
1676  /// Check if point is contained by the bounding box.
1677  ///
1678  /// ```
1679  /// # use math_utils::*;
1680  /// # use math_utils::geometry::*;
1681  /// let x = Obb2::new (
1682  ///   [3.0, 6.0].into(),
1683  ///   Positive::unchecked (2.0),
1684  ///   Positive::unchecked (0.75),
1685  ///   AngleWrapped::wrap (Rad (std::f32::consts::FRAC_PI_4)));
1686  /// assert!(x.contains (&[2.0f32, 5.0].into()));
1687  /// assert!(!x.contains (&[3.0f32, 2.0].into()));
1688  /// ```
1689  pub fn contains (&self, point : &Point2 <S>) -> bool where
1690    S : Real + num::real::Real + MaybeSerDes
1691  {
1692    // re-center
1693    let p = point - self.center().0;
1694    // reverse rotation
1695    let p = Rotation2::from_angle (-self.orientation().angle()).rotate (p);
1696    p.0.x.abs() < *self.half_extent_x && p.0.y.abs() < *self.half_extent_y
1697  }
1698
1699  /// Increase or decrease each dimension by the given amount.
1700  ///
1701  /// Panics if any dimension would become zero or negative:
1702  ///
1703  /// ```should_panic
1704  /// # use math_utils::*;
1705  /// # use math_utils::geometry::*;
1706  /// # use math_utils::num_traits::Zero;
1707  /// let x = Obb2::new (
1708  ///   Point2::origin(),
1709  ///   Positive::unchecked (0.5),
1710  ///   Positive::unchecked (0.5),
1711  ///   AngleWrapped::zero()
1712  /// );
1713  /// let y = x.dilate (-1.0); // panic!
1714  /// ```
1715  pub fn dilate (mut self, amount : S) -> Self where S : OrderedField {
1716    self.half_extent_x = Positive::noisy (*self.half_extent_x + amount);
1717    self.half_extent_y = Positive::noisy (*self.half_extent_y + amount);
1718    self
1719  }
1720}
1721impl <S : Real> From <Aabb2 <S>> for Obb2 <S> {
1722  /// Panics if AABB has a zero dimension:
1723  ///
1724  /// ```should_panic
1725  /// # use math_utils::*;
1726  /// # use math_utils::geometry::*;
1727  /// # use math_utils::num_traits::Zero;
1728  /// let x = Aabb2::with_minmax (
1729  ///   [0.0, 0.0].into(),
1730  ///   [0.0, 1.0].into()
1731  /// );
1732  /// let y = Obb2::from (x); // panic!
1733  /// ```
1734  fn from (aabb : Aabb2 <S>) -> Self {
1735    use num::Zero;
1736    let center        = aabb.center();
1737    let half_extent_x = Positive::noisy (*aabb.width()  / S::two());
1738    let half_extent_y = Positive::noisy (*aabb.height() / S::two());
1739    let orientation   = AngleWrapped::zero();
1740    Obb2::new (center, half_extent_x, half_extent_y, orientation)
1741  }
1742}
1743
1744impl_numcast_fields!(Obb3, center, half_extent_x, half_extent_y, half_extent_z,
1745  orientation);
1746impl <S : Ring> Obb3 <S> {
1747  /// Construct a new OBB
1748  #[inline]
1749  pub const fn new (
1750    center        : Point3 <S>,
1751    half_extent_x : Positive <S>,
1752    half_extent_y : Positive <S>,
1753    half_extent_z : Positive <S>,
1754    orientation   : Angles3 <S>
1755  ) -> Self {
1756    Obb3 { center, half_extent_x, half_extent_y, half_extent_z, orientation }
1757  }
1758  /// Compute the OBB of a given orientation
1759  pub fn containing_points_with_orientation (
1760    points : &[Point3 <S>], orientation : Angles3 <S>
1761  ) -> Self where
1762    S : Real + num::real::Real + MaybeSerDes
1763  {
1764    let [x, y, z] = Rotation3::from (orientation).into_col_arrays().map (Vector3::from);
1765    let min_dot_x = -support3 (points, -x).1;
1766    let max_dot_x =  support3 (points,  x).1;
1767    let min_dot_y = -support3 (points, -y).1;
1768    let max_dot_y =  support3 (points,  y).1;
1769    let min_dot_z = -support3 (points, -z).1;
1770    let max_dot_z =  support3 (points,  z).1;
1771    let center = Point3 (
1772      (x * min_dot_x + x * max_dot_x) * S::half() +
1773      (y * min_dot_y + y * max_dot_y) * S::half() +
1774      (z * min_dot_z + z * max_dot_z) * S::half());
1775    let half_extent_x = Positive::unchecked (S::half() * (max_dot_x - min_dot_x));
1776    let half_extent_y = Positive::unchecked (S::half() * (max_dot_y - min_dot_y));
1777    let half_extent_z = Positive::unchecked (S::half() * (max_dot_z - min_dot_z));
1778    Obb3 { center, half_extent_x, half_extent_y, half_extent_z, orientation }
1779  }
1780  /// Construct an approximate  minimum OBB containing the convex hull of a given set of
1781  /// points.
1782  ///
1783  /// Returns None if fewer than 4 points are given or if points span an empty set:
1784  /// ```
1785  /// # use math_utils::*;
1786  /// # use math_utils::geometry::*;
1787  /// assert!(Obb3::containing_points_approx (
1788  ///   &[[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]].map (Point3::from)
1789  /// ).is_none());
1790  ///
1791  /// assert!(Obb3::containing_points_approx (
1792  ///   &[[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [-1.0,  0.0, -1.0]]
1793  ///     .map (Point3::from)
1794  /// ).is_none());
1795  /// ```
1796  pub fn containing_points_approx (points : &[Point3 <S>]) -> Option <Self> where
1797    S : Real + num::real::Real + approx::RelativeEq <Epsilon=S> + std::fmt::Debug
1798      + MaybeSerDes,
1799  {
1800    if points.len() < 4 {
1801      return None
1802    }
1803    let (hull, mesh) = Hull3::from_points_with_mesh (points)?;
1804    Self::containing_approx (&hull, &mesh)
1805  }
1806  /// Construct an approximate minimum OBB containing the given convex hull and
1807  /// corresponding mesh.
1808  ///
1809  /// Returns None if fewer than 4 points are given or if points span an empty set:
1810  /// ```
1811  /// # use math_utils::*;
1812  /// # use math_utils::geometry::*;
1813  /// let (hull, mesh) = Hull3::from_points_with_mesh (
1814  ///   &[[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]].map (Point3::from)
1815  /// ).unwrap();
1816  /// assert!(Obb3::containing_approx (&hull, &mesh).is_none());
1817  ///
1818  /// let (hull, mesh) = Hull3::from_points_with_mesh (
1819  ///   &[[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [-1.0,  0.0, -1.0]]
1820  ///     .map (Point3::from)
1821  /// ).unwrap();
1822  /// assert!(Obb3::containing_approx (&hull, &mesh).is_none());
1823  /// ```
1824  pub fn containing_approx (hull : &Hull3 <S>, mesh : &VertexEdgeTriangleMesh)
1825    -> Option <Self>
1826  where
1827    S : Real + num::NumCast + approx::RelativeEq <Epsilon=S> + std::fmt::Debug
1828      + MaybeSerDes
1829  {
1830    // algorithm from:
1831    // <https://github.com/isl-org/Open3D/blob/fb7088ceebef38d54c575b47935e568979b50954/cpp/open3d/t/geometry/kernel/MinimumOBB.cpp>
1832    use num::Inv;
1833    if hull.points().len() < 4 {
1834      return None
1835    }
1836    let mut min_obb : Obb3 <S> = Aabb3::containing (hull.points()).into();
1837    let mut min_vol = min_obb.volume();
1838    for triangle in mesh.triangles().values() {
1839      let [a, b, c] = triangle.vertices().map (|i| hull.points()[i as usize]);
1840      let mut u = b - a;
1841      let mut v = c - a;
1842      let mut w = u.cross (v);
1843      v = w.cross (u);
1844      u = u.normalize();
1845      v = v.normalize();
1846      w = w.normalize();
1847      let m = Matrix3::from_row_arrays (std::array::from_fn (|i| [u[i], v[i], w[i]]));
1848      let m_rot = Rotation3::new_approx (m).unwrap();
1849      let rot_inv = m_rot.inv();
1850      let center  = a;
1851      let rotated = hull.points().iter().copied()
1852        .map (|p| rot_inv.rotate_around (p, center))
1853        .collect::<Vec <_>>();
1854      let aabb   = Aabb3::containing (rotated.as_slice());
1855      let aabb_volume = aabb.volume();
1856      if approx::abs_diff_eq!(*aabb_volume, S::zero()) {
1857        // hull contains no volume
1858        return None
1859      }
1860      let volume = Positive::new (*aabb_volume).unwrap();
1861      if volume < min_vol {
1862        min_vol = volume;
1863        min_obb = aabb.into();
1864        min_obb.orientation = m_rot.into();
1865        min_obb.center = m_rot.rotate_around (min_obb.center, center);
1866      }
1867    }
1868    Some (min_obb)
1869  }
1870  /// Construct an approximate minimum OBB containing the given convex hull of points
1871  /// oriented according to principal component analysis bases.
1872  ///
1873  /// Returns None if fewer than 4 points are given or if points span an empty set:
1874  /// ```
1875  /// # use math_utils::*;
1876  /// # use math_utils::geometry::*;
1877  /// let hull = Hull3::from_points (
1878  ///   &[[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]].map (Point3::from)
1879  /// ).unwrap();
1880  /// assert!(Obb3::containing_approx_pca (&hull).is_none());
1881  ///
1882  /// let hull = Hull3::from_points (
1883  ///   &[[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [-1.0,  0.0, -1.0]]
1884  ///     .map (Point3::from)
1885  /// ).unwrap();
1886  /// assert!(Obb3::containing_approx_pca (&hull).is_none());
1887  /// ```
1888  pub fn containing_approx_pca (hull : &Hull3 <S>) -> Option <Self> where
1889    S : Real + num::real::Real + num::NumCast + approx::RelativeEq <Epsilon=S>
1890      + std::fmt::Debug + MaybeSerDes
1891  {
1892    if hull.points().len() < 4 {
1893      return None
1894    }
1895    // coplanar check
1896    // TODO: should we store dimensionality information in the hull to avoid doing this
1897    // calculation ?
1898    let [p1, p2, p3] = &hull.points()[..3] else { unreachable!() };
1899    let mut coplanar = true;
1900    for p in &hull.points()[3..] {
1901      if !coplanar_3d (p1, p2, p3, p) {
1902        coplanar = false;
1903        break
1904      }
1905    }
1906    if coplanar {
1907      return None
1908    }
1909    // do PCA analysis of points
1910    let orientation = data::pca_3 (hull.points()).into();
1911    Some (Self::containing_points_with_orientation (hull.points(), orientation))
1912  }
1913  #[inline]
1914  pub const fn center (&self) -> Point3 <S> {
1915    self.center
1916  }
1917  #[inline]
1918  pub const fn half_extent_x (&self) -> Positive <S> {
1919    self.half_extent_x
1920  }
1921  #[inline]
1922  pub const fn half_extent_y (&self) -> Positive <S> {
1923    self.half_extent_y
1924  }
1925  #[inline]
1926  pub const fn half_extent_z (&self) -> Positive <S> {
1927    self.half_extent_z
1928  }
1929  #[inline]
1930  pub const fn orientation (&self) -> Angles3 <S> {
1931    self.orientation
1932  }
1933  pub fn dimensions (&self) -> Vector3 <S> where S : Field {
1934    self.extents() * S::two()
1935  }
1936  pub fn extents (&self) -> Vector3 <S> where S : Field {
1937    [*self.half_extent_x, *self.half_extent_y, *self.half_extent_z].into()
1938  }
1939  /// X dimension
1940  #[inline]
1941  pub fn width (&self) -> Positive <S> where S : OrderedField {
1942    self.half_extent_x * Positive::unchecked (S::two())
1943  }
1944  /// Y dimension
1945  #[inline]
1946  pub fn depth (&self) -> Positive <S> where S : OrderedField {
1947    self.half_extent_y * Positive::unchecked (S::two())
1948  }
1949  /// Z dimension
1950  #[inline]
1951  pub fn height (&self) -> Positive <S> where S : OrderedField {
1952    self.half_extent_z * Positive::unchecked (S::two())
1953  }
1954  #[inline]
1955  pub fn volume (&self) -> Positive <S> where S : OrderedField {
1956    self.width() * self.depth() * self.height()
1957  }
1958  #[inline]
1959  pub fn corners (&self) -> [Point3 <S>; 8] where
1960    S : Real + num::real::Real + MaybeSerDes
1961  {
1962    let extents = self.extents();
1963    let mut points = [Point3::origin(); 8];
1964    for i in 0..2 {
1965      for j in 0..2 {
1966        for k in 0..2 {
1967          let sign_x = if i % 2 == 0 { -S::one() } else { S::one() };
1968          let sign_y = if j % 2 == 0 { -S::one() } else { S::one() };
1969          let sign_z = if k % 2 == 0 { -S::one() } else { S::one() };
1970          points[i * 4 + j * 2 + k] =
1971            [extents.x * sign_x, extents.y * sign_y, extents.z * sign_z].into();
1972        }
1973      }
1974    }
1975    let rotation = Rotation3::from (self.orientation);
1976    points.map (|point| rotation.rotate (point) + self.center.0)
1977  }
1978
1979  /// Check if point is contained by the bounding box
1980  pub fn contains (&self, point : &Point3 <S>) -> bool where
1981    S : Real + num::real::Real + MaybeSerDes
1982  {
1983    use num::Inv;
1984    // re-center
1985    let p = point - self.center().0;
1986    // reverse rotation
1987    let p = Rotation3::from (self.orientation()).inv().rotate (p);
1988    p.0.x.abs() < *self.half_extent_x &&
1989    p.0.y.abs() < *self.half_extent_y &&
1990    p.0.z.abs() < *self.half_extent_z
1991  }
1992
1993  /// Increase or decrease each dimension by the given amount.
1994  ///
1995  /// Panics if any dimension would become zero or negative:
1996  ///
1997  /// ```should_panic
1998  /// # use math_utils::*;
1999  /// # use math_utils::geometry::*;
2000  /// let x = Obb3::new (
2001  ///   Point3::origin(),
2002  ///   Positive::unchecked (0.5),
2003  ///   Positive::unchecked (0.5),
2004  ///   Positive::unchecked (0.5),
2005  ///   Angles3::zero()
2006  /// );
2007  /// let y = x.dilate (-1.0); // panic!
2008  /// ```
2009  pub fn dilate (mut self, amount : S) -> Self where S : OrderedField {
2010    self.half_extent_x = Positive::noisy (*self.half_extent_x + amount);
2011    self.half_extent_y = Positive::noisy (*self.half_extent_y + amount);
2012    self.half_extent_z = Positive::noisy (*self.half_extent_z + amount);
2013    self
2014  }
2015}
2016impl <S : Real> From <Aabb3 <S>> for Obb3 <S> {
2017  /// Panics if AABB has a zero dimension:
2018  ///
2019  /// ```should_panic
2020  /// # use math_utils::*;
2021  /// # use math_utils::geometry::*;
2022  /// # use math_utils::num_traits::Zero;
2023  /// let x = Aabb3::with_minmax (
2024  ///   [0.0, 0.0, 0.0].into(),
2025  ///   [0.0, 1.0, 1.0].into()
2026  /// );
2027  /// let y = Obb3::from (x); // panic!
2028  /// ```
2029  fn from (aabb : Aabb3 <S>) -> Self {
2030    let center = aabb.center();
2031    let [half_extent_x, half_extent_y, half_extent_z] =
2032      aabb.extents().into_array().map (Positive::noisy);
2033    let orientation = Angles3::zero();
2034    Obb3::new (center, half_extent_x, half_extent_y, half_extent_z, orientation)
2035  }
2036}
2037
2038impl_numcast_fields!(Capsule3, center, half_height, radius);
2039impl <S : OrderedRing> Capsule3 <S> {
2040  pub fn aabb3 (&self) -> Aabb3 <S> where S : Real + std::fmt::Debug {
2041    use shape::Aabb;
2042    let shape_aabb = shape::Capsule {
2043      radius:      self.radius,
2044      half_height: self.half_height
2045    }.aabb();
2046    let center_vec = self.center.0;
2047    let min        = *shape_aabb.min() + center_vec;
2048    let max        = *shape_aabb.max() + center_vec;
2049    Aabb3::with_minmax (min, max)
2050  }
2051  /// Return the (upper sphere, cylinder, lower sphere) making up this capsule
2052  pub fn decompose (&self) -> (Sphere3 <S>, Option <Cylinder3 <S>>, Sphere3 <S>) {
2053    let cylinder = if *self.half_height > S::zero() {
2054      Some (Cylinder3 {
2055        center:      self.center,
2056        radius:      self.radius,
2057        half_height: Positive::unchecked (*self.half_height)
2058      })
2059    } else {
2060      None
2061    };
2062    let upper_sphere = Sphere3 {
2063      center: self.center + (Vector3::unit_z() * *self.half_height),
2064      radius: self.radius
2065    };
2066    let lower_sphere = Sphere3 {
2067      center: self.center - (Vector3::unit_z() * *self.half_height),
2068      radius: self.radius
2069    };
2070    (upper_sphere, cylinder, lower_sphere)
2071  }
2072}
2073
2074// TODO: impl Primitive for Capsule3
2075
2076impl_numcast_fields!(Cylinder3, center, half_height, radius);
2077impl <S : Ring> Cylinder3 <S> {
2078  pub fn aabb3 (&self) -> Aabb3 <S> where S : Real + std::fmt::Debug {
2079    use shape::Aabb;
2080    let shape_aabb = shape::Cylinder::unchecked (*self.radius, *self.half_height).aabb();
2081    let center_vec = self.center.0;
2082    let min        = *shape_aabb.min() + center_vec;
2083    let max        = *shape_aabb.max() + center_vec;
2084    Aabb3::with_minmax (min, max)
2085  }
2086}
2087
2088// TODO: impl Primitive for Cylinder3
2089
2090impl_numcast_fields!(Line2, base, direction);
2091impl <S : Real> Line2 <S> {
2092  /// Construct a new 2D line
2093  #[inline]
2094  pub const fn new (base : Point2 <S>, direction : Unit2 <S>) -> Self {
2095    Line2 { base, direction }
2096  }
2097  #[inline]
2098  pub fn point (&self, t : S) -> Point2 <S> {
2099    self.base + *self.direction * t
2100  }
2101  #[inline]
2102  #[expect(clippy::type_complexity)]
2103  pub fn intersect_aabb (&self, aabb : &Aabb2 <S>)
2104    -> Option <((S, Point2 <S>), (S, Point2 <S>))>
2105  where S : std::fmt::Debug {
2106    intersect::continuous_line2_aabb2 (self, aabb)
2107  }
2108  #[inline]
2109  #[expect(clippy::type_complexity)]
2110  pub fn intersect_sphere (&self, sphere : &Sphere2 <S>)
2111    -> Option <((S, Point2 <S>), (S, Point2 <S>))>
2112  {
2113    intersect::continuous_line2_sphere2 (self, sphere)
2114  }
2115}
2116impl <S : Real> Default for Line2 <S> {
2117  fn default() -> Self {
2118    Line2 {
2119      base:      Point2::origin(),
2120      direction: Unit2::axis_y()
2121    }
2122  }
2123}
2124
2125// TODO: impl Primitive for Line2
2126
2127impl_numcast_fields!(Line3, base, direction);
2128impl <S : Real> Line3 <S> {
2129  /// Consructs a new 3D line with given base point and direction vector
2130  #[inline]
2131  pub const fn new (base : Point3 <S>, direction : Unit3 <S>) -> Self {
2132    Line3 { base, direction }
2133  }
2134  #[inline]
2135  pub fn point (&self, t : S) -> Point3 <S> {
2136    self.base + *self.direction * t
2137  }
2138  #[inline]
2139  pub fn intersect_plane (&self, plane : &Plane3 <S>) -> Option <(S, Point3 <S>)> where
2140    S : approx::RelativeEq
2141  {
2142    intersect::continuous_line3_plane3 (self, plane)
2143  }
2144  #[inline]
2145  #[expect(clippy::type_complexity)]
2146  pub fn intersect_aabb (&self, aabb : &Aabb3 <S>)
2147    -> Option <((S, Point3 <S>), (S, Point3 <S>))>
2148  where S : num::Float + approx::RelativeEq <Epsilon=S> + std::fmt::Debug {
2149    intersect::continuous_line3_aabb3 (self, aabb)
2150  }
2151  #[inline]
2152  #[expect(clippy::type_complexity)]
2153  pub fn intersect_sphere (&self, sphere : &Sphere3 <S>)
2154    -> Option <((S, Point3 <S>), (S, Point3 <S>))>
2155  {
2156    intersect::continuous_line3_sphere3 (self, sphere)
2157  }
2158}
2159impl <S : Real> Default for Line3 <S> {
2160  fn default() -> Self {
2161    Line3 {
2162      base:      Point3::origin(),
2163      direction: Unit3::axis_z()
2164    }
2165  }
2166}
2167
2168// TODO: impl Primitive for Line3
2169
2170impl_numcast_fields!(Plane3, base, normal);
2171impl <S : Real> Plane3 <S> {
2172  /// Consructs a new 3D plane with given base point and normal vector
2173  #[inline]
2174  pub const fn new (base : Point3 <S>, normal : Unit3 <S>) -> Self {
2175    Plane3 { base, normal }
2176  }
2177}
2178impl <S : Real> Default for Plane3 <S> {
2179  fn default() -> Self {
2180    Plane3 {
2181      base:   Point3::origin(),
2182      normal: Unit3::axis_z()
2183    }
2184  }
2185}
2186
2187// TODO: impl Primitive for Plane3
2188
2189impl_numcast_fields!(Sphere2, center, radius);
2190impl <S : OrderedRing> Sphere2 <S> {
2191  /// Unit circle
2192  #[inline]
2193  pub fn unit() -> Self where S : Field {
2194    Sphere2 {
2195      center: Point2::origin(),
2196      radius: num::One::one()
2197    }
2198  }
2199  /// Discrete intersection test with another sphere
2200  #[inline]
2201  pub fn intersects (&self, other : &Self) -> bool {
2202    intersect::discrete_sphere2_sphere2 (self, other)
2203  }
2204}
2205
2206// TODO: impl Primitive for Sphere2
2207
2208impl_numcast_fields!(Sphere3, center, radius);
2209impl <S : OrderedRing> Sphere3 <S> {
2210  /// Unit sphere
2211  #[inline]
2212  pub fn unit() -> Self where S : Field {
2213    Sphere3 {
2214      center: Point3::origin(),
2215      radius: num::One::one()
2216    }
2217  }
2218  /// Discrete intersection test with another sphere
2219  #[inline]
2220  pub fn intersects (&self, other : &Self) -> bool {
2221    intersect::discrete_sphere3_sphere3 (self, other)
2222  }
2223}
2224
2225// TODO: impl Primitive for Sphere3
2226
2227////////////////////////////////////////////////////////////////////////////////////////
2228//  tests                                                                             //
2229////////////////////////////////////////////////////////////////////////////////////////
2230
2231#[cfg(test)]
2232mod tests {
2233  use approx::{assert_relative_eq, assert_ulps_eq};
2234  use sorted_vec::partial::SortedSet;
2235  use super::*;
2236
2237  #[test]
2238  fn project_2d_point_on_line() {
2239    use super::project_2d_point_on_line;
2240    use Unit2;
2241    let point : Point2 <f64> = [2.0, 2.0].into();
2242    let line = Line2::<f64>::new ([0.0, 0.0].into(), Unit2::axis_x());
2243    assert_eq!(project_2d_point_on_line (&point, &line), [2.0, 0.0].into());
2244    let line = Line2::<f64>::new ([0.0, 0.0].into(), Unit2::axis_y());
2245    assert_eq!(project_2d_point_on_line (&point, &line), [0.0, 2.0].into());
2246    let point : Point2 <f64> = [0.0, 1.0].into();
2247    let line = Line2::<f64>::new (
2248      [0.0, -1.0].into(), Unit2::normalize ([1.0, 1.0].into()));
2249    assert_relative_eq!(
2250      project_2d_point_on_line (&point, &line), [1.0, 0.0].into());
2251    // the answer should be the same for lines with equivalent definitions
2252    let point : Point2 <f64> = [1.0, 3.0].into();
2253    let line_a = Line2::<f64>::new (
2254      [0.0, -1.0].into(), Unit2::normalize ([2.0, 1.0].into()));
2255    let line_b = Line2::<f64>::new (
2256      [2.0, 0.0].into(),  Unit2::normalize ([2.0, 1.0].into()));
2257    assert_relative_eq!(
2258      project_2d_point_on_line (&point, &line_a),
2259      project_2d_point_on_line (&point, &line_b));
2260    let line_a = Line2::<f64>::new (
2261      [0.0, 0.0].into(),   Unit2::normalize ([1.0, 1.0].into()));
2262    let line_b = Line2::<f64>::new (
2263      [-2.0, -2.0].into(), Unit2::normalize ([1.0, 1.0].into()));
2264    assert_ulps_eq!(
2265      project_2d_point_on_line (&point, &line_a),
2266      project_2d_point_on_line (&point, &line_b)
2267    );
2268    assert_relative_eq!(
2269      project_2d_point_on_line (&point, &line_a),
2270      [2.0, 2.0].into());
2271  }
2272
2273  #[test]
2274  fn project_3d_point_on_line() {
2275    use Unit3;
2276    use super::project_3d_point_on_line;
2277    // all the tests from 2d projection with 0.0 for the Z component
2278    let point : Point3 <f64> = [2.0, 2.0, 0.0].into();
2279    let line = Line3::<f64>::new ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
2280    assert_eq!(project_3d_point_on_line (&point, &line), [2.0, 0.0, 0.0].into());
2281    let line = Line3::<f64>::new ([0.0, 0.0, 0.0].into(), Unit3::axis_y());
2282    assert_eq!(project_3d_point_on_line (&point, &line), [0.0, 2.0, 0.0].into());
2283    let point : Point3 <f64> = [0.0, 1.0, 0.0].into();
2284    let line = Line3::<f64>::new (
2285      [0.0, -1.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
2286    assert_relative_eq!(
2287      project_3d_point_on_line (&point, &line), [1.0, 0.0, 0.0].into());
2288    // the answer should be the same for lines with equivalent definitions
2289    let point : Point3 <f64> = [1.0, 3.0, 0.0].into();
2290    let line_a = Line3::<f64>::new (
2291      [0.0, -1.0, 0.0].into(), Unit3::normalize ([2.0, 1.0, 0.0].into()));
2292    let line_b = Line3::<f64>::new (
2293      [2.0, 0.0, 0.0].into(),  Unit3::normalize ([2.0, 1.0, 0.0].into()));
2294    assert_relative_eq!(
2295      project_3d_point_on_line (&point, &line_a),
2296      project_3d_point_on_line (&point, &line_b));
2297    let line_a = Line3::<f64>::new (
2298      [0.0, 0.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
2299    let line_b = Line3::<f64>::new (
2300      [2.0, 2.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
2301    assert_relative_eq!(
2302      project_3d_point_on_line (&point, &line_a),
2303      project_3d_point_on_line (&point, &line_b));
2304    assert_relative_eq!(
2305      project_3d_point_on_line (&point, &line_a),
2306      [2.0, 2.0, 0.0].into());
2307    // more tests
2308    let point : Point3 <f64> = [0.0, 0.0, 2.0].into();
2309    let line_a = Line3::<f64>::new (
2310      [-4.0, -4.0, -4.0].into(), Unit3::normalize ([1.0, 1.0, 1.0].into()));
2311    let line_b = Line3::<f64>::new (
2312      [4.0, 4.0, 4.0].into(),    Unit3::normalize ([1.0, 1.0, 1.0].into()));
2313    assert_relative_eq!(
2314      project_3d_point_on_line (&point, &line_a),
2315      project_3d_point_on_line (&point, &line_b),
2316      epsilon = 0.000_000_000_000_01
2317    );
2318    assert_relative_eq!(
2319      project_3d_point_on_line (&point, &line_a),
2320      [2.0/3.0, 2.0/3.0, 2.0/3.0].into(),
2321      epsilon = 0.000_000_000_000_01
2322    );
2323  }
2324
2325  #[test]
2326  fn smallest_rect() {
2327    use super::smallest_rect;
2328    // points are in counter-clockwise order
2329    let points : Vec <Point2 <f32>> = [
2330      [-3.0, -3.0],
2331      [ 3.0, -3.0],
2332      [ 3.0,  3.0],
2333      [ 0.0,  6.0],
2334      [-3.0,  3.0]
2335    ].map (Point2::from).into_iter().collect();
2336    let hull = Hull2::from_points (points.as_slice()).unwrap();
2337    assert_eq!(hull.points(), points);
2338    let rect = smallest_rect (0, 1, &hull);
2339    assert_eq!(rect.area, 54.0);
2340    // points are in counter-clockwise order
2341    let points : Vec <Point2 <f32>> = [
2342      [-1.0, -4.0],
2343      [ 2.0,  2.0],
2344      [-4.0, -1.0]
2345    ].map (Point2::from).into_iter().collect();
2346    let hull = Hull2::from_points (points.as_slice()).unwrap();
2347    assert_eq!(hull.points(), points);
2348    let rect = smallest_rect (0, 1, &hull);
2349    assert_eq!(rect.area, 27.0);
2350  }
2351
2352  #[test]
2353  fn obb2() {
2354    let points : Vec <Point2 <f32>> = [
2355      [-1.0, -4.0],
2356      [ 2.0,  2.0],
2357      [-4.0, -1.0]
2358    ].map (Point2::from).into_iter().collect();
2359    let obb = Obb2::containing_points (&points).unwrap();
2360    let corners = obb.corners();
2361    assert_eq!(obb.center, [-0.25, -0.25].into());
2362    approx::assert_relative_eq!(point2 (-4.0, -1.0), corners[0], max_relative = 0.00001);
2363    approx::assert_relative_eq!(point2 ( 0.5,  3.5), corners[1], max_relative = 0.00001);
2364    approx::assert_relative_eq!(point2 (-1.0, -4.0), corners[2], max_relative = 0.00001);
2365    approx::assert_relative_eq!(point2 ( 3.5,  0.5), corners[3], max_relative = 0.00001);
2366  }
2367
2368  #[test]
2369  #[expect(clippy::suboptimal_flops)]
2370  fn obb3() {
2371    use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
2372    use approx::AbsDiffEq;
2373
2374    let points = [
2375      [ 1.0, -1.0, -1.0],
2376      [ 1.0, -1.0,  1.0],
2377      [ 1.0,  1.0, -1.0],
2378      [ 1.0,  1.0,  1.0],
2379      [-1.0, -1.0, -1.0],
2380      [-1.0, -1.0,  1.0],
2381      [-1.0,  1.0, -1.0],
2382      [-1.0,  1.0,  1.0]
2383    ].map (Point3::<f32>::from);
2384    let obb1 = Obb3::containing_points_with_orientation (&points, Angles3::zero());
2385    assert_eq!(
2386      SortedSet::from_unsorted (obb1.corners().to_vec()),
2387      SortedSet::from_unsorted (Aabb3::containing (&points).corners().to_vec()));
2388    let obb2 = Obb3::containing_points_approx (&points).unwrap();
2389    assert_eq!(obb1, obb2);
2390
2391    let rotation = Rotation3::from_angle_y (FRAC_PI_4.into());
2392    let points = points.map (|p| rotation.rotate (p));
2393    let obb1 = Obb3::containing_points_with_orientation (&points,
2394      // because the cube is symmetrical, it gets oriented by one of the faces
2395      Angles3::wrap (
2396        (3.0 * FRAC_PI_2).into(),
2397        (2.0 * PI - FRAC_PI_4).into(),
2398        FRAC_PI_2.into())
2399    );
2400    let obb2 = Obb3::containing_points_approx (&points).unwrap();
2401    approx::assert_abs_diff_eq!(obb1.center(), obb2.center());
2402    assert_eq!(obb1.orientation(), obb2.orientation());
2403    approx::assert_abs_diff_eq!(*obb1.half_extent_x(), *obb2.half_extent_x(),
2404      epsilon=4.0 * f32::default_epsilon());
2405    approx::assert_abs_diff_eq!(*obb1.half_extent_y(), *obb2.half_extent_y(),
2406      epsilon=4.0 * f32::default_epsilon());
2407    approx::assert_abs_diff_eq!(*obb1.half_extent_z(), *obb2.half_extent_z(),
2408      epsilon=2.0 * f32::default_epsilon());
2409    // because of rounding errors we can't sort the points to compare
2410    let corners = obb2.corners();
2411    approx::assert_abs_diff_eq!(corners[0], points[7],
2412      epsilon=8.0 * f32::default_epsilon());
2413    approx::assert_abs_diff_eq!(corners[1], points[5],
2414      epsilon=8.0 * f32::default_epsilon());
2415    approx::assert_abs_diff_eq!(corners[2], points[3],
2416      epsilon=8.0 * f32::default_epsilon());
2417    approx::assert_abs_diff_eq!(corners[3], points[1],
2418      epsilon=8.0 * f32::default_epsilon());
2419    approx::assert_abs_diff_eq!(corners[4], points[6],
2420      epsilon=8.0 * f32::default_epsilon());
2421    approx::assert_abs_diff_eq!(corners[5], points[4],
2422      epsilon=8.0 * f32::default_epsilon());
2423    approx::assert_abs_diff_eq!(corners[6], points[2],
2424      epsilon=8.0 * f32::default_epsilon());
2425    approx::assert_abs_diff_eq!(corners[7], points[0],
2426      epsilon=8.0 * f32::default_epsilon());
2427  }
2428} // end tests