Skip to main content

math_utils/geometry/primitive/
mod.rs

1//! Affine, convex, and combinatorial spaces
2
3use rand;
4
5use crate::*;
6use super::{intersect, shape};
7use super::mesh::VertexEdgeTriangleMesh;
8
9#[cfg(feature = "derive_serdes")]
10use serde::{Deserialize, Serialize};
11
12/// Primitives over signed integers
13pub mod integer;
14
15mod hull;
16mod simplex;
17
18#[macro_use]
19mod macro_def;
20
21pub use self::hull::*;
22pub use self::simplex::{simplex2, simplex3, Simplex2, Simplex3};
23pub use simplex2::{
24  Segment       as Segment2,
25  Triangle      as Triangle2,
26  SegmentPoint  as Segment2Point,
27  TrianglePoint as Triangle2Point
28};
29pub use simplex3::{
30  Segment          as Segment3,
31  Triangle         as Triangle3,
32  Tetrahedron      as Tetrahedron3,
33  SegmentPoint     as Segment3Point,
34  TrianglePoint    as Triangle3Point,
35  TetrahedronPoint as Tetrahedron3Point
36};
37
38/// A parameterized point on a 2D line
39pub type Line2Point <S> = (S, Point2 <S>);
40/// A parameterized point on a 3D line
41pub type Line3Point <S> = (S, Point3 <S>);
42/// A parameterized point on a 2D ray
43pub type Ray2Point <S> = (NonNegative <S>, Point2 <S>);
44/// A parameterized point on a 3D ray
45pub type Ray3Point <S> = (NonNegative <S>, Point3 <S>);
46
47/// Geometric primitives generic over dimension
48pub trait Primitive <S, P> : std::fmt::Debug where
49  S : Field,
50  P : AffineSpace <S>
51{
52  fn translate (&mut self, displacement : P::Translation);
53  fn scale     (&mut self, scale : Positive <S>);
54  /// Returns the point with largeest dot product in search direction
55  fn support   (&self, direction : <P::Translation as VectorSpace <S>>::NonZero)
56    -> (P, S);
57}
58
59////////////////////////////////////////////////////////////////////////////////////////
60//  structs                                                                           //
61////////////////////////////////////////////////////////////////////////////////////////
62
63/// 1D interval. Points are allowed to be equal.
64#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct Interval <S> {
67  min : S,
68  max : S
69}
70
71/// 2D axis-aligned bounding box.
72///
73/// Min/max can be co-linear:
74/// ```
75/// # use math_utils::geometry::Aabb2;
76/// let x = Aabb2::with_minmax (
77///   [0.0, 0.0].into(),
78///   [0.0, 1.0].into()
79/// ).unwrap();
80/// ```
81/// But not identical:
82/// ```should_panic
83/// # use math_utils::geometry::Aabb2;
84/// // panic!
85/// let x = Aabb2::with_minmax (
86///   [0.0, 0.0].into(),
87///   [0.0, 0.0].into()
88/// ).unwrap();
89/// ```
90#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
91#[derive(Clone, Copy, Debug, Eq, PartialEq)]
92pub struct Aabb2 <S> {
93  min : Point2 <S>,
94  max : Point2 <S>
95}
96
97/// 3D axis-aligned bounding box.
98///
99/// See also `shape::Aabb` trait for primitive and shape types that can compute a 3D
100/// AABB.
101///
102/// Min/max can be co-planar or co-linear:
103/// ```
104/// # use math_utils::geometry::Aabb3;
105/// let x = Aabb3::with_minmax (
106///   [0.0, 0.0, 0.0].into(),
107///   [0.0, 1.0, 1.0].into()
108/// ).unwrap();
109/// let y = Aabb3::with_minmax (
110///   [0.0, 0.0, 0.0].into(),
111///   [0.0, 1.0, 0.0].into()
112/// ).unwrap();
113/// ```
114/// But not identical:
115/// ```should_panic
116/// # use math_utils::geometry::Aabb3;
117/// let x = Aabb3::with_minmax (
118///   [0.0, 0.0, 0.0].into(),
119///   [0.0, 0.0, 0.0].into()
120/// ).unwrap();
121/// ```
122#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
123#[derive(Clone, Copy, Debug, Eq, PartialEq)]
124pub struct Aabb3 <S> {
125  min : Point3 <S>,
126  max : Point3 <S>
127}
128
129/// 2D oriented bounding box
130#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub struct Obb2 <S> {
133  pub center      : Point2 <S>,
134  pub extents     : Vector2 <Positive <S>>,
135  pub orientation : AngleWrapped <S>
136}
137
138/// 3D oriented bounding box
139#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
141pub struct Obb3 <S> {
142  pub center      : Point3 <S>,
143  pub extents     : Vector3 <Positive <S>>,
144  pub orientation : Angles3 <S>
145}
146
147/// 3D Z-axis-aligned cylinder
148#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
150pub struct Cylinder3 <S> {
151  pub center      : Point3   <S>,
152  pub half_height : Positive <S>,
153  pub radius      : Positive <S>
154}
155
156/// 3D Z-axis-aligned capsule
157#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
158#[derive(Clone, Copy, Debug, Eq, PartialEq)]
159pub struct Capsule3 <S> {
160  pub center      : Point3   <S>,
161  pub half_height : Positive <S>,
162  pub radius      : Positive <S>
163}
164
165/// 3D Z-axis-aligned cone
166#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
167#[derive(Clone, Copy, Debug, Eq, PartialEq)]
168pub struct Cone3 <S> {
169  pub center      : Point3   <S>,
170  pub half_height : Positive <S>,
171  pub radius      : Positive <S>
172}
173
174/// An infinitely extended line in 2D space defined by a base point and normalized
175/// direction
176#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
178pub struct Line2 <S> {
179  pub base      : Point2 <S>,
180  pub direction : Unit2  <S>
181}
182
183/// An infinitely extended ray in 2D space defined by a base point and normalized
184/// direction
185#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187pub struct Ray2 <S> {
188  pub base      : Point2 <S>,
189  pub direction : Unit2  <S>
190}
191
192/// An infinitely extended line in 3D space defined by a base point and normalized
193/// direction
194// TODO: currently most algorithms for lines are written to work with a line defined by
195// a line segment (no need to normalize); can we create another line type to improve
196// type checking?
197#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
198#[derive(Clone, Copy, Debug, Eq, PartialEq)]
199pub struct Line3 <S> {
200  pub base      : Point3 <S>,
201  pub direction : Unit3  <S>
202}
203
204/// An infinitely extended ray in 3D space defined by a base point and normalized
205/// direction
206#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub struct Ray3 <S> {
209  pub base      : Point3 <S>,
210  pub direction : Unit3  <S>
211}
212
213/// A plane in 3D space defined by a base point and (unit) normal vector
214#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
215#[derive(Clone, Copy, Debug, Eq, PartialEq)]
216pub struct Plane3 <S> {
217  pub base   : Point3 <S>,
218  pub normal : Unit3  <S>
219}
220
221/// Sphere in 2D space (a circle)
222#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
223#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224pub struct Sphere2 <S> {
225  pub center : Point2   <S>,
226  pub radius : Positive <S>
227}
228
229/// Sphere in 3D space
230#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
231#[derive(Clone, Copy, Debug, Eq, PartialEq)]
232pub struct Sphere3 <S> {
233  pub center : Point3   <S>,
234  pub radius : Positive <S>
235}
236
237/// Describes points of a 2D convex hull that support a rectangle and the area of the
238/// rectangle
239#[derive(Clone, Debug)]
240struct Rect <S> {
241  pub edge    : Vector2 <S>,
242  pub perp    : Vector2 <S>,
243  /// bottom, right, top, left
244  pub indices : [u32; 4],
245  pub area    : S,
246  pub edge_len_squared : S
247}
248
249////////////////////////////////////////////////////////////////////////////////////////
250//  functions                                                                         //
251////////////////////////////////////////////////////////////////////////////////////////
252
253/// Returns true when three points lie on the same line in 2D space.
254///
255/// ```
256/// # use math_utils::geometry::colinear_2;
257/// assert!(colinear_2 (
258///   [-1.0, -1.0].into(),
259///   [ 0.0,  0.0].into(),
260///   [ 1.0,  1.0].into())
261/// );
262/// assert!(!colinear_2 (
263///   [-1.0, -1.0].into(),
264///   [ 0.0,  1.0].into(),
265///   [ 1.0, -1.0].into())
266/// );
267/// ```
268pub fn colinear_2 <S> (a : Point2 <S>, b : Point2 <S>, c : Point2 <S>) -> bool where
269  S : OrderedField + approx::AbsDiffEq <Epsilon = S>
270{
271  let epsilon = adaptive_epsilon (&[a.0, b.0, c.0], &[b - a, c - a], None);
272  colinear_2_epsilon (a, b, c, epsilon)
273}
274
275pub fn colinear_2_epsilon <S> (
276  a : Point2 <S>, b : Point2 <S>, c : Point2 <S>, epsilon : S
277) -> bool where S : OrderedField {
278  // early exit: a pair of points are equal
279  if a == b || a == c || b == c {
280    return true
281  }
282  let e1 = b - a;
283  let e2 = c - a;
284  // pre-scale by max norm: components will lie in [-1, 1]
285  let e1_max = e1.norm_max();
286  let e2_max = e2.norm_max();
287  if *e1_max == S::zero() || *e2_max == S::zero() {
288    return true
289  }
290  let u        = e1 / *e1_max;
291  let v        = e2 / *e2_max;
292  let u_mag_sq = u.magnitude_squared();
293  let v_mag_sq = v.magnitude_squared();
294  let cross    = u.exterior_product (v);
295  cross * cross <= epsilon * epsilon * u_mag_sq * v_mag_sq
296}
297
298/// Returns true when three points lie on the same line in 3D space.
299///
300/// ```
301/// # use math_utils::geometry::colinear_3;
302/// assert!(colinear_3 (
303///   [-1.0, -1.0, -1.0].into(),
304///   [ 0.0,  0.0,  0.0].into(),
305///   [ 1.0,  1.0,  1.0].into())
306/// );
307/// ```
308pub fn colinear_3 <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>) -> bool where
309  S : OrderedField + approx::AbsDiffEq <Epsilon = S>
310{
311  let max_epsilon = S::two().powi (-3);
312  let epsilon = adaptive_epsilon (&[a.0, b.0, c.0], &[b - a, c - a], Some (max_epsilon));
313  colinear_3_epsilon (a, b, c, epsilon)
314}
315
316pub fn colinear_3_epsilon <S> (
317  a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, epsilon : S
318) -> bool where
319  S : OrderedField
320{
321  // early exit: a pair of points are equal
322  if a == b || a == c || b == c {
323    return true
324  }
325  let e1 = b - a;
326  let e2 = c - a;
327  // pre-scale by max norm: components will lie in [-1, 1]
328  let e1_max = e1.norm_max();
329  let e2_max = e2.norm_max();
330  if *e1_max == S::zero() || *e2_max == S::zero() {
331    return true
332  }
333  let u        = e1 / *e1_max;
334  let v        = e2 / *e2_max;
335  let u_mag_sq = u.magnitude_squared();
336  let v_mag_sq = v.magnitude_squared();
337  let cross_sq = u.cross (v).magnitude_squared();
338  cross_sq <= epsilon * epsilon * u_mag_sq * v_mag_sq
339}
340
341/// Returns true when four points lie on the same plane in 3D space.
342///
343/// ```
344/// # use math_utils::geometry::coplanar_3;
345/// assert!(coplanar_3 (
346///   [-1.0, -1.0, -1.0].into(),
347///   [ 1.0,  1.0,  1.0].into(),
348///   [-1.0,  1.0,  0.0].into(),
349///   [ 1.0, -1.0,  0.0].into()
350/// ));
351/// ```
352pub fn coplanar_3 <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>)
353  -> bool
354where S : OrderedField + approx::AbsDiffEq <Epsilon = S> {
355  let max_epsilon = S::two().powi (-2);
356  let epsilon     = adaptive_epsilon (
357    &[a.0, b.0, c.0, d.0], &[b - a, c - a, d - a], Some (max_epsilon));
358  coplanar_3_epsilon (a, b, c, d, epsilon)
359}
360
361pub fn coplanar_3_epsilon <S> (
362  a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>, epsilon : S
363) -> bool where
364  S : OrderedField + approx::AbsDiffEq <Epsilon = S>
365{
366  let ab = b - a;
367  let ac = c - a;
368  let ad = d - a;
369  let ab_max_norm = ab.norm_max();
370  let ac_max_norm = ac.norm_max();
371  let ad_max_norm = ad.norm_max();
372  if *ab_max_norm == S::zero() || *ac_max_norm == S::zero()
373    || *ad_max_norm == S::zero()
374  {
375    // early exit: a pair of points are equal
376    return true
377  }
378  let u = ab / *ab_max_norm;
379  let v = ac / *ac_max_norm;
380  let w = ad / *ad_max_norm;
381  let n = u.cross (v);
382  let n_sq = n.magnitude_squared();
383  if n_sq == S::zero() {
384    // a, b, c are colinear
385    return true
386  }
387  let tp   = n.dot (w);
388  let w_sq = w.magnitude_squared();
389  tp * tp <= epsilon * epsilon * n_sq * w_sq
390}
391
392/// Return a signum indicating which side of the triangle ABC that point D lies on,
393/// where +1 indicates that D lies on the side that the normal $(B - A) \times (C - A)$
394/// points to, -1 if D lies on the opposite side, and 0 if D lies on the plane ABC,
395/// within tolerance
396pub fn plane_side_3 <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>)
397  -> S
398where S : OrderedField + approx::AbsDiffEq <Epsilon = S> {
399  let max_epsilon = S::two().powi (-2);
400  let epsilon     = adaptive_epsilon (
401    &[a.0, b.0, c.0, d.0], &[b - a, c - a, d - a], Some (max_epsilon));
402  plane_side_3_epsilon (a, b, c, d, epsilon)
403}
404
405pub fn plane_side_3_epsilon <S> (
406  a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>, epsilon : S
407) -> S where
408  S : OrderedField + approx::AbsDiffEq <Epsilon = S>
409{
410  let ab = b - a;
411  let ac = c - a;
412  let ad = d - a;
413  let ab_max_norm = ab.norm_max();
414  let ac_max_norm = ac.norm_max();
415  let ad_max_norm = ad.norm_max();
416  if *ab_max_norm == S::zero() || *ac_max_norm == S::zero()
417    || *ad_max_norm == S::zero()
418  {
419    // a pair of points are equal => degenerate
420    return S::zero()
421  }
422  let u = ab / *ab_max_norm;
423  let v = ac / *ac_max_norm;
424  let w = ad / *ad_max_norm;
425  let n = u.cross (v);
426  let n_sq = n.magnitude_squared();
427  if n_sq == S::zero() {
428    // a, b, c are colinear
429    return S::zero()
430  }
431  let tp   = n.dot (w);
432  let w_sq = w.magnitude_squared();
433  if tp * tp <= epsilon * epsilon * n_sq * w_sq {
434    S::zero()
435  } else if tp > S::zero() {
436    S::one()
437  } else {
438    -S::one()
439  }
440}
441
442/// Computes the determinant of a matrix formed by the three points as columns
443#[inline]
444pub fn determinant_3 <S : Ring> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>) -> S {
445  Matrix3::from_col_arrays ([a.0.into_array(), b.0.into_array(), c.0.into_array()])
446    .determinant()
447}
448
449/// Create an orthonormal basis given the first basis component
450pub fn orthonormal_basis <S> (e1 : Unit3 <S>) -> [Unit3 <S>; 3] where S : Real {
451  use num::Zero;
452  let u = Unit3::axis_x();
453  let e2 = {
454    let mut e2 = (*e1).cross (*u);
455    e2.z += if e2.is_zero() {
456      S::one()
457    } else {
458      S::zero()
459    };
460    Unit3::new (e2).unwrap()
461  };
462  let e3 = Unit3::new ((*e1).cross (*e2)).unwrap();
463  [e1, e2, e3]
464}
465
466/// Coordinate-wise min
467#[inline]
468pub fn point2_min <S : PartialOrd> (a : Point2 <S>, b : Point2 <S>) -> Point2 <S> {
469  Vector2::partial_min (a.0, b.0).into()
470}
471
472/// Coordinate-wise max
473#[inline]
474pub fn point2_max <S : PartialOrd> (a : Point2 <S>, b : Point2 <S>) -> Point2 <S> {
475  Vector2::partial_max (a.0, b.0).into()
476}
477
478/// Coordinate-wise min
479#[inline]
480pub fn point3_min <S : PartialOrd> (a : Point3 <S>, b : Point3 <S>) -> Point3 <S> {
481  Vector3::partial_min (a.0, b.0).into()
482}
483
484/// Coordinate-wise max
485#[inline]
486pub fn point3_max <S : PartialOrd> (a : Point3 <S>, b : Point3 <S>) -> Point3 <S> {
487  Vector3::partial_max (a.0, b.0).into()
488}
489
490/// Given a 2D point and a 2D line, returns the nearest point on the line to the given
491/// point
492#[inline]
493pub fn project_point2_on_line2 <S : OrderedRing> (point : Point2 <S>, line : Line2 <S>)
494  -> Line2Point <S>
495{
496  let dot_dir = line.direction.dot (point - line.base);
497  (dot_dir, line.point (dot_dir))
498}
499
500/// Given a 3D point and a 3D line, returns the nearest point on the line to the given
501/// point
502#[inline]
503pub fn project_point3_on_line3 <S : OrderedRing> (point : Point3 <S>, line : Line3 <S>)
504  -> Line3Point <S>
505{
506  let dot_dir = line.direction.dot (point - line.base);
507  (dot_dir, line.point (dot_dir))
508}
509
510/// Given a 3D point and a 3D plane, returns the nearest point on the plane to the given
511/// point
512#[inline]
513pub fn project_point3_on_plane3 <S : Real> (point : Point3 <S>, plane : Plane3 <S>)
514  -> Point3 <S>
515{
516  let v = point - plane.base;
517  let distance = (*plane.normal).dot (v);
518  point - *plane.normal * distance
519}
520
521/// Returns point furthest along given direction together with dot product with the
522/// direction vector
523#[inline]
524pub fn support_2 <S> (points : &[Point2 <S>], direction : NonZero2 <S>)
525  -> (Point2 <S>, S)
526where S : Ring + PartialOrd {
527  debug_assert!(!points.is_empty());
528  points.iter()
529    .map (|point| (*point, direction.dot (point.0)))
530    .max_by (|(_, dot_a), (_, dot_b)| dot_a.partial_cmp (dot_b).unwrap()).unwrap()
531}
532
533/// Returns point furthest along given direction together with dot product with the
534/// direction vector
535#[inline]
536pub fn support_3 <S> (points : &[Point3 <S>], direction : NonZero3 <S>)
537  -> (Point3 <S>, S)
538where S : Ring + PartialOrd {
539  debug_assert!(!points.is_empty());
540  points.iter()
541    .map (|point| (*point, direction.dot (point.0)))
542    .max_by (|(_, dot_a), (_, dot_b)| dot_a.partial_cmp (dot_b).unwrap()).unwrap()
543}
544
545/// Square area of three points in 3D space.
546///
547/// Uses a numerically stable Heron's formula:
548/// <https://en.wikipedia.org/wiki/Heron%27s_formula#Numerical_stability>
549///
550/// ```
551/// # use math_utils::approx::assert_relative_eq;
552/// # use math_utils::geometry::triangle_3d_area_squared;
553/// assert_relative_eq!(
554///   3.0/4.0,
555///   *triangle_3d_area_squared (
556///     [-1.0,  0.0,  0.0].into(),
557///     [ 0.0,  0.0,  1.0].into(),
558///     [ 0.0,  1.0,  0.0].into())
559/// );
560/// ```
561///
562/// If the area squared is zero then the points are colinear:
563///
564/// ```
565/// # use math_utils::geometry::triangle_3d_area_squared;
566/// assert_eq!(
567///   0.0,
568///   *triangle_3d_area_squared (
569///     [-1.0, -1.0, -1.0].into(),
570///     [ 0.0,  0.0,  0.0].into(),
571///     [ 1.0,  1.0,  1.0].into())
572/// );
573/// ```
574pub fn triangle_3d_area_squared <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>)
575  -> NonNegative <S>
576where
577  S : OrderedField + Sqrt + approx::AbsDiffEq <Epsilon = S>
578{
579  use num::Zero;
580  if a == b || a == c || b == c {
581    return NonNegative::zero()
582  }
583  // compute the length of each side
584  let ab_mag = *(b-a).norm();
585  let ac_mag = *(c-a).norm();
586  let bc_mag = *(c-b).norm();
587  // order as min <= mid <= max
588  let min_ab_ac = S::min (ab_mag, ac_mag);
589  let max_ab_ac = S::max (ab_mag, ac_mag);
590  let (min, mid, max) = if bc_mag <= min_ab_ac {
591    (bc_mag, min_ab_ac, max_ab_ac)
592  } else if bc_mag >= max_ab_ac {
593    (min_ab_ac, max_ab_ac, bc_mag)
594  } else {
595    (min_ab_ac, bc_mag, max_ab_ac)
596  };
597  // 1.0/16.0
598  let frac = S::two().powi (-4);
599  let mut area_squared = frac
600    * (max + (mid + min))
601    * (min - (max - mid))
602    * (min + (max - mid))
603    * (max + (mid - min));
604  // numerical accuracy may result in a negative value; from tests this is always a
605  // relatively small value, in pathological cases (e.g. colinear points at several
606  // thousand units) can result in a value on the order of -0.00001
607  if area_squared < S::zero() {
608    if cfg!(debug_assertions) {
609      let lengths_squared = (b - a).magnitude_squared() + (c - b).magnitude_squared()
610        + (a - c).magnitude_squared();
611      approx::assert_abs_diff_eq!(area_squared, S::zero(),
612        epsilon = S::default_epsilon() * S::two().powi (44) * lengths_squared)
613    }
614    area_squared = S::zero();
615  }
616  NonNegative::unchecked (area_squared)
617}
618
619// private
620
621/// Smallest rectangle for given edge indices
622fn smallest_rect <S : Real> (i0 : u32, i1 : u32, hull : &Hull2 <S>) -> Rect <S> {
623  // NOTE: the following algorithm assumes that the points in the hull are
624  // counter-clockwise sorted and that the hull does not contain any colinear points.
625  // The construction of the hull should ensure these conditions.
626  let points = hull.points();
627  let edge             = points[i1 as usize] - points[i0 as usize];
628  let perp             = vector2 (-edge.y, edge.x);
629  let edge_len_squared = edge.magnitude_squared();
630  let origin           = points[i1 as usize];
631  let mut support      = [Point2::<S>::origin(); 4];
632  let mut rect_index   = [i1, i1, i1, i1];
633  #[expect(clippy::cast_possible_truncation)]
634  for (i, point) in points.iter().enumerate() {
635    let diff = point - origin;
636    let v    = point2 (edge.dot (diff), perp.dot (diff));
637    if v.x() > support[1].x() || v.x() == support[1].x() && v.y() > support[1].y() {
638      rect_index[1] = i as u32;
639      support[1]    = v;
640    }
641    if v.y() > support[2].y() || v.y() == support[2].y() && v.x() < support[2].x() {
642      rect_index[2] = i as u32;
643      support[2]    = v;
644    }
645    if v.x() < support[3].x() || v.x() == support[3].x() && v.y() < support[3].y() {
646      rect_index[3] = i as u32;
647      support[3]    = v;
648    }
649  }
650  let scaled_width  = support[1].x() - support[3].x();
651  let scaled_height = support[2].y();
652  let area = scaled_width * scaled_height / edge_len_squared;
653
654  Rect {
655    edge, perp, area, edge_len_squared,
656    indices: [rect_index[0], rect_index[1], rect_index[2], rect_index[3]]
657  }
658}
659
660fn adaptive_epsilon <S, V> (points : &[V], edges : &[V], max_epsilon : Option <S>)
661  -> S
662where
663  S : OrderedField + approx::AbsDiffEq <Epsilon = S>,
664  V : NormedVectorSpace <S> + Copy
665{
666  use num::Zero;
667  debug_assert!(!points.is_empty());
668  debug_assert!(!edges.is_empty());
669  let max_point_max_norm = points.iter().copied().map (NormedVectorSpace::norm_max)
670    .max_by (|a, b| a.partial_cmp (b).unwrap()).unwrap();
671  let min_edge_max_norm  = edges.iter().copied().map (NormedVectorSpace::norm_max)
672    .min_by (|a, b| a.partial_cmp (b).unwrap()).unwrap();
673  let ratio = if min_edge_max_norm == NonNegative::zero() {
674    NonNegative::zero()
675  } else {
676    max_point_max_norm / min_edge_max_norm
677  };
678  let epsilon = S::default_epsilon() * S::eight() * (S::one() + *ratio);
679  max_epsilon.map_or (epsilon, |max_epsilon| epsilon.min (max_epsilon))
680}
681
682////////////////////////////////////////////////////////////////////////////////////////
683//  impls                                                                             //
684////////////////////////////////////////////////////////////////////////////////////////
685
686impl <S> Interval <S> where S : Copy {
687  #[inline]
688  pub const fn min (self) -> S {
689    self.min
690  }
691  #[inline]
692  pub const fn max (self) -> S {
693    self.max
694  }
695}
696impl <S> Interval <S> where
697  S : MinMax + Copy + PartialOrd + std::fmt::Debug
698{
699  #[inline]
700  pub fn from_points (a : S, b : S) -> Self {
701    let min = S::min (a, b);
702    let max = S::max (a, b);
703    Interval { min, max }
704  }
705  /// Returns `None` if points are not min/max
706  #[inline]
707  pub fn with_minmax (min : S, max : S) -> Option <Self> {
708    if min > max {
709      None
710    } else {
711      Some (Interval { min, max })
712    }
713  }
714  /// Construct a new AABB with given min and max points.
715  ///
716  /// Debug panic if points are not min/max:
717  ///
718  /// ```should_panic
719  /// # use math_utils::geometry::*;
720  /// let b = Interval::with_minmax_unchecked (1.0, 0.0);  // panic!
721  /// ```
722  #[inline]
723  pub fn with_minmax_unchecked (min : S, max : S) -> Self {
724    debug_assert_eq!(min, S::min (min, max));
725    debug_assert_eq!(max, S::max (min, max));
726    Interval { min, max }
727  }
728  /// Construct the minimum AABB containing the given set of points.
729  ///
730  /// Returns `None` fewer than 2 points are given or all points are identical:
731  ///
732  /// ```
733  /// # use math_utils::geometry::*;
734  /// assert!(Interval::<f32>::containing (&[]).is_none());
735  /// ```
736  #[inline]
737  pub fn containing (points : &[S]) -> Option <Self> {
738    if points.len() < 2 {
739      None
740    } else {
741      debug_assert!(points.len() >= 2);
742      let mut min = S::min (points[0], points[1]);
743      let mut max = S::max (points[0], points[1]);
744      for point in points.iter().skip (2) {
745        min = S::min (min, *point);
746        max = S::max (max, *point);
747      }
748      Interval::with_minmax (min, max)
749    }
750  }
751  #[inline]
752  pub fn numcast <T> (self) -> Option <Interval <T>> where
753    S : num::ToPrimitive,
754    T : num::NumCast
755  {
756    Some (Interval {
757      min: T::from (self.min)?,
758      max: T::from (self.max)?
759    })
760  }
761  /// Create a new AABB that is the union of the two input AABBs
762  #[inline]
763  pub fn union (a : Interval <S>, b : Interval <S>) -> Self {
764    Interval::with_minmax_unchecked (
765      S::min (a.min(), b.min()), S::max (a.max(), b.max()))
766  }
767  #[inline]
768  pub fn length (self) -> NonNegative <S> where
769    S : num::Zero + std::ops::Sub <Output=S>
770  {
771    self.width()
772  }
773  #[inline]
774  pub fn width (self) -> NonNegative <S> where
775    S : num::Zero + std::ops::Sub <Output=S>
776  {
777    NonNegative::unchecked (self.max - self.min)
778  }
779  #[inline]
780  pub fn contains (self, point : S) -> bool {
781    self.min < point && point < self.max
782  }
783  /// Clamp a given point to the AABB interval.
784  ///
785  /// ```
786  /// # use math_utils::geometry::*;
787  /// let b = Interval::with_minmax_unchecked (-1.0, 1.0);
788  /// assert_eq!(b.clamp (-2.0), -1.0);
789  /// assert_eq!(b.clamp ( 2.0),  1.0);
790  /// assert_eq!(b.clamp ( 0.0),  0.0);
791  /// ```
792  #[inline]
793  pub fn clamp (self, point : S) -> S {
794    point.clamp (self.min, self.max)
795  }
796  /// Generate a random point contained in the AABB
797  ///
798  /// ```
799  /// # use rand;
800  /// # use rand_xorshift;
801  /// # use math_utils::geometry::*;
802  /// # fn main () {
803  /// use rand_xorshift;
804  /// use rand::SeedableRng;
805  /// // random sequence will be the same each time this is run
806  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
807  /// let aabb = Interval::<f32>::with_minmax_unchecked (-10.0, 10.0);
808  /// let point = aabb.rand_point (&mut rng);
809  /// assert!(aabb.contains (point));
810  /// let point = aabb.rand_point (&mut rng);
811  /// assert!(aabb.contains (point));
812  /// let point = aabb.rand_point (&mut rng);
813  /// assert!(aabb.contains (point));
814  /// let point = aabb.rand_point (&mut rng);
815  /// assert!(aabb.contains (point));
816  /// let point = aabb.rand_point (&mut rng);
817  /// assert!(aabb.contains (point));
818  /// # }
819  /// ```
820  #[inline]
821  pub fn rand_point <R> (self, rng : &mut R) -> S where
822    S : rand::distr::uniform::SampleUniform,
823    R : rand::RngExt
824  {
825    rng.random_range (self.min..self.max)
826  }
827  #[inline]
828  pub fn intersects (self, other : Interval <S>) -> bool {
829    intersect::overlaps_interval (self, other)
830  }
831  #[inline]
832  pub fn intersection (self, other : Interval <S>) -> Option <Interval <S>> {
833    intersect::intersect_interval (self, other)
834  }
835
836  /// Increase or decrease each endpoint by the given amount.
837  ///
838  /// Returns `None` if the interval width is less than twice the given amount:
839  ///
840  /// ```
841  /// # use math_utils::geometry::*;
842  /// let x = Interval::<f32>::with_minmax_unchecked (-1.0, 1.0);
843  /// assert!(x.dilate (-2.0).is_none());
844  /// ```
845  pub fn dilate (mut self, amount : S) -> Option <Self> where
846    S : std::ops::AddAssign + std::ops::SubAssign
847  {
848    self.min -= amount;
849    self.max += amount;
850    if self.min > self.max {
851      None
852    } else {
853      Some (self)
854    }
855  }
856}
857
858impl <S> Primitive <S, S> for Interval <S> where
859  S : OrderedField + AffineSpace <S, Translation=S>
860    + VectorSpace <S, NonZero=NonZero <S>>
861{
862  fn translate (&mut self, displacement : S) {
863    self.min += displacement;
864    self.max += displacement;
865  }
866  fn scale (&mut self, scale : Positive <S>) {
867    let old_width = *self.width();
868    let new_width = old_width * *scale;
869    let half_difference = (new_width - old_width) / S::two();
870    self.min -= half_difference;
871    self.max += half_difference;
872  }
873  fn support (&self, direction : NonZero <S>) -> (S, S) {
874    if *direction > S::zero() {
875      (self.max, self.max * *direction)
876    } else {
877      (self.min, self.min * *direction)
878    }
879  }
880}
881
882impl <S> std::ops::Add <S> for Interval <S> where
883  S    : Field + AffineSpace <S, Translation=S> + VectorSpace <S>,
884  Self : Primitive <S, S>
885{
886  type Output = Self;
887  #[expect(clippy::renamed_function_params)]
888  fn add (mut self, displacement : S) -> Self {
889    self.translate (displacement);
890    self
891  }
892}
893
894impl <S> std::ops::Sub <S> for Interval <S> where
895  S    : Field + AffineSpace <S, Translation=S> + VectorSpace <S>,
896  Self : Primitive <S, S>
897{
898  type Output = Self;
899  #[expect(clippy::renamed_function_params)]
900  fn sub (mut self, displacement : S) -> Self {
901    self.translate (-displacement);
902    self
903  }
904}
905
906impl_numcast_fields!(Aabb2, min, max);
907impl <S> Aabb2 <S> where S : Copy {
908  #[inline]
909  pub const fn min (self) -> Point2 <S> {
910    self.min
911  }
912  #[inline]
913  pub const fn max (self) -> Point2 <S> {
914    self.max
915  }
916}
917impl <S> Aabb2 <S> where
918  S : Copy + PartialOrd + std::fmt::Debug
919{
920  /// Returns `None` if points are not min/max or points are identical
921  #[inline]
922  pub fn with_minmax (min : Point2 <S>, max : Point2 <S>) -> Option <Self> {
923    if min == max || min != point2_min (min, max) || max != point2_max (min, max) {
924      None
925    } else {
926      Some (Aabb2 { min, max })
927    }
928  }
929  /// Returns `None` if points are identical
930  #[inline]
931  pub fn from_points (a : Point2 <S>, b : Point2 <S>) -> Option <Self> {
932    if a == b {
933      None
934    } else {
935      let min = point2_min (a, b);
936      let max = point2_max (a, b);
937      Some (Aabb2 { min, max })
938    }
939  }
940  /// Construct a new AABB with given min and max points.
941  ///
942  /// Debug panic if points are not min/max:
943  ///
944  /// ```should_panic
945  /// # use math_utils::geometry::*;
946  /// let b = Aabb2::with_minmax_unchecked (
947  ///   [1.0, 1.0].into(),
948  ///   [0.0, 0.0].into());  // panic!
949  /// ```
950  ///
951  /// Debug panic if points are identical:
952  ///
953  /// ```should_panic
954  /// # use math_utils::geometry::*;
955  /// let b = Aabb2::with_minmax_unchecked (
956  ///   [0.0, 0.0].into(),
957  ///   [0.0, 0.0].into());  // panic!
958  /// ```
959  #[inline]
960  pub fn with_minmax_unchecked (min : Point2 <S>, max : Point2 <S>) -> Self {
961    debug_assert_ne!(min, max);
962    debug_assert_eq!(min, point2_min (min, max));
963    debug_assert_eq!(max, point2_max (min, max));
964    Aabb2 { min, max }
965  }
966  /// Construct a new AABB using the two given points to determine min/max.
967  ///
968  /// Debug panic if points are identical:
969  ///
970  /// ```should_panic
971  /// # use math_utils::geometry::*;
972  /// let b = Aabb2::from_points_unchecked (
973  ///   [0.0, 0.0].into(),
974  ///   [0.0, 0.0].into());  // panic!
975  /// ```
976  #[inline]
977  pub fn from_points_unchecked (a : Point2 <S>, b : Point2 <S>) -> Self {
978    debug_assert_ne!(a, b);
979    let min = point2_min (a, b);
980    let max = point2_max (a, b);
981    Aabb2 { min, max }
982  }
983  /// Construct the minimum AABB containing the given set of points.
984  ///
985  /// Returns `None` if fewer than 2 points are given or all points are identical:
986  ///
987  /// ```
988  /// # use math_utils::geometry::*;
989  /// assert!(Aabb2::<f32>::containing (&[]).is_none());
990  /// ```
991  ///
992  /// ```
993  /// # use math_utils::geometry::*;
994  /// assert!(Aabb2::containing (&[[0.0, 0.0].into()]).is_none());
995  /// ```
996  #[inline]
997  pub fn containing (points : &[Point2 <S>]) -> Option <Self> {
998    if points.len() < 2 {
999      None
1000    } else {
1001      debug_assert!(points.len() >= 2);
1002      let mut min = point2_min (points[0], points[1]);
1003      let mut max = point2_max (points[0], points[1]);
1004      for point in points.iter().skip (2) {
1005        min = point2_min (min, *point);
1006        max = point2_max (max, *point);
1007      }
1008      Aabb2::with_minmax (min, max)
1009    }
1010  }
1011  /// Create a new AABB that is the union of the two input AABBs
1012  #[inline]
1013  pub fn union (a : Aabb2 <S>, b : Aabb2 <S>) -> Self {
1014    Aabb2::with_minmax_unchecked (
1015      point2_min (a.min(), b.min()), point2_max (a.max(), b.max()))
1016  }
1017  #[inline]
1018  pub fn center (self) -> Point2 <S> where S : Ring + std::ops::Div <Output=S> {
1019    Point2 ((self.min.0 + self.max.0) / S::two())
1020  }
1021  #[inline]
1022  pub fn dimensions (self) -> Vector2 <NonNegative <S>> where
1023    S : num::Zero + std::ops::Sub <Output=S>
1024  {
1025    (self.max.0 - self.min.0).map (NonNegative::unchecked)
1026  }
1027  #[inline]
1028  pub fn extents (self) -> Vector2 <NonNegative <S>> where S : Field {
1029    self.dimensions().map (|d| d * NonNegative::unchecked (S::half()))
1030  }
1031  /// X dimension
1032  #[inline]
1033  pub fn width (self) -> NonNegative <S> where S : num::Zero + std::ops::Sub <Output=S> {
1034    NonNegative::unchecked (self.max.x() - self.min.x())
1035  }
1036  /// Y dimension
1037  #[inline]
1038  pub fn height (self) -> NonNegative <S> where
1039    S : num::Zero + std::ops::Sub <Output=S>
1040  {
1041    NonNegative::unchecked (self.max.y() - self.min.y())
1042  }
1043  #[inline]
1044  pub fn area (self) -> NonNegative <S> where
1045    S : num::Zero + std::ops::Sub <Output=S> + std::ops::Mul <Output=S>
1046  {
1047    self.width() * self.height()
1048  }
1049  #[inline]
1050  pub fn contains (self, point : Point2 <S>) -> bool {
1051    self.min.x() < point.x() && point.x() < self.max.x() &&
1052    self.min.y() < point.y() && point.y() < self.max.y()
1053  }
1054  /// Clamp a given point to the AABB.
1055  ///
1056  /// ```
1057  /// # use math_utils::geometry::*;
1058  /// let b = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into()).unwrap();
1059  /// assert_eq!(b.clamp ([-2.0, 0.0].into()), [-1.0, 0.0].into());
1060  /// assert_eq!(b.clamp ([ 2.0, 2.0].into()), [ 1.0, 1.0].into());
1061  /// assert_eq!(b.clamp ([-0.5, 0.5].into()), [-0.5, 0.5].into());
1062  /// ```
1063  pub fn clamp (self, point : Point2 <S>) -> Point2 <S> where S : MinMax {
1064    [ point.x().clamp (self.min.x(), self.max.x()),
1065      point.y().clamp (self.min.y(), self.max.y())
1066    ].into()
1067  }
1068  /// Generate a random point contained in the AABB
1069  ///
1070  /// ```
1071  /// # use rand;
1072  /// # use rand_xorshift;
1073  /// # use math_utils::geometry::*;
1074  /// # fn main () {
1075  /// use rand_xorshift;
1076  /// use rand::SeedableRng;
1077  /// // random sequence will be the same each time this is run
1078  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
1079  /// let aabb = Aabb2::<f32>::with_minmax (
1080  ///   [-10.0, -10.0].into(),
1081  ///   [ 10.0,  10.0].into()
1082  /// ).unwrap();
1083  /// let point = aabb.rand_point (&mut rng);
1084  /// assert!(aabb.contains (point));
1085  /// let point = aabb.rand_point (&mut rng);
1086  /// assert!(aabb.contains (point));
1087  /// let point = aabb.rand_point (&mut rng);
1088  /// assert!(aabb.contains (point));
1089  /// let point = aabb.rand_point (&mut rng);
1090  /// assert!(aabb.contains (point));
1091  /// let point = aabb.rand_point (&mut rng);
1092  /// assert!(aabb.contains (point));
1093  /// # }
1094  /// ```
1095  #[inline]
1096  pub fn rand_point <R> (self, rng : &mut R) -> Point2 <S> where
1097    S : rand::distr::uniform::SampleUniform,
1098    R : rand::RngExt
1099  {
1100    let x_range = self.min.x()..self.max.x();
1101    let y_range = self.min.y()..self.max.y();
1102    let x = if x_range.is_empty() {
1103      self.min.x()
1104    } else {
1105      rng.random_range (x_range)
1106    };
1107    let y = if y_range.is_empty() {
1108      self.min.y()
1109    } else {
1110      rng.random_range (y_range)
1111    };
1112    [x, y].into()
1113  }
1114  #[inline]
1115  pub fn intersects (self, other : Aabb2 <S>) -> bool {
1116    intersect::overlaps_aabb2_aabb2 (self, other)
1117  }
1118  #[inline]
1119  pub fn intersection (self, other : Aabb2 <S>) -> Option <Aabb2 <S>> {
1120    intersect::intersect_aabb2_aabb2 (self, other)
1121  }
1122
1123  pub fn corner (self, quadrant : Quadrant) -> Point2 <S> {
1124    match quadrant {
1125      // upper-right
1126      Quadrant::PosPos => self.max,
1127      // lower-right
1128      Quadrant::PosNeg => [self.max.x(), self.min.y()].into(),
1129      // upper-left
1130      Quadrant::NegPos => [self.min.x(), self.max.y()].into(),
1131      // lower-left
1132      Quadrant::NegNeg => self.min
1133    }
1134  }
1135
1136  /// Corner points clockwise from min
1137  pub fn corners (self) -> [Point2 <S>; 4] {
1138    [ self.min, [self.min.x(), self.max.y()].into(),
1139      self.max, [self.max.x(), self.min.y()].into()
1140    ]
1141  }
1142
1143  pub fn edge (self, direction : SignedAxis2) -> Self {
1144    let (min, max) = match direction {
1145      SignedAxis2::PosX => (
1146        self.corner (Quadrant::PosNeg),
1147        self.corner (Quadrant::PosPos) ),
1148      SignedAxis2::NegX => (
1149        self.corner (Quadrant::NegNeg),
1150        self.corner (Quadrant::NegPos) ),
1151      SignedAxis2::PosY => (
1152        self.corner (Quadrant::NegPos),
1153        self.corner (Quadrant::PosPos) ),
1154      SignedAxis2::NegY => (
1155        self.corner (Quadrant::NegNeg),
1156        self.corner (Quadrant::PosNeg) )
1157    };
1158    Aabb2::with_minmax_unchecked (min, max)
1159  }
1160
1161  /// Extrude (or inset) a new Aabb from a face:
1162  ///
1163  /// ```
1164  /// # use math_utils::*;
1165  /// # use math_utils::geometry::*;
1166  /// let x = Aabb2::with_minmax ([-1.0, -1.0].into(), [1.0, 1.0].into())
1167  ///   .unwrap();
1168  /// assert_eq!(x.extrude (SignedAxis2::PosY, 0.5),
1169  ///   Aabb2::with_minmax ([-1.0, 1.0].into(), [1.0, 1.5].into()));
1170  /// assert_eq!(x.extrude (SignedAxis2::PosY, -0.5),
1171  ///   Aabb2::with_minmax ([-1.0, 0.5].into(), [1.0, 1.0].into()));
1172  /// ```
1173  #[must_use]
1174  pub fn extrude (self, axis : SignedAxis2, amount : S) -> Option <Self> where S : Ring {
1175    let (p1, p2) = match axis {
1176      SignedAxis2::PosX => (
1177        self.min + Vector2::zero().with_x (*self.width()),
1178        self.max + Vector2::zero().with_x (amount) ),
1179      SignedAxis2::NegX => (
1180        self.min - Vector2::zero().with_x (amount),
1181        self.max - Vector2::zero().with_x (*self.height()) ),
1182      SignedAxis2::PosY => (
1183        self.min + Vector2::zero().with_y (*self.height()),
1184        self.max + Vector2::zero().with_y (amount) ),
1185      SignedAxis2::NegY => (
1186        self.min - Vector2::zero().with_y (amount),
1187        self.max - Vector2::zero().with_y (*self.height()) )
1188    };
1189    Self::from_points (p1, p2)
1190  }
1191
1192  /// Extend (or contract) one of the extents
1193  #[must_use]
1194  pub fn extend (mut self, axis : SignedAxis2, amount : S) -> Option <Self>
1195    where S : std::ops::AddAssign + std::ops::SubAssign
1196  {
1197    match axis {
1198      SignedAxis2::PosX => self.max.0.x += amount,
1199      SignedAxis2::NegX => self.min.0.x -= amount,
1200      SignedAxis2::PosY => self.max.0.y += amount,
1201      SignedAxis2::NegY => self.min.0.y -= amount
1202    }
1203    Self::with_minmax (self.min, self.max)
1204  }
1205
1206  pub fn with_z (self, z : Interval <S>) -> Aabb3 <S> {
1207    Aabb3::with_minmax_unchecked (
1208      self.min.0.with_z (z.min()).into(),
1209      self.max.0.with_z (z.max()).into()
1210    )
1211  }
1212
1213  /// Increase or decrease each extent by the given amount.
1214  ///
1215  /// Returns `None` if any extent would become negative or if all dimensions become
1216  /// zero:
1217  /// ```
1218  /// # use math_utils::geometry::*;
1219  /// let x = Aabb2::with_minmax ([0.0, 0.0].into(), [1.0, 2.0].into()).unwrap();
1220  /// assert!(x.dilate (-1.0).is_none());
1221  /// ```
1222  #[must_use]
1223  pub fn dilate (mut self, amount : S) -> Option <Self> where S : Ring {
1224    self.min -= Vector2::broadcast (amount);
1225    self.max += Vector2::broadcast (amount);
1226    if self.min == self.max || self.min != point2_min (self.min, self.max)
1227      || self.max != point2_max (self.min, self.max)
1228    {
1229      None
1230    } else {
1231      Some (self)
1232    }
1233  }
1234
1235  /// Project *along* the given axis.
1236  ///
1237  /// For example, projecting *along* the X-axis projects *onto* the Y-axis.
1238  pub fn project (self, axis : Axis2) -> Interval <S> where S : MinMax {
1239    let (min, max) = match axis {
1240      Axis2::X => (self.min.y(), self.max.y()),
1241      Axis2::Y => (self.min.x(), self.max.x())
1242    };
1243    Interval::with_minmax_unchecked (min, max)
1244  }
1245}
1246
1247impl <S> Primitive <S, Point2 <S>> for Aabb2 <S> where S : OrderedField {
1248  fn translate (&mut self, displacement : Vector2 <S>) {
1249    self.min += displacement;
1250    self.max += displacement;
1251  }
1252  fn scale (&mut self, scale : Positive <S>) {
1253    let center = self.center();
1254    self.translate (-center.0);
1255    self.min.0 *= *scale;
1256    self.max.0 *= *scale;
1257    self.translate (center.0)
1258  }
1259  fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
1260    let out = if let Some (quadrant) = Quadrant::from_vec_strict (*direction) {
1261      self.corner (quadrant)
1262    } else if direction.x > S::zero() {
1263      debug_assert_eq!(direction.y, S::zero());
1264      point2 (self.max.x(), (self.min.y() + self.max.y()) / S::two())
1265    } else if direction.x < S::zero() {
1266      debug_assert_eq!(direction.y, S::zero());
1267      point2 (self.min.x(), (self.min.y() + self.max.y()) / S::two())
1268    } else {
1269      debug_assert_eq!(direction.x, S::zero());
1270      if direction.y > S::zero() {
1271        point2 ((self.min.x() + self.max.x()) / S::two(), self.max.y())
1272      } else {
1273        debug_assert!(direction.y < S::zero());
1274        point2 ((self.min.x() + self.max.x()) / S::two(), self.min.y())
1275      }
1276    };
1277    (out, out.0.dot (*direction))
1278  }
1279}
1280
1281impl <S> std::ops::Add <Vector2 <S>> for Aabb2 <S> where
1282  S    : Field,
1283  Self : Primitive <S, Point2 <S>>
1284{
1285  type Output = Self;
1286  #[expect(clippy::renamed_function_params)]
1287  fn add (mut self, displacement : Vector2 <S>) -> Self {
1288    self.translate (displacement);
1289    self
1290  }
1291}
1292
1293impl <S> std::ops::Sub <Vector2 <S>> for Aabb2 <S> where
1294  S    : Field,
1295  Self : Primitive <S, Point2 <S>>
1296{
1297  type Output = Self;
1298  #[expect(clippy::renamed_function_params)]
1299  fn sub (mut self, displacement : Vector2 <S>) -> Self {
1300    self.translate (-displacement);
1301    self
1302  }
1303}
1304
1305impl_numcast_fields!(Aabb3, min, max);
1306impl <S> Aabb3 <S> where S : Copy {
1307  #[inline]
1308  pub const fn min (self) -> Point3 <S> {
1309    self.min
1310  }
1311  #[inline]
1312  pub const fn max (self) -> Point3 <S> {
1313    self.max
1314  }
1315}
1316impl <S> Aabb3 <S> where S : Copy + PartialOrd + std::fmt::Debug {
1317  /// Returns `None` if points are not min/max or points are identical
1318  #[inline]
1319  pub fn with_minmax (min : Point3 <S>, max : Point3 <S>) -> Option <Self> {
1320    if min == max || min != point3_min (min, max) || max != point3_max (min, max) {
1321      None
1322    } else {
1323      Some (Aabb3 { min, max })
1324    }
1325  }
1326  /// Returns `None` if points are identical
1327  #[inline]
1328  pub fn from_points (a : Point3 <S>, b : Point3 <S>) -> Option <Self> {
1329    if a == b {
1330      None
1331    } else {
1332      let min = point3_min (a, b);
1333      let max = point3_max (a, b);
1334      Some (Aabb3 { min, max })
1335    }
1336  }
1337  /// Construct a new AABB with given min and max points.
1338  ///
1339  /// Debug panic if points are not min/max:
1340  ///
1341  /// ```should_panic
1342  /// # use math_utils::geometry::*;
1343  /// let b = Aabb3::with_minmax_unchecked (
1344  ///   [1.0, 1.0, 1.0].into(),
1345  ///   [0.0, 0.0, 0.0].into());  // panic!
1346  /// ```
1347  ///
1348  /// Debug panic if points are identical:
1349  ///
1350  /// ```should_panic
1351  /// # use math_utils::geometry::*;
1352  /// let b = Aabb3::with_minmax_unchecked (
1353  ///   [0.0, 0.0, 0.0].into(),
1354  ///   [0.0, 0.0, 0.0].into());  // panic!
1355  /// ```
1356  #[inline]
1357  pub fn with_minmax_unchecked (min : Point3 <S>, max : Point3 <S>) -> Self {
1358    debug_assert_ne!(min, max);
1359    debug_assert_eq!(min, point3_min (min, max));
1360    debug_assert_eq!(max, point3_max (min, max));
1361    Aabb3 { min, max }
1362  }
1363  /// Construct a new AABB using the two given points to determine min/max.
1364  ///
1365  /// Panic if points are identical:
1366  ///
1367  /// ```should_panic
1368  /// # use math_utils::geometry::*;
1369  /// let b = Aabb3::from_points_unchecked (
1370  ///   [0.0, 0.0, 0.0].into(),
1371  ///   [0.0, 0.0, 0.0].into());  // panic!
1372  /// ```
1373  #[inline]
1374  pub fn from_points_unchecked (a : Point3 <S>, b : Point3 <S>) -> Self {
1375    debug_assert_ne!(a, b);
1376    let min = point3_min (a, b);
1377    let max = point3_max (a, b);
1378    Aabb3 { min, max }
1379  }
1380  /// Construct the minimum AABB containing the given set of points.
1381  ///
1382  /// Returns `None` if fewer than 2 points are given or if all points are identical:
1383  ///
1384  /// ```should_panic
1385  /// # use math_utils::geometry::*;
1386  /// assert!(Aabb3::<f32>::containing (&[]).is_none());
1387  /// ```
1388  ///
1389  /// ```should_panic
1390  /// # use math_utils::geometry::*;
1391  /// assert!(Aabb3::containing (&[[0.0, 0.0, 0.0].into()]).is_none());
1392  /// ```
1393  #[inline]
1394  pub fn containing (points : &[Point3 <S>]) -> Option <Self> {
1395    debug_assert!(points.len() >= 2);
1396    let mut min = point3_min (points[0], points[1]);
1397    let mut max = point3_max (points[0], points[1]);
1398    for point in points.iter().skip (2) {
1399      min = point3_min (min, *point);
1400      max = point3_max (max, *point);
1401    }
1402    Aabb3::with_minmax (min, max)
1403  }
1404  /// Create a new AABB that is the union of the two input AABBs
1405  #[inline]
1406  pub fn union (a : Aabb3 <S>, b : Aabb3 <S>) -> Self {
1407    Aabb3::with_minmax_unchecked (
1408      point3_min (a.min(), b.min()), point3_max (a.max(), b.max()))
1409  }
1410  #[inline]
1411  pub fn center (self) -> Point3 <S> where S : Ring + std::ops::Div <Output=S> {
1412    Point3 ((self.min.0 + self.max.0) / S::two())
1413  }
1414  #[inline]
1415  pub fn dimensions (self) -> Vector3 <NonNegative <S>> where
1416    S : num::Zero + std::ops::Sub <Output=S>
1417  {
1418    (self.max.0 - self.min.0).map (NonNegative::unchecked)
1419  }
1420  #[inline]
1421  pub fn extents (self) -> Vector3 <NonNegative <S>> where S : Field {
1422    self.dimensions().map (|d| d * NonNegative::unchecked (S::half()))
1423  }
1424  /// X dimension
1425  #[inline]
1426  pub fn width (self) -> NonNegative <S> where S : num::Zero + std::ops::Sub <Output=S> {
1427    NonNegative::unchecked (self.max.x() - self.min.x())
1428  }
1429  /// Y dimension
1430  #[inline]
1431  pub fn depth (self) -> NonNegative <S> where S : num::Zero + std::ops::Sub <Output=S> {
1432    NonNegative::unchecked (self.max.y() - self.min.y())
1433  }
1434  /// Z dimension
1435  #[inline]
1436  pub fn height (self) -> NonNegative <S> where
1437    S : num::Zero + std::ops::Sub <Output=S>
1438  {
1439    NonNegative::unchecked (self.max.z() - self.min.z())
1440  }
1441  #[inline]
1442  pub fn volume (self) -> NonNegative <S> where
1443    S : num::Zero + std::ops::Sub <Output=S> + std::ops::Mul <Output=S>
1444  {
1445    self.width() * self.depth() * self.height()
1446  }
1447  #[inline]
1448  pub fn contains (self, point : Point3 <S>) -> bool {
1449    self.min.x() < point.x() && point.x() < self.max.x() &&
1450    self.min.y() < point.y() && point.y() < self.max.y() &&
1451    self.min.z() < point.z() && point.z() < self.max.z()
1452  }
1453  /// Clamp a given point to the AABB.
1454  ///
1455  /// ```
1456  /// # use math_utils::geometry::*;
1457  /// let b = Aabb3::with_minmax ([-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into())
1458  ///   .unwrap();
1459  /// assert_eq!(b.clamp ([-2.0, 0.0, 0.0].into()), [-1.0, 0.0, 0.0].into());
1460  /// assert_eq!(b.clamp ([ 2.0, 2.0, 0.0].into()), [ 1.0, 1.0, 0.0].into());
1461  /// assert_eq!(b.clamp ([-1.0, 2.0, 3.0].into()), [-1.0, 1.0, 1.0].into());
1462  /// assert_eq!(b.clamp ([-0.5, 0.5, 0.0].into()), [-0.5, 0.5, 0.0].into());
1463  /// ```
1464  pub fn clamp (self, point : Point3 <S>) -> Point3 <S> where S : MinMax {
1465    [ point.x().clamp (self.min.x(), self.max.x()),
1466      point.y().clamp (self.min.y(), self.max.y()),
1467      point.z().clamp (self.min.z(), self.max.z())
1468    ].into()
1469  }
1470  /// Generate a random point contained in the AABB
1471  ///
1472  /// ```
1473  /// # use rand;
1474  /// # use rand_xorshift;
1475  /// # use math_utils::geometry::*;
1476  /// # fn main () {
1477  /// use rand_xorshift;
1478  /// use rand::SeedableRng;
1479  /// // random sequence will be the same each time this is run
1480  /// let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (0);
1481  /// let aabb = Aabb3::<f32>::with_minmax (
1482  ///   [-10.0, -10.0, -10.0].into(),
1483  ///   [ 10.0,  10.0,  10.0].into()
1484  /// ).unwrap();
1485  /// let point = aabb.rand_point (&mut rng);
1486  /// assert!(aabb.contains (point));
1487  /// let point = aabb.rand_point (&mut rng);
1488  /// assert!(aabb.contains (point));
1489  /// let point = aabb.rand_point (&mut rng);
1490  /// assert!(aabb.contains (point));
1491  /// let point = aabb.rand_point (&mut rng);
1492  /// assert!(aabb.contains (point));
1493  /// let point = aabb.rand_point (&mut rng);
1494  /// assert!(aabb.contains (point));
1495  /// # }
1496  /// ```
1497  #[inline]
1498  pub fn rand_point <R> (self, rng : &mut R) -> Point3 <S> where
1499    S : rand::distr::uniform::SampleUniform,
1500    R : rand::RngExt
1501  {
1502    let x_range = self.min.x()..self.max.x();
1503    let y_range = self.min.y()..self.max.y();
1504    let z_range = self.min.z()..self.max.z();
1505    let x = if x_range.is_empty() {
1506      self.min.x()
1507    } else {
1508      rng.random_range (x_range)
1509    };
1510    let y = if y_range.is_empty() {
1511      self.min.y()
1512    } else {
1513      rng.random_range (y_range)
1514    };
1515    let z = if z_range.is_empty() {
1516      self.min.z()
1517    } else {
1518      rng.random_range (z_range)
1519    };
1520    [x, y, z].into()
1521  }
1522  #[inline]
1523  pub fn intersects (self, other : Aabb3 <S>) -> bool {
1524    intersect::overlaps_aabb3_aabb3 (self, other)
1525  }
1526  #[inline]
1527  pub fn intersection (self, other : Aabb3 <S>) -> Option <Aabb3 <S>> {
1528    intersect::intersect_aabb3_aabb3 (self, other)
1529  }
1530
1531  pub fn corner (self, octant : Octant) -> Point3 <S> {
1532    match octant {
1533      Octant::PosPosPos => self.max,
1534      Octant::NegPosPos => [self.min.x(), self.max.y(), self.max.z()].into(),
1535      Octant::PosNegPos => [self.max.x(), self.min.y(), self.max.z()].into(),
1536      Octant::NegNegPos => [self.min.x(), self.min.y(), self.max.z()].into(),
1537      Octant::PosPosNeg => [self.max.x(), self.max.y(), self.min.z()].into(),
1538      Octant::NegPosNeg => [self.min.x(), self.max.y(), self.min.z()].into(),
1539      Octant::PosNegNeg => [self.max.x(), self.min.y(), self.min.z()].into(),
1540      Octant::NegNegNeg => self.min
1541    }
1542  }
1543
1544  pub fn corners (self) -> [Point3 <S>; 8] {
1545    [ self.min,
1546      [self.min.x(), self.max.y(), self.max.z()].into(),
1547      [self.max.x(), self.min.y(), self.max.z()].into(),
1548      [self.min.x(), self.min.y(), self.max.z()].into(),
1549      [self.max.x(), self.max.y(), self.min.z()].into(),
1550      [self.min.x(), self.max.y(), self.min.z()].into(),
1551      [self.max.x(), self.min.y(), self.min.z()].into(),
1552      self.max
1553    ]
1554  }
1555
1556  pub fn face (self, direction : SignedAxis3) -> Self {
1557    let (min, max) = match direction {
1558      SignedAxis3::PosX => (
1559        self.corner (Octant::PosNegNeg),
1560        self.corner (Octant::PosPosPos) ),
1561      SignedAxis3::NegX => (
1562        self.corner (Octant::NegNegNeg),
1563        self.corner (Octant::NegPosPos) ),
1564      SignedAxis3::PosY => (
1565        self.corner (Octant::NegPosNeg),
1566        self.corner (Octant::PosPosPos) ),
1567      SignedAxis3::NegY => (
1568        self.corner (Octant::NegNegNeg),
1569        self.corner (Octant::PosNegPos) ),
1570      SignedAxis3::PosZ => (
1571        self.corner (Octant::NegNegPos),
1572        self.corner (Octant::PosPosPos) ),
1573      SignedAxis3::NegZ => (
1574        self.corner (Octant::NegNegNeg),
1575        self.corner (Octant::PosPosNeg) )
1576    };
1577    Aabb3::with_minmax_unchecked (min, max)
1578  }
1579
1580  /// Extrude (or inset) a new Aabb from a face:
1581  ///
1582  /// ```
1583  /// # use math_utils::*;
1584  /// # use math_utils::geometry::*;
1585  /// let x = Aabb3::with_minmax ([-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into())
1586  ///   .unwrap();
1587  /// assert_eq!(x.extrude (SignedAxis3::PosZ, 0.5),
1588  ///   Aabb3::with_minmax ([-1.0, -1.0, 1.0].into(), [1.0, 1.0, 1.5].into()));
1589  /// assert_eq!(x.extrude (SignedAxis3::PosZ, -0.5),
1590  ///   Aabb3::with_minmax ([-1.0, -1.0, 0.5].into(), [1.0, 1.0, 1.0].into()));
1591  /// ```
1592  #[must_use]
1593  pub fn extrude (self, axis : SignedAxis3, amount : S) -> Option <Self> where S : Ring {
1594    let (p1, p2) = match axis {
1595      SignedAxis3::PosX => (
1596        self.min + Vector3::zero().with_x (*self.width()),
1597        self.max + Vector3::zero().with_x (amount)
1598      ),
1599      SignedAxis3::NegX => (
1600        self.min - Vector3::zero().with_x (amount),
1601        self.max - Vector3::zero().with_x (*self.width())
1602      ),
1603      SignedAxis3::PosY => (
1604        self.min + Vector3::zero().with_y (*self.depth()),
1605        self.max + Vector3::zero().with_y (amount)
1606      ),
1607      SignedAxis3::NegY => (
1608        self.min - Vector3::zero().with_y (amount),
1609        self.max - Vector3::zero().with_y (*self.depth())
1610      ),
1611      SignedAxis3::PosZ => (
1612        self.min + Vector3::zero().with_z (*self.height()),
1613        self.max + Vector3::zero().with_z (amount)
1614      ),
1615      SignedAxis3::NegZ => (
1616        self.min - Vector3::zero().with_z (amount),
1617        self.max - Vector3::zero().with_z (*self.height())
1618      )
1619    };
1620    Aabb3::from_points (p1, p2)
1621  }
1622
1623  /// Extend (or contract) one of the extents
1624  #[must_use]
1625  pub fn extend (mut self, axis : SignedAxis3, amount : S) -> Option <Self>
1626    where S : std::ops::AddAssign + std::ops::SubAssign
1627  {
1628    match axis {
1629      SignedAxis3::PosX => self.max.0.x += amount,
1630      SignedAxis3::NegX => self.min.0.x -= amount,
1631      SignedAxis3::PosY => self.max.0.y += amount,
1632      SignedAxis3::NegY => self.min.0.y -= amount,
1633      SignedAxis3::PosZ => self.max.0.z += amount,
1634      SignedAxis3::NegZ => self.min.0.z -= amount
1635    }
1636    Self::with_minmax (self.min, self.max)
1637  }
1638
1639  /// Increase or decrease each extent by the given amount.
1640  ///
1641  /// Returns `None` if any extent would become negative:
1642  /// ```
1643  /// # use math_utils::geometry::*;
1644  /// let x = Aabb3::with_minmax ([0.0, 0.0, 0.0].into(), [1.0, 2.0, 3.0].into())
1645  ///   .unwrap();
1646  /// assert!(x.dilate (-1.0).is_none());
1647  /// ```
1648  #[must_use]
1649  pub fn dilate (mut self, amount : S) -> Option <Self> where S : Ring {
1650    self.min -= Vector3::broadcast (amount);
1651    self.max += Vector3::broadcast (amount);
1652    if self.min == self.max || self.min != point3_min (self.min, self.max)
1653      || self.max != point3_max (self.min, self.max)
1654    {
1655      None
1656    } else {
1657      Some (self)
1658    }
1659  }
1660
1661  /// Project *along* the given axis.
1662  ///
1663  /// For example, projecting *along* the X-axis projects *onto* the YZ-plane.
1664  pub fn project (self, axis : Axis3) -> Aabb2 <S> {
1665    let (min, max) = match axis {
1666      Axis3::X => ([self.min.y(), self.min.z()], [self.max.y(), self.max.z()]),
1667      Axis3::Y => ([self.min.x(), self.min.z()], [self.max.x(), self.max.z()]),
1668      Axis3::Z => ([self.min.x(), self.min.y()], [self.max.x(), self.max.y()]),
1669    };
1670    Aabb2::with_minmax_unchecked (min.into(), max.into())
1671  }
1672}
1673
1674impl <S> Primitive <S, Point3 <S>> for Aabb3 <S> where S : OrderedField {
1675  fn translate (&mut self, displacement : Vector3 <S>) {
1676    self.min += displacement;
1677    self.max += displacement;
1678  }
1679  fn scale (&mut self, scale : Positive <S>) {
1680    let center = self.center();
1681    self.translate (-center.0);
1682    self.min.0 *= *scale;
1683    self.max.0 *= *scale;
1684    self.translate (center.0);
1685  }
1686  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
1687    let out = if let Some (octant) = Octant::from_vec_strict (*direction) {
1688      self.corner (octant)
1689    } else {
1690      use Sign::*;
1691      let center = self.center();
1692      match direction.map (S::sign).into_array() {
1693        // faces
1694        [Positive, Zero, Zero] => center.0.with_x (self.max.x()).into(),
1695        [Negative, Zero, Zero] => center.0.with_x (self.min.x()).into(),
1696        [Zero, Positive, Zero] => center.0.with_y (self.max.y()).into(),
1697        [Zero, Negative, Zero] => center.0.with_y (self.min.y()).into(),
1698        [Zero, Zero, Positive] => center.0.with_z (self.max.z()).into(),
1699        [Zero, Zero, Negative] => center.0.with_z (self.min.z()).into(),
1700        // edges
1701        [Positive, Positive, Zero] =>
1702          center.0.with_x (self.max.x()).with_y (self.max.y()).into(),
1703        [Positive, Negative, Zero] =>
1704          center.0.with_x (self.max.x()).with_y (self.min.y()).into(),
1705        [Negative, Positive, Zero] =>
1706          center.0.with_x (self.min.x()).with_y (self.max.y()).into(),
1707        [Negative, Negative, Zero] =>
1708          center.0.with_x (self.min.x()).with_y (self.min.y()).into(),
1709
1710        [Positive, Zero, Positive] =>
1711          center.0.with_x (self.max.x()).with_z (self.max.z()).into(),
1712        [Positive, Zero, Negative] =>
1713          center.0.with_x (self.max.x()).with_z (self.min.z()).into(),
1714        [Negative, Zero, Positive] =>
1715          center.0.with_x (self.min.x()).with_z (self.max.z()).into(),
1716        [Negative, Zero, Negative] =>
1717          center.0.with_x (self.min.x()).with_z (self.min.z()).into(),
1718
1719        [Zero, Positive, Positive] =>
1720          center.0.with_y (self.max.y()).with_z (self.max.z()).into(),
1721        [Zero, Positive, Negative] =>
1722          center.0.with_y (self.max.y()).with_z (self.min.z()).into(),
1723        [Zero, Negative, Positive] =>
1724          center.0.with_y (self.min.y()).with_z (self.max.z()).into(),
1725        [Zero, Negative, Negative] =>
1726          center.0.with_y (self.min.y()).with_z (self.min.z()).into(),
1727        [_, _, _] => unreachable!()
1728      }
1729    };
1730    (out, out.0.dot (*direction))
1731  }
1732}
1733
1734impl <S> std::ops::Add <Vector3 <S>> for Aabb3 <S> where
1735  S    : Field,
1736  Self : Primitive <S, Point3 <S>>
1737{
1738  type Output = Self;
1739  #[expect(clippy::renamed_function_params)]
1740  fn add (mut self, displacement : Vector3 <S>) -> Self {
1741    self.translate (displacement);
1742    self
1743  }
1744}
1745
1746impl <S> std::ops::Sub <Vector3 <S>> for Aabb3 <S> where
1747  S    : Field,
1748  Self : Primitive <S, Point3 <S>>
1749{
1750  type Output = Self;
1751  #[expect(clippy::renamed_function_params)]
1752  fn sub (mut self, displacement : Vector3 <S>) -> Self {
1753    self.translate (-displacement);
1754    self
1755  }
1756}
1757
1758impl_numcast_fields!(Obb2, center, extents, orientation);
1759impl <S : OrderedRing> Obb2 <S> {
1760  /// Construct a new OBB
1761  #[inline]
1762  pub const fn new (
1763    center      : Point2 <S>,
1764    extents     : Vector2 <Positive <S>>,
1765    orientation : AngleWrapped <S>
1766  ) -> Self {
1767    Obb2 { center, extents, orientation }
1768  }
1769  /// Compute the OBB of a given orientation.
1770  ///
1771  /// Returns None if the points span an empty set.
1772  pub fn containing_points_with_orientation (
1773    points : &[Point2 <S>], orientation : AngleWrapped <S>
1774  ) -> Option <Self> where
1775    S : Real + num::real::Real + MaybeSerDes
1776  {
1777    let [x, y] = Rotation2::from_angle (orientation.angle()).cols
1778      .map (NonZero2::unchecked).into_array();
1779    let min_dot_x = -support_2 (points, -x).1;
1780    let max_dot_x =  support_2 (points,  x).1;
1781    let min_dot_y = -support_2 (points, -y).1;
1782    let max_dot_y =  support_2 (points,  y).1;
1783    let center = Point2 (
1784      (*x * min_dot_x + *x * max_dot_x) * S::half() +
1785      (*y * min_dot_y + *y * max_dot_y) * S::half());
1786    let extents = vector2 (
1787      Positive::new (S::half() * (max_dot_x - min_dot_x))?,
1788      Positive::new (S::half() * (max_dot_y - min_dot_y))?);
1789    Some (Obb2 { center, extents, orientation })
1790  }
1791  /// Construct the minimum OBB containing the convex hull of a given set of points
1792  /// using rotating calipers algorithm. To construct the OBB containing a convex hull
1793  /// directoy use the `Obb2::containing` method.
1794  ///
1795  /// Returns None if fewer than 3 points are given or if points span an empty set:
1796  ///
1797  /// ```
1798  /// # use math_utils::*;
1799  /// # use math_utils::geometry::*;
1800  /// assert!(Obb2::containing_points (
1801  ///   &[[0.0, 1.0], [1.0, 0.0]].map (Point2::from)
1802  /// ).is_none());
1803  ///
1804  /// assert!(Obb2::containing_points (
1805  ///   &[[1.0, 1.0], [0.0, 0.0], [-1.0, -1.0]].map (Point2::from)
1806  /// ).is_none());
1807  /// ```
1808  pub fn containing_points (points : &[Point2 <S>]) -> Option <Self> where S : Real {
1809    let hull = Hull2::from_points (points)?;
1810    Self::containing (&hull)
1811  }
1812  /// Construct the minimum OBB containing the given convex hull of points using
1813  /// rotating calipers algorithm.
1814  ///
1815  /// Returns None if fewer than 3 points are given or if points span an empty set:
1816  ///
1817  /// ```
1818  /// # use math_utils::*;
1819  /// # use math_utils::geometry::*;
1820  /// let hull = Hull2::from_points (
1821  ///   &[[0.0, 1.0], [1.0, 0.0]].map (Point2::from)).unwrap();
1822  /// assert!(Obb2::containing (&hull).is_none());
1823  ///
1824  /// let hull = Hull2::from_points (
1825  ///   &[[1.0, 1.0], [0.0, 0.0], [-1.0, -1.0]].map (Point2::from)).unwrap();
1826  /// assert!(Obb2::containing (&hull).is_none());
1827  /// ```
1828  // code follows GeometricTools:
1829  // <https://github.com/davideberly/GeometricTools/blob/f78dd0b65bc3a0a4723586de6991dd2c339b08ad/GTE/Mathematics/MinimumAreaBox2.h>
1830  pub fn containing (hull : &Hull2 <S>) -> Option <Self> where S : Real {
1831    let points = hull.points();
1832    if points.len() < 3 {
1833      return None
1834    }
1835    let mut visited  = vec![false; points.len()];
1836    #[expect(clippy::cast_possible_truncation)]
1837    let mut min_rect = smallest_rect (points.len() as u32 - 1, 0, hull);
1838    visited[min_rect.indices[0] as usize] = true;
1839    // rotating calipers
1840    let mut rect = min_rect.clone();
1841    for _ in 0..points.len() {
1842      let mut angles  = [(S::zero(), u32::MAX); 4];
1843      let mut nangles = u32::MAX;
1844      if !compute_angles (hull, &rect, &mut angles, &mut nangles) {
1845        break
1846      }
1847      let sorted = sort_angles (&angles, nangles);
1848      if !update_support (&angles, nangles, &sorted, hull, &mut visited, &mut rect) {
1849        break
1850      }
1851      if rect.area < min_rect.area {
1852        min_rect = rect.clone();
1853      }
1854    }
1855    // construct obb from min rect
1856    let sum = [
1857      points[min_rect.indices[1] as usize].0 + points[min_rect.indices[3] as usize].0,
1858      points[min_rect.indices[2] as usize].0 + points[min_rect.indices[0] as usize].0
1859    ];
1860    let diff = [
1861      points[min_rect.indices[1] as usize].0 - points[min_rect.indices[3] as usize].0,
1862      points[min_rect.indices[2] as usize].0 - points[min_rect.indices[0] as usize].0
1863    ];
1864    let obb = {
1865      let center = Point2::from ((min_rect.edge * min_rect.edge.dot (sum[0]) +
1866        min_rect.perp * min_rect.perp.dot (sum[1]))
1867        * S::half() / min_rect.edge_len_squared);
1868      let extents = {
1869        let extent_x = Positive::unchecked (S::sqrt (
1870          (S::half() * min_rect.edge.dot (diff[0])).squared()
1871          / min_rect.edge_len_squared));
1872        let extent_y = Positive::unchecked (S::sqrt (
1873          (S::half() * min_rect.perp.dot (diff[1])).squared()
1874          / min_rect.edge_len_squared));
1875        vector2 (extent_x, extent_y)
1876      };
1877      let orientation =
1878        AngleWrapped::wrap (Rad (min_rect.edge.y.atan2 (min_rect.edge.x)));
1879      Obb2 { center, extents, orientation }
1880    };
1881    fn compute_angles <S : Real> (
1882      hull    : &Hull2 <S>,
1883      rect    : &Rect <S>,
1884      angles  : &mut [(S, u32); 4],
1885      nangles : &mut u32
1886    ) -> bool {
1887      *nangles = 0;
1888      let points = hull.points();
1889      let mut k0 = 3;
1890      let mut k1 = 0;
1891      while k1 < 4 {
1892        if rect.indices[k0] != rect.indices[k1] {
1893          let d = {
1894            let mut d = if k0 & 1 != 0 {
1895              rect.perp
1896            } else {
1897              rect.edge
1898            };
1899            if k0 & 2 != 0 {
1900              d = -d;
1901            }
1902            d
1903          };
1904          let j0 = rect.indices[k0];
1905          let mut j1 = j0 + 1;
1906          #[expect(clippy::cast_possible_truncation)]
1907          if j1 == points.len() as u32 {
1908            j1 = 0;
1909          }
1910          let e = points[j1 as usize] - points[j0 as usize];
1911          let dp = d.dot ([e.y, -e.x].into());
1912          let e_lensq = e.magnitude_squared();
1913          let sin_theta_squared = (dp * dp) / e_lensq;
1914          angles[*nangles as usize] =
1915            #[expect(clippy::cast_possible_truncation)]
1916            (sin_theta_squared, k0 as u32);
1917          *nangles += 1;
1918        }
1919        k0 = k1;
1920        k1 += 1;
1921      }
1922      *nangles > 0
1923    }
1924    fn sort_angles <S : PartialOrd> (angles : &[(S, u32); 4], nangles : u32)
1925      -> [u32; 4]
1926    {
1927      let mut sorted = [0u32, 1, 2, 3];
1928      match nangles {
1929        0 | 1 => {}
1930        2 => if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
1931          sorted.swap (0, 1)
1932        }
1933        3 => {
1934          if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
1935            sorted.swap (0, 1)
1936          }
1937          if angles[sorted[0] as usize].0 > angles[sorted[2] as usize].0 {
1938            sorted.swap (0, 2)
1939          }
1940          if angles[sorted[1] as usize].0 > angles[sorted[2] as usize].0 {
1941            sorted.swap (1, 2)
1942          }
1943        }
1944        4 => {
1945          if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
1946            sorted.swap (0, 1)
1947          }
1948          if angles[sorted[2] as usize].0 > angles[sorted[3] as usize].0 {
1949            sorted.swap (2, 3)
1950          }
1951          if angles[sorted[0] as usize].0 > angles[sorted[2] as usize].0 {
1952            sorted.swap (0, 2)
1953          }
1954          if angles[sorted[1] as usize].0 > angles[sorted[3] as usize].0 {
1955            sorted.swap (1, 3)
1956          }
1957          if angles[sorted[1] as usize].0 > angles[sorted[2] as usize].0 {
1958            sorted.swap (1, 2)
1959          }
1960        }
1961        _ => unreachable!()
1962      }
1963      sorted
1964    }
1965    fn update_support <S : Real> (
1966      angles  : &[(S, u32); 4],
1967      nangles : u32,
1968      sorted  : &[u32; 4],
1969      hull    : &Hull2 <S>,
1970      visited : &mut [bool],
1971      rect    : &mut Rect <S>
1972    ) -> bool {
1973      let points = hull.points();
1974      let min_angle = angles[sorted[0] as usize];
1975      for k in 0..nangles as usize {
1976        let (angle, index) = angles[sorted[k] as usize];
1977        if angle == min_angle.0 {
1978          rect.indices[index as usize] += 1;
1979          #[expect(clippy::cast_possible_truncation)]
1980          if rect.indices[index as usize] == points.len() as u32 {
1981            rect.indices[index as usize] = 0;
1982          }
1983        }
1984      }
1985      let bottom = rect.indices[min_angle.1 as usize];
1986      if visited[bottom as usize] {
1987        return false
1988      }
1989      visited[bottom as usize] = true;
1990      let mut next_index = [u32::MAX; 4];
1991      for k in 0u32..4 {
1992        next_index[k as usize] = rect.indices[((min_angle.1 + k) % 4) as usize];
1993      }
1994      rect.indices = next_index;
1995      let j1 = rect.indices[0] as usize;
1996      let j0 = if j1 == 0 {
1997        points.len() - 1
1998      } else {
1999        j1 - 1
2000      };
2001      rect.edge = points[j1] - points[j0];
2002      rect.perp = vector2 (-rect.edge.y, rect.edge.x);
2003      let edge_len_squared = rect.edge.magnitude_squared();
2004      let diff = [
2005        points[rect.indices[1] as usize] - points[rect.indices[3] as usize],
2006        points[rect.indices[2] as usize] - points[rect.indices[0] as usize]
2007      ];
2008      rect.area = rect.edge.dot (diff[0]) * rect.perp.dot (diff[1]) / edge_len_squared;
2009      true
2010    }
2011    Some (obb)
2012  }
2013  #[inline]
2014  pub fn dimensions (self) -> Vector2 <Positive <S>> {
2015    self.extents * Positive::unchecked (S::two())
2016  }
2017  /// X dimension
2018  #[inline]
2019  pub fn width (self) -> Positive <S> {
2020    self.extents.x * Positive::unchecked (S::two())
2021  }
2022  /// Y dimension
2023  #[inline]
2024  pub fn height (self) -> Positive <S> {
2025    self.extents.y * Positive::unchecked (S::two())
2026  }
2027  #[inline]
2028  pub fn area (self) -> Positive <S> {
2029    self.width() * self.height()
2030  }
2031  #[inline]
2032  pub fn corners (self) -> [Point2 <S>; 4] where
2033    S : Real + num::real::Real + MaybeSerDes
2034  {
2035    let mut points = [Point2::origin(); 4];
2036    for i in 0..2 {
2037      for j in 0..2 {
2038        let sign_x = if i % 2 == 0 { -S::one() } else { S::one() };
2039        let sign_y = if j % 2 == 0 { -S::one() } else { S::one() };
2040        points[i * 2 + j] = [*self.extents.x * sign_x, *self.extents.y * sign_y].into();
2041      }
2042    }
2043    let rotation = Rotation2::from_angle (self.orientation.angle());
2044    points.map (|point| rotation.rotate (point) + self.center.0)
2045  }
2046
2047  pub fn aabb (self) -> Aabb2 <S> where S : Real + num::real::Real + MaybeSerDes {
2048    Aabb2::containing (&self.corners()).unwrap()
2049  }
2050
2051  /// Check if point is contained by the bounding box.
2052  ///
2053  /// ```
2054  /// # use math_utils::*;
2055  /// # use math_utils::geometry::*;
2056  /// let x = Obb2::new (
2057  ///   [3.0, 6.0].into(),
2058  ///   [2.0, 0.75].map (Positive::noisy).into(),
2059  ///   AngleWrapped::wrap (Rad (std::f32::consts::FRAC_PI_4)));
2060  /// assert!(x.contains ([2.0f32, 5.0].into()));
2061  /// assert!(!x.contains ([3.0f32, 2.0].into()));
2062  /// ```
2063  pub fn contains (self, point : Point2 <S>) -> bool where
2064    S : Real + num::real::Real + MaybeSerDes
2065  {
2066    // re-center
2067    let p = point - self.center.0;
2068    // reverse rotation
2069    let p = Rotation2::from_angle (-self.orientation.angle()).rotate (p);
2070    p.x().abs() < *self.extents.x && p.y().abs() < *self.extents.y
2071  }
2072
2073  /// Increase or decrease each extent by the given amount.
2074  ///
2075  /// ```
2076  /// # use math_utils::*;
2077  /// # use math_utils::geometry::*;
2078  /// # use math_utils::num::Zero;
2079  /// let x = Obb2::new (
2080  ///   Point2::origin(),
2081  ///   [0.5, 0.5].map (Positive::noisy).into(),
2082  ///   AngleWrapped::zero()
2083  /// );
2084  /// let y = x.dilate (1.0);
2085  /// assert_eq!(*y.dimensions().x, *x.dimensions().x + 2.0);
2086  /// assert_eq!(*y.dimensions().y, *x.dimensions().y + 2.0);
2087  /// ```
2088  ///
2089  /// Panics if any extent would become zero or negative:
2090  ///
2091  /// ```should_panic
2092  /// # use math_utils::*;
2093  /// # use math_utils::geometry::*;
2094  /// # use math_utils::num::Zero;
2095  /// let x = Obb2::new (
2096  ///   Point2::origin(),
2097  ///   [0.5, 0.5].map (Positive::noisy).into(),
2098  ///   AngleWrapped::zero()
2099  /// );
2100  /// let y = x.dilate (-1.0); // panic!
2101  /// ```
2102  pub fn dilate (mut self, amount : S) -> Self {
2103    self.extents = self.extents.map (|e| e.map_noisy (|e| e + amount));
2104    self
2105  }
2106}
2107impl <S : Real> From <Aabb2 <S>> for Obb2 <S> {
2108  /// Panics if AABB has a zero dimension:
2109  ///
2110  /// ```should_panic
2111  /// # use math_utils::*;
2112  /// # use math_utils::geometry::*;
2113  /// # use math_utils::num::Zero;
2114  /// let x = Aabb2::with_minmax (
2115  ///   [0.0, 0.0].into(),
2116  ///   [0.0, 1.0].into()
2117  /// ).unwrap();
2118  /// let y = Obb2::from (x); // panic!
2119  /// ```
2120  fn from (aabb : Aabb2 <S>) -> Self {
2121    use num::Zero;
2122    let center        = aabb.center();
2123    let extents = vector2 (
2124      Positive::noisy (*aabb.width()  / S::two()),
2125      Positive::noisy (*aabb.height() / S::two()));
2126    let orientation   = AngleWrapped::zero();
2127    Obb2::new (center, extents, orientation)
2128  }
2129}
2130impl <S> Primitive <S, Point2 <S>> for Obb2 <S> where
2131  S : Real + num::real::Real + MaybeSerDes
2132{
2133  fn translate (&mut self, displacement : Vector2 <S>) {
2134    self.center += displacement;
2135  }
2136  fn scale (&mut self, scale : Positive <S>) {
2137    self.extents *= scale;
2138  }
2139  fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
2140    use num::Inv;
2141    let rotation = Rotation2::from_angle (self.orientation.angle());
2142    let aabb = Aabb2::with_minmax_unchecked (
2143      self.center - self.extents.map (|e| *e),
2144      self.center + self.extents.map (|e| *e));
2145    let aabb_direction = rotation.inv() * direction;
2146    let (aabb_support, _) = aabb.support (aabb_direction);
2147    let out = rotation.rotate_around (aabb_support, self.center);
2148    (out, out.0.dot (*direction))
2149  }
2150}
2151
2152impl_numcast_fields!(Obb3, center, extents, orientation);
2153impl <S : OrderedRing> Obb3 <S> {
2154  /// Construct a new OBB
2155  #[inline]
2156  pub const fn new (
2157    center      : Point3 <S>,
2158    extents     : Vector3 <Positive <S>>,
2159    orientation : Angles3 <S>
2160  ) -> Self {
2161    Obb3 { center, extents, orientation }
2162  }
2163  /// Compute the OBB of a given orientation.
2164  ///
2165  /// Returns None if the points span an empty set.
2166  pub fn containing_points_with_orientation (
2167    points : &[Point3 <S>], orientation : Angles3 <S>
2168  ) -> Option <Self> where
2169    S : Real + num::real::Real + MaybeSerDes
2170  {
2171    let [x, y, z] = Rotation3::from (orientation).cols.map (NonZero3::unchecked)
2172      .into_array();
2173    let min_dot_x = -support_3 (points, -x).1;
2174    let max_dot_x =  support_3 (points,  x).1;
2175    let min_dot_y = -support_3 (points, -y).1;
2176    let max_dot_y =  support_3 (points,  y).1;
2177    let min_dot_z = -support_3 (points, -z).1;
2178    let max_dot_z =  support_3 (points,  z).1;
2179    let center = Point3 (
2180      (*x * min_dot_x + *x * max_dot_x) * S::half() +
2181      (*y * min_dot_y + *y * max_dot_y) * S::half() +
2182      (*z * min_dot_z + *z * max_dot_z) * S::half());
2183    let extents = vector3 (
2184      Positive::new (S::half() * (max_dot_x - min_dot_x))?,
2185      Positive::new (S::half() * (max_dot_y - min_dot_y))?,
2186      Positive::new (S::half() * (max_dot_z - min_dot_z))?);
2187    Some (Obb3 { center, extents, orientation })
2188  }
2189  /// Construct an approximate minimum OBB containing the convex hull of a given set of
2190  /// points.
2191  ///
2192  /// Returns None if fewer than 4 points are given or if points span an empty set:
2193  /// ```
2194  /// # use math_utils::*;
2195  /// # use math_utils::geometry::*;
2196  /// assert!(Obb3::containing_points_approx (
2197  ///   &[[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]].map (Point3::from)
2198  /// ).is_none());
2199  ///
2200  /// assert!(Obb3::containing_points_approx (
2201  ///   &[[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [-1.0,  0.0, -1.0]]
2202  ///     .map (Point3::from)
2203  /// ).is_none());
2204  /// ```
2205  pub fn containing_points_approx (points : &[Point3 <S>]) -> Option <Self> where
2206    S : Real + num::real::Real + approx::RelativeEq <Epsilon=S> + MaybeSerDes
2207  {
2208    if points.len() < 4 {
2209      return None
2210    }
2211    let (hull, mesh) = Hull3::from_points_with_mesh (points)?;
2212    Self::containing_approx (&hull, &mesh)
2213  }
2214  /// Construct an approximate minimum OBB containing the given convex hull and
2215  /// corresponding mesh.
2216  ///
2217  /// Returns None if fewer than 4 points are given or if points span an empty set:
2218  /// ```
2219  /// # use math_utils::*;
2220  /// # use math_utils::geometry::*;
2221  /// let (hull, mesh) = Hull3::from_points_with_mesh (
2222  ///   &[[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]].map (Point3::from)
2223  /// ).unwrap();
2224  /// assert!(Obb3::containing_approx (&hull, &mesh).is_none());
2225  ///
2226  /// let (hull, mesh) = Hull3::from_points_with_mesh (
2227  ///   &[[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [-1.0,  0.0, -1.0]]
2228  ///     .map (Point3::from)
2229  /// ).unwrap();
2230  /// assert!(Obb3::containing_approx (&hull, &mesh).is_none());
2231  /// ```
2232  pub fn containing_approx (hull : &Hull3 <S>, mesh : &VertexEdgeTriangleMesh)
2233    -> Option <Self>
2234  where S : Real + num::NumCast + approx::RelativeEq <Epsilon=S> + MaybeSerDes {
2235    // algorithm from:
2236    // <https://github.com/isl-org/Open3D/blob/fb7088ceebef38d54c575b47935e568979b50954/cpp/open3d/t/geometry/kernel/MinimumOBB.cpp>
2237    use num::Inv;
2238    if hull.points().len() < 4 {
2239      return None
2240    }
2241    let mut min_obb : Obb3 <S> = Aabb3::containing (hull.points()).unwrap().into();
2242    let mut min_vol = min_obb.volume();
2243    for triangle in mesh.triangles().values() {
2244      let [a, b, c] = triangle.vertices().map (|i| hull.points()[i as usize]);
2245      let mut u = b - a;
2246      let mut v = c - a;
2247      let mut w = u.cross (v);
2248      v = w.cross (u);
2249      u = *u.normalize();
2250      v = *v.normalize();
2251      w = *w.normalize();
2252      let m = Matrix3::from_row_arrays (std::array::from_fn (|i| [u[i], v[i], w[i]]));
2253      let m_rot       = Rotation3::new_approx (m).unwrap();
2254      let rot_inv     = m_rot.inv();
2255      let center      = a;
2256      let rotated     = hull.points().iter().copied()
2257        .map (|p| rot_inv.rotate_around (p, center))
2258        .collect::<Vec <_>>();
2259      let aabb        = Aabb3::containing (rotated.as_slice()).unwrap();
2260      let aabb_volume = aabb.volume();
2261      if approx::abs_diff_eq!(*aabb_volume, S::zero()) {
2262        // hull contains no volume
2263        return None
2264      }
2265      let volume = Positive::new (*aabb_volume).unwrap();
2266      if volume < min_vol {
2267        min_vol = volume;
2268        min_obb = aabb.into();
2269        min_obb.orientation = m_rot.into();
2270        min_obb.center = m_rot.rotate_around (min_obb.center, center);
2271      }
2272    }
2273    Some (min_obb)
2274  }
2275  /// Construct an approximate minimum OBB containing the given convex hull of points
2276  /// oriented according to principal component analysis bases.
2277  ///
2278  /// Returns None if fewer than 4 points are given or if points span an empty set:
2279  /// ```
2280  /// # use math_utils::*;
2281  /// # use math_utils::geometry::*;
2282  /// let hull = Hull3::from_points (
2283  ///   &[[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]].map (Point3::from)
2284  /// ).unwrap();
2285  /// assert!(Obb3::containing_approx_pca (&hull).is_none());
2286  ///
2287  /// let hull = Hull3::from_points (
2288  ///   &[[1.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0], [-1.0,  0.0, -1.0]]
2289  ///     .map (Point3::from)
2290  /// ).unwrap();
2291  /// assert!(Obb3::containing_approx_pca (&hull).is_none());
2292  /// ```
2293  pub fn containing_approx_pca (hull : &Hull3 <S>) -> Option <Self> where
2294    S : Real + num::real::Real + num::NumCast + approx::RelativeEq <Epsilon=S>
2295      + MaybeSerDes
2296  {
2297    if hull.points().len() < 4 {
2298      return None
2299    }
2300    // coplanar check
2301    // TODO: should we store dimensionality information in the hull to avoid doing this
2302    // calculation ?
2303    let [p1, p2, p3] = &hull.points()[..3] else { unreachable!() };
2304    let mut coplanar = true;
2305    for p in &hull.points()[3..] {
2306      if !coplanar_3 (*p1, *p2, *p3, *p) {
2307        coplanar = false;
2308        break
2309      }
2310    }
2311    if coplanar {
2312      return None
2313    }
2314    // do PCA analysis of points
2315    let orientation = data::pca_3 (hull.points()).into();
2316    Some (Self::containing_points_with_orientation (hull.points(), orientation).unwrap())
2317  }
2318  #[inline]
2319  pub fn dimensions (self) -> Vector3 <Positive <S>> {
2320    self.extents * Positive::unchecked (S::two())
2321  }
2322  /// X dimension
2323  #[inline]
2324  pub fn width (self) -> Positive <S> {
2325    self.extents.x * Positive::unchecked (S::two())
2326  }
2327  /// Y dimension
2328  #[inline]
2329  pub fn depth (self) -> Positive <S> {
2330    self.extents.y * Positive::unchecked (S::two())
2331  }
2332  /// Z dimension
2333  #[inline]
2334  pub fn height (self) -> Positive <S> {
2335    self.extents.z * Positive::unchecked (S::two())
2336  }
2337  #[inline]
2338  pub fn volume (self) -> Positive <S> {
2339    self.width() * self.depth() * self.height()
2340  }
2341  #[inline]
2342  pub fn corners (self) -> [Point3 <S>; 8] where
2343    S : Real + num::real::Real + MaybeSerDes
2344  {
2345    let mut points = [Point3::origin(); 8];
2346    for i in 0..2 {
2347      for j in 0..2 {
2348        for k in 0..2 {
2349          let sign_x = if i % 2 == 0 { -S::one() } else { S::one() };
2350          let sign_y = if j % 2 == 0 { -S::one() } else { S::one() };
2351          let sign_z = if k % 2 == 0 { -S::one() } else { S::one() };
2352          points[i * 4 + j * 2 + k] = [
2353            *self.extents.x * sign_x,
2354            *self.extents.y * sign_y,
2355            *self.extents.z * sign_z
2356          ].into();
2357        }
2358      }
2359    }
2360    let rotation = Rotation3::from (self.orientation);
2361    points.map (|point| rotation.rotate (point) + self.center.0)
2362  }
2363
2364  pub fn aabb (self) -> Aabb3 <S> where S : Real + num::real::Real + MaybeSerDes {
2365    Aabb3::containing (&self.corners()).unwrap()
2366  }
2367
2368  /// Check if point is contained by the bounding box
2369  pub fn contains (self, point : Point3 <S>) -> bool where
2370    S : Real + num::real::Real + MaybeSerDes
2371  {
2372    use num::Inv;
2373    // re-center
2374    let p = point - self.center.0;
2375    // reverse rotation
2376    let p = Rotation3::from (self.orientation).inv().rotate (p);
2377    p.x().abs() < *self.extents.x &&
2378    p.y().abs() < *self.extents.y &&
2379    p.z().abs() < *self.extents.z
2380  }
2381
2382  /// Increase or decrease each extent by the given amount.
2383  ///
2384  /// Panics if any extent would become zero or negative:
2385  ///
2386  /// ```should_panic
2387  /// # use math_utils::*;
2388  /// # use math_utils::geometry::*;
2389  /// let x = Obb3::new (
2390  ///   Point3::origin(),
2391  ///   [0.5, 0.5, 0.5].map (Positive::noisy).into(),
2392  ///   Angles3::zero()
2393  /// );
2394  /// let y = x.dilate (-1.0); // panic!
2395  /// ```
2396  pub fn dilate (mut self, amount : S) -> Self {
2397    self.extents = self.extents.map (|e| e.map_noisy (|e| e + amount));
2398    self
2399  }
2400}
2401impl <S : Real> From <Aabb3 <S>> for Obb3 <S> {
2402  /// Panics if AABB has a zero dimension:
2403  ///
2404  /// ```should_panic
2405  /// # use math_utils::*;
2406  /// # use math_utils::geometry::*;
2407  /// # use math_utils::num::Zero;
2408  /// let x = Aabb3::with_minmax (
2409  ///   [0.0, 0.0, 0.0].into(),
2410  ///   [0.0, 1.0, 1.0].into()
2411  /// ).unwrap();
2412  /// let y = Obb3::from (x); // panic!
2413  /// ```
2414  fn from (aabb : Aabb3 <S>) -> Self {
2415    let center = aabb.center();
2416    let extents = aabb.extents().map (|d| Positive::noisy (*d));
2417    let orientation = Angles3::zero();
2418    Obb3::new (center, extents, orientation)
2419  }
2420}
2421impl <S> Primitive <S, Point3 <S>> for Obb3 <S> where
2422  S : Real + num::real::Real + MaybeSerDes
2423{
2424  fn translate (&mut self, displacement : Vector3 <S>) {
2425    self.center += displacement;
2426  }
2427  fn scale (&mut self, scale : Positive <S>) {
2428    self.extents *= scale;
2429  }
2430  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2431    use num::Inv;
2432    let rotation = Rotation3::from (self.orientation);
2433    let aabb = Aabb3::with_minmax_unchecked (
2434      self.center - self.extents.map (|e| *e),
2435      self.center + self.extents.map (|e| *e));
2436    let aabb_direction = rotation.inv() * direction;
2437    let (aabb_support, _) = aabb.support (aabb_direction);
2438    let out = rotation.rotate_around (aabb_support, self.center);
2439    (out, out.0.dot (*direction))
2440  }
2441}
2442
2443impl_numcast_fields!(Capsule3, center, half_height, radius);
2444impl <S : OrderedRing> Capsule3 <S> {
2445  pub fn aabb3 (self) -> Aabb3 <S> where S : Real {
2446    use shape::Aabb;
2447    let shape_aabb = shape::Capsule {
2448      radius:      self.radius,
2449      half_height: self.half_height
2450    }.aabb();
2451    let center_vec = self.center.0;
2452    let min        = shape_aabb.min() + center_vec;
2453    let max        = shape_aabb.max() + center_vec;
2454    Aabb3::with_minmax_unchecked (min, max)
2455  }
2456  /// Return the (upper sphere, cylinder, lower sphere) making up this capsule
2457  pub fn decompose (self) -> (Sphere3 <S>, Option <Cylinder3 <S>>, Sphere3 <S>) {
2458    let cylinder = if *self.half_height > S::zero() {
2459      Some (Cylinder3 {
2460        center:      self.center,
2461        radius:      self.radius,
2462        half_height: Positive::unchecked (*self.half_height)
2463      })
2464    } else {
2465      None
2466    };
2467    let upper_sphere = Sphere3 {
2468      center: self.center + (Vector3::unit_z() * *self.half_height),
2469      radius: self.radius
2470    };
2471    let lower_sphere = Sphere3 {
2472      center: self.center - (Vector3::unit_z() * *self.half_height),
2473      radius: self.radius
2474    };
2475    (upper_sphere, cylinder, lower_sphere)
2476  }
2477}
2478impl <S> Primitive <S, Point3 <S>> for Capsule3 <S> where
2479  S : Real + num::real::Real + MaybeSerDes
2480{
2481  fn translate (&mut self, displacement : Vector3 <S>) {
2482    self.center += displacement;
2483  }
2484  fn scale (&mut self, scale : Positive <S>) {
2485    self.half_height *= scale;
2486    self.radius *= scale;
2487  }
2488  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2489    let dir_unit = direction.normalized();
2490    let out = self.center + if direction.z > S::zero() {
2491      Vector3::unit_z() * *self.half_height + dir_unit * self.radius
2492    } else if direction.z < S::zero() {
2493      -Vector3::unit_z() * *self.half_height + dir_unit * self.radius
2494    } else {
2495      dir_unit * self.radius
2496    };
2497    (out, out.0.dot (*direction))
2498  }
2499}
2500
2501impl_numcast_fields!(Cylinder3, center, half_height, radius);
2502impl <S : OrderedRing> Cylinder3 <S> {
2503  pub fn aabb3 (self) -> Aabb3 <S> where S : Real {
2504    use shape::Aabb;
2505    let shape_aabb = shape::Cylinder::unchecked (*self.radius, *self.half_height).aabb();
2506    let center_vec = self.center.0;
2507    let min        = shape_aabb.min() + center_vec;
2508    let max        = shape_aabb.max() + center_vec;
2509    Aabb3::with_minmax_unchecked (min, max)
2510  }
2511}
2512impl <S> Primitive <S, Point3 <S>> for Cylinder3 <S> where
2513  S : Real + num::real::Real + MaybeSerDes
2514{
2515  fn translate (&mut self, displacement : Vector3 <S>) {
2516    self.center += displacement;
2517  }
2518  fn scale (&mut self, scale : Positive <S>) {
2519    self.half_height *= scale;
2520    self.radius *= scale;
2521  }
2522  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2523    let out = self.center + if *direction == Vector3::unit_z() {
2524      Vector3::unit_z() * *self.half_height
2525    } else if *direction == -Vector3::unit_z() {
2526      -Vector3::unit_z() * *self.half_height
2527    } else if direction.z == S::zero() {
2528      direction.normalized() * self.radius
2529    } else if direction.z > S::zero() {
2530      Vector3::unit_z() * *self.half_height
2531        + vector3 (direction.x, direction.y, S::zero()).normalized() * self.radius
2532    } else {
2533      debug_assert!(direction.z < S::zero());
2534      -Vector3::unit_z() * *self.half_height
2535        + vector3 (direction.x, direction.y, S::zero()).normalized() * self.radius
2536    };
2537    (out, out.0.dot (*direction))
2538  }
2539}
2540
2541impl_numcast_fields!(Cone3, center, half_height, radius);
2542impl <S : OrderedRing> Cone3 <S> {
2543  pub fn height (self) -> Positive <S> {
2544    self.half_height * Positive::unchecked (S::two())
2545  }
2546  pub fn aabb3 (self) -> Aabb3 <S> where S : Real {
2547    use shape::Aabb;
2548    let shape_aabb = shape::Cone {
2549      radius:      self.radius,
2550      half_height: self.half_height
2551    }.aabb();
2552    let center_vec = self.center.0;
2553    let min        = shape_aabb.min() + center_vec;
2554    let max        = shape_aabb.max() + center_vec;
2555    Aabb3::with_minmax_unchecked (min, max)
2556  }
2557}
2558impl <S> Primitive <S, Point3 <S>> for Cone3 <S> where
2559  S : Real + num::real::Real + approx::RelativeEq + MaybeSerDes
2560{
2561  fn translate (&mut self, displacement : Vector3 <S>) {
2562    self.center += displacement;
2563  }
2564  fn scale (&mut self, scale : Positive <S>) {
2565    self.half_height *= scale;
2566    self.radius *= scale;
2567  }
2568  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2569    let top = || Vector3::unit_z() * *self.half_height;
2570    let bottom = || -top();
2571    let edge = || bottom()
2572      + vector3 (direction.x, direction.y, S::zero()).normalized() * self.radius;
2573    let out = self.center + if *direction == Vector3::unit_z() {
2574      top()
2575    } else if *direction == -Vector3::unit_z() {
2576      bottom()
2577    } else if direction.z <= S::zero() {
2578      edge()
2579    } else {
2580      // TODO: possibly cheaper with dot products ?
2581      let angle_dir =
2582        Trig::atan2 (direction.z, Sqrt::sqrt (S::one() - direction.z.squared()));
2583      debug_assert!(angle_dir > S::zero());
2584      let angle_cone = Trig::atan2 (-*self.height(), *self.radius);
2585      debug_assert!(angle_cone < S::zero());
2586      let pi_2 = S::pi() / S::two();
2587      let angle_total = angle_dir - angle_cone;
2588      if approx::relative_eq!(angle_total, pi_2) {
2589        (top() + edge()) * S::half()
2590      } else if angle_total > pi_2 {
2591        top()
2592      } else {
2593        debug_assert!(angle_total < pi_2);
2594        edge()
2595      }
2596    };
2597    (out, out.0.dot (*direction))
2598  }
2599}
2600
2601impl_line_ray_common!(Line2, Point2, Unit2, Line2, Ray2, S);
2602impl_numcast_fields!(Line2, base, direction);
2603impl <S : OrderedRing> Line2 <S> {
2604  #[inline]
2605  pub fn nearest_point (self, point : Point2 <S>) -> Line2Point <S> {
2606    project_point2_on_line2 (point, self)
2607  }
2608  #[inline]
2609  pub fn intersect_aabb (self, aabb : Aabb2 <S>)
2610    -> Option <(Line2Point <S>, Line2Point <S>)>
2611  {
2612    intersect::intersect_line2_aabb2 (self, aabb)
2613  }
2614  #[inline]
2615  pub fn intersect_sphere (self, sphere : Sphere2 <S>)
2616    -> Option <(Line2Point <S>, Line2Point <S>)>
2617  where S : Field + Sqrt {
2618    intersect::intersect_line2_sphere2 (self, sphere)
2619  }
2620}
2621impl <S> Primitive <S, Point2 <S>> for Line2 <S> where
2622  S : OrderedField + num::float::FloatCore + approx::AbsDiffEq
2623{
2624  fn translate (&mut self, displacement : Vector2 <S>) {
2625    self.base += displacement;
2626  }
2627  #[expect(unused_variables)]
2628  fn scale (&mut self, scale : Positive <S>) {
2629    /* no-op */
2630  }
2631  fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
2632    let dot = self.direction.dot (*direction);
2633    let out = if approx::abs_diff_eq!(dot, S::zero()) {
2634      self.base
2635    } else if dot > S::zero() {
2636      Point2 (self.direction.sigvec().map (|x| if x == S::zero() {
2637        S::zero()
2638      } else {
2639        x * S::infinity()
2640      }))
2641    } else {
2642      debug_assert!(dot < S::zero());
2643      Point2 ((-self.direction).sigvec().map (|x| if x == S::zero() {
2644        S::zero()
2645      } else {
2646        x * S::infinity()
2647      }))
2648    };
2649    (out, out.0.dot (*direction))
2650  }
2651}
2652
2653impl_line_ray_common!(Ray2, Point2, Unit2, Line2, Line2, NonNegative <S>);
2654impl_numcast_fields!(Ray2, base, direction);
2655impl <S : OrderedRing> Ray2 <S> {
2656  pub fn nearest_point (self, point : Point2 <S>) -> Ray2Point <S> {
2657    use num::Zero;
2658    let (t, point) = Line2::from (self).nearest_point (point);
2659    if t < S::zero() {
2660      (NonNegative::zero(), self.base)
2661    } else {
2662      (NonNegative::unchecked (t), point)
2663    }
2664  }
2665  #[inline]
2666  pub fn intersect_aabb (self, aabb : Aabb2 <S>)
2667    -> Option <(Ray2Point <S>, Ray2Point <S>)>
2668  {
2669    intersect::intersect_ray2_aabb2 (self, aabb)
2670  }
2671  #[inline]
2672  pub fn intersect_sphere (self, sphere : Sphere2 <S>)
2673    -> Option <(Ray2Point <S>, Ray2Point <S>)>
2674  {
2675    intersect::intersect_ray2_sphere2 (self, sphere)
2676  }
2677}
2678impl <S> Primitive <S, Point2 <S>> for Ray2 <S> where
2679  S : OrderedField + num::float::FloatCore + approx::AbsDiffEq <Epsilon = S>
2680{
2681  fn translate (&mut self, displacement : Vector2 <S>) {
2682    self.base += displacement;
2683  }
2684  #[expect(unused_variables)]
2685  fn scale (&mut self, scale : Positive <S>) {
2686    /* no-op */
2687  }
2688  fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
2689    let dot = self.direction.dot (*direction);
2690    let out = if dot < S::default_epsilon() {
2691      self.base
2692    } else {
2693      Point2 (self.direction.sigvec().map (|x| if x == S::zero() {
2694        S::zero()
2695      } else {
2696        x * S::infinity()
2697      }))
2698    };
2699    (out, out.0.dot (*direction))
2700  }
2701}
2702
2703impl_line_ray_common!(Line3, Point3, Unit3, Line3, Ray3, S);
2704impl_numcast_fields!(Line3, base, direction);
2705impl <S : OrderedRing> Line3 <S> {
2706  #[inline]
2707  pub fn nearest_point (self, point : Point3 <S>) -> Line3Point <S> {
2708    project_point3_on_line3 (point, self)
2709  }
2710  #[inline]
2711  pub fn intersect_plane (self, plane : Plane3 <S>) -> Option <Line3Point <S>> where
2712    S : Field + approx::RelativeEq
2713  {
2714    intersect::intersect_line3_plane3 (self, plane)
2715  }
2716  #[inline]
2717  pub fn intersect_aabb (self, aabb : Aabb3 <S>)
2718    -> Option <(Line3Point <S>, Line3Point <S>)>
2719  where S : num::Float + approx::RelativeEq <Epsilon=S> {
2720    intersect::intersect_line3_aabb3 (self, aabb)
2721  }
2722  #[inline]
2723  pub fn intersect_sphere (self, sphere : Sphere3 <S>)
2724    -> Option <(Line3Point <S>, Line3Point <S>)>
2725  where S : Field + Sqrt {
2726    intersect::intersect_line3_sphere3 (self, sphere)
2727  }
2728  #[inline]
2729  pub fn intersect_triangle (self, triangle : Triangle3 <S>)
2730    -> Option <Line3Point <S>>
2731  where S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
2732    intersect::intersect_line3_triangle3 (self, triangle)
2733  }
2734}
2735impl <S> Primitive <S, Point3 <S>> for Line3 <S> where
2736  S : OrderedField + num::float::FloatCore + approx::AbsDiffEq
2737{
2738  fn translate (&mut self, displacement : Vector3 <S>) {
2739    self.base += displacement;
2740  }
2741  #[expect(unused_variables)]
2742  fn scale (&mut self, scale : Positive <S>) {
2743    /* no-op */
2744  }
2745  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2746    let dot = self.direction.dot (*direction);
2747    let out = if approx::abs_diff_eq!(dot, S::zero()) {
2748      self.base
2749    } else if dot > S::zero() {
2750      Point3 (self.direction.sigvec().map (|x| if x == S::zero() {
2751        S::zero()
2752      } else {
2753        x * S::infinity()
2754      }))
2755    } else {
2756      debug_assert!(dot < S::zero());
2757      Point3 (-self.direction.sigvec().map (|x| if x == S::zero() {
2758        S::zero()
2759      } else {
2760        x * S::infinity()
2761      }))
2762    };
2763    (out, out.0.dot (*direction))
2764  }
2765}
2766
2767impl_line_ray_common!(Ray3, Point3, Unit3, Line3, Line3, NonNegative <S>);
2768impl_numcast_fields!(Ray3, base, direction);
2769impl <S : OrderedRing> Ray3 <S> {
2770  pub fn nearest_point (self, point : Point3 <S>) -> Ray3Point <S> {
2771    use num::Zero;
2772    let (t, point) = Line3::from (self).nearest_point (point);
2773    if t < S::zero() {
2774      (NonNegative::zero(), self.base)
2775    } else {
2776      (NonNegative::unchecked (t), point)
2777    }
2778  }
2779  #[inline]
2780  pub fn intersect_plane (self, plane : Plane3 <S>) -> Option <Ray3Point <S>> where
2781    S : approx::RelativeEq
2782  {
2783    intersect::intersect_ray3_plane3 (self, plane)
2784  }
2785  #[inline]
2786  pub fn intersect_aabb (self, aabb : Aabb3 <S>)
2787    -> Option <(Ray3Point <S>, Ray3Point <S>)>
2788  where S : num::Float + approx::RelativeEq <Epsilon=S> {
2789    intersect::intersect_ray3_aabb3 (self, aabb)
2790  }
2791  #[inline]
2792  pub fn intersect_sphere (self, sphere : Sphere3 <S>)
2793    -> Option <(Ray3Point <S>, Ray3Point <S>)>
2794  {
2795    intersect::intersect_ray3_sphere3 (self, sphere)
2796  }
2797  #[inline]
2798  pub fn intersect_triangle (self, triangle : Triangle3 <S>)
2799    -> Option <Ray3Point <S>>
2800  where S : Real + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
2801    intersect::intersect_ray3_triangle3 (self, triangle)
2802  }
2803}
2804impl <S> Primitive <S, Point3 <S>> for Ray3 <S> where
2805  S : OrderedField + num::float::FloatCore + approx::AbsDiffEq <Epsilon = S>
2806{
2807  fn translate (&mut self, displacement : Vector3 <S>) {
2808    self.base += displacement;
2809  }
2810  #[expect(unused_variables)]
2811  fn scale (&mut self, scale : Positive <S>) {
2812    /* no-op */
2813  }
2814  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2815    let dot = self.direction.dot (*direction);
2816    let out = if dot < S::default_epsilon() {
2817      self.base
2818    } else {
2819      Point3 (self.direction.sigvec().map (|x| if x == S::zero() {
2820        S::zero()
2821      } else {
2822        x * S::infinity()
2823      }))
2824    };
2825    (out, out.0.dot (*direction))
2826  }
2827}
2828
2829impl_numcast_fields!(Plane3, base, normal);
2830impl <S> Plane3 <S> {
2831  /// Consructs a new 3D plane with given base point and normal vector
2832  #[inline]
2833  pub const fn new (base : Point3 <S>, normal : Unit3 <S>) -> Self {
2834    Plane3 { base, normal }
2835  }
2836}
2837impl <S : Real> Default for Plane3 <S> {
2838  fn default() -> Self {
2839    Plane3 {
2840      base:   Point3::origin(),
2841      normal: Unit3::axis_z()
2842    }
2843  }
2844}
2845impl <S> Primitive <S, Point3 <S>> for Plane3 <S> where
2846  S : Real + num::float::FloatCore + approx::RelativeEq
2847{
2848  fn translate (&mut self, displacement : Vector3 <S>) {
2849    self.base += displacement;
2850  }
2851  #[expect(unused_variables)]
2852  fn scale (&mut self, scale : Positive <S>) {
2853    /* no-op */
2854  }
2855  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2856    let dot = self.normal.dot (*direction);
2857    let out = if approx::relative_eq!(dot, S::one()) {
2858      self.base
2859    } else {
2860      let projected =
2861        project_point3_on_plane3 (Point3 (self.base.0 + *direction), *self).0;
2862      Point3 (projected.sigvec().map (|x| if x == S::zero() {
2863        S::zero()
2864      } else {
2865        x * S::infinity()
2866      }))
2867    };
2868    (out, out.0.dot (*direction))
2869  }
2870}
2871
2872impl_numcast_fields!(Sphere2, center, radius);
2873impl <S : OrderedRing> Sphere2 <S> {
2874  /// Unit circle
2875  #[inline]
2876  pub fn unit() -> Self {
2877    Sphere2 {
2878      center: Point2::origin(),
2879      radius: num::One::one()
2880    }
2881  }
2882  /// Discrete intersection test with another sphere
2883  #[inline]
2884  pub fn intersects (self, other : Self) -> bool {
2885    intersect::overlaps_sphere2_sphere2 (self, other)
2886  }
2887}
2888impl <S> Primitive <S, Point2 <S>> for Sphere2 <S> where
2889  S : Real + num::real::Real + MaybeSerDes
2890{
2891  fn translate (&mut self, displacement : Vector2 <S>) {
2892    self.center += displacement;
2893  }
2894  fn scale (&mut self, scale : Positive <S>) {
2895    self.radius *= scale;
2896  }
2897  fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
2898    let out = self.center + direction.normalized() * self.radius;
2899    (out, out.0.dot (*direction))
2900  }
2901}
2902
2903impl_numcast_fields!(Sphere3, center, radius);
2904impl <S : OrderedRing> Sphere3 <S> {
2905  /// Unit sphere
2906  #[inline]
2907  pub fn unit() -> Self where S : Field {
2908    Sphere3 {
2909      center: Point3::origin(),
2910      radius: num::One::one()
2911    }
2912  }
2913  /// Discrete intersection test with another sphere
2914  #[inline]
2915  pub fn intersects (self, other : Self) -> bool {
2916    intersect::overlaps_sphere3_sphere3 (self, other)
2917  }
2918}
2919impl <S> Primitive <S, Point3 <S>> for Sphere3 <S> where
2920  S : Real + num::real::Real + std::fmt::Debug + MaybeSerDes
2921{
2922  fn translate (&mut self, displacement : Vector3 <S>) {
2923    self.center += displacement;
2924  }
2925  fn scale (&mut self, scale : Positive <S>) {
2926    self.radius *= scale;
2927  }
2928  fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
2929    let out = self.center + direction.normalized() * self.radius;
2930    (out, out.0.dot (*direction))
2931  }
2932}
2933
2934////////////////////////////////////////////////////////////////////////////////////////
2935//  tests                                                                             //
2936////////////////////////////////////////////////////////////////////////////////////////
2937
2938#[cfg(test)]
2939mod tests {
2940  use approx::{assert_relative_eq, assert_ulps_eq};
2941  use rand;
2942  use rand_distr;
2943  use rand_xorshift;
2944  use sorted_vec::partial::SortedSet;
2945  use super::*;
2946
2947  #[test]
2948  fn colinear_2d() {
2949    use rand::{RngExt, SeedableRng};
2950    use rand_xorshift::XorShiftRng;
2951    use rand_distr::Distribution;
2952    macro test ($t:ty) {
2953      let mut rng = XorShiftRng::seed_from_u64 (0);
2954      let normal  = rand_distr::StandardNormal;
2955      let cauchy  = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
2956      let randn   = |rng : &mut XorShiftRng| normal.sample (rng);
2957      let randf   = |rng : &mut XorShiftRng| cauchy.sample (rng);
2958      // colinear
2959      for _ in 0..100000 {
2960        let dir  = Unit2::<$t>::normalize ([randn (&mut rng), randn (&mut rng)].into());
2961        let base = point2 (randf (&mut rng), randf (&mut rng));
2962        let line = Line2::new (base, dir);
2963        let p1   = line.point (randf (&mut rng));
2964        let p2   = line.point (randf (&mut rng));
2965        let p3   = line.point (randf (&mut rng));
2966        assert!(colinear_2 (p1, p2, p3));
2967      }
2968      // not colinear
2969      for _ in 0..100000 {
2970        let p1        = point2 (randf (&mut rng), randf (&mut rng));
2971        let v         = p1.0;
2972        let len       = 0.1 + randf (&mut rng).abs();
2973        let angle     = Deg (rng.random_range (-180.0..180.0)).into();
2974        let rotation  = Rotation2::from_angle (angle);
2975        let p2        = p1 + rotation * (v.normalized() * len);
2976        let v         = p2 - p1;
2977        let len       = 0.1 + randf (&mut rng).abs();
2978        let mut angle = rng.random_range (5.0..175.0);
2979        if rng.random_bool (0.5) {
2980          angle = -angle;
2981        }
2982        let rotation = Rotation2::from_angle (Deg (angle).into());
2983        let p3       = p2 + rotation * v * len;
2984        assert!(!colinear_2 (p1, p2, p3));
2985      }
2986    }
2987    test!(f32);
2988    test!(f64);
2989  }
2990
2991  #[test]
2992  fn colinear_3d() {
2993    use rand::{RngExt, SeedableRng};
2994    use rand_xorshift::XorShiftRng;
2995    use rand_distr::Distribution;
2996    macro test ($t:ty) {
2997      let mut rng = XorShiftRng::seed_from_u64 (0);
2998      let normal  = rand_distr::StandardNormal;
2999      let cauchy  = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
3000      let randf   = |rng : &mut XorShiftRng| cauchy.sample (rng);
3001      let randn   = |rng : &mut XorShiftRng| normal.sample (rng);
3002      // colinear
3003      for _ in 0..50000 {
3004        let dir = Unit3::<$t>::normalize (
3005          [randn (&mut rng), randn (&mut rng), randn (&mut rng)].into());
3006        let base = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3007        let line = Line3::new (base, dir);
3008        let p1   = line.point (randf (&mut rng));
3009        let p2   = line.point (randf (&mut rng));
3010        let p3   = line.point (randf (&mut rng));
3011        assert!(colinear_3 (p1, p2, p3));
3012      }
3013      // not colinear
3014      for _ in 0..50000 {
3015        let p1       = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3016        let v        = p1.0;
3017        let len      = 0.1 + randf (&mut rng).abs();
3018        let axis     = vector3 (randn (&mut rng), randn (&mut rng), randn (&mut rng));
3019        let angle    = Deg (rng.random_range (-180.0..180.0)).into();
3020        let rotation = Rotation3::from_axis_angle (axis, angle);
3021        let p2       = p1 + rotation * (v.normalized() * len);
3022        let v        = p2 - p1;
3023        let len      = 0.1 + randf (&mut rng).abs();
3024        let axis     = loop {
3025          let axis = vector3 (randn (&mut rng), randn (&mut rng), randn (&mut rng));
3026          if axis.normalized().cross (v.normalized()).magnitude() > 0.1 {
3027            break axis
3028          }
3029        };
3030        let mut angle = rng.random_range (5.0..175.0);
3031        if rng.random_bool (0.5) {
3032          angle = -angle;
3033        }
3034        let rotation = Rotation3::from_axis_angle (axis, Deg (angle).into());
3035        let p3       = p2 + rotation * v * len;
3036        assert!(!colinear_3 (p1, p2, p3));
3037      }
3038    }
3039    test!(f32);
3040    test!(f64);
3041  }
3042
3043  #[test]
3044  fn coplanar_3d() {
3045    use rand::SeedableRng;
3046    use rand_xorshift::XorShiftRng;
3047    use rand_distr::Distribution;
3048    macro test ($t:ty) {
3049      let mut rng = XorShiftRng::seed_from_u64 (0);
3050      let cauchy  = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
3051      let randf   = |rng : &mut XorShiftRng| cauchy.sample (rng);
3052      // coplanar
3053      for _ in 0..50000 {
3054        let p1   = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3055        let p2   = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3056        let p3   = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3057        let s    = randf (&mut rng);
3058        let t    = randf (&mut rng);
3059        let p4   = p1 + (p2 - p1) * s + (p3 - p1) * t;
3060        assert!(coplanar_3::<$t> (p1, p2, p3, p4));
3061      }
3062      // not coplanar
3063      for _ in 0..50000 {
3064        let p1    = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3065        let p2    = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3066        let p3    = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
3067        let s     = randf (&mut rng);
3068        let t     = randf (&mut rng);
3069        let e1    = p2 - p1;
3070        let e2    = p3 - p1;
3071        let n     = e1.cross (e2).normalized();
3072        let rn    = randf (&mut rng);
3073        let pnew  = p1 + e1 * s + e2 * t;
3074        let n_len = (0.3 * (pnew - p1).magnitude()).mul_add (rn.signum(), rn);
3075        //let n_len = rn + 0.3 * (pnew - p1).magnitude() * rn.signum();
3076        let p4    = pnew + n * n_len;
3077        assert!(!coplanar_3::<$t> (p1, p2, p3, p4));
3078      }
3079    }
3080    test!(f32);
3081    test!(f64);
3082  }
3083
3084  #[test]
3085  fn triangle_area_squared() {
3086    assert_relative_eq!(
3087      81.0,
3088      *triangle_3d_area_squared (
3089        [-2.0, -1.0,  0.0].into(),
3090        [ 1.0, -1.0,  0.0].into(),
3091        [ 1.0,  5.0,  0.0].into())
3092    );
3093    assert_relative_eq!(
3094      20.25,
3095      *triangle_3d_area_squared (
3096        [-2.0, -1.0,  0.0].into(),
3097        [ 1.0, -1.0,  0.0].into(),
3098        [ 1.0,  2.0,  0.0].into())
3099    );
3100    assert_relative_eq!(
3101      20.25,
3102      *triangle_3d_area_squared (
3103        [ 1.0, -1.0,  0.0].into(),
3104        [-2.0, -1.0,  0.0].into(),
3105        [ 1.0,  2.0,  0.0].into())
3106    );
3107    assert_relative_eq!(
3108      20.25,
3109      *triangle_3d_area_squared (
3110        [ 1.0, -1.0,  0.0].into(),
3111        [ 1.0,  2.0,  0.0].into(),
3112        [-2.0, -1.0,  0.0].into())
3113    );
3114  }
3115
3116  #[test]
3117  fn project_point_on_line_2d() {
3118    use super::project_point2_on_line2;
3119    use Unit2;
3120    let point : Point2 <f64> = [2.0, 2.0].into();
3121    let line = Line2::<f64>::new ([0.0, 0.0].into(), Unit2::axis_x());
3122    assert_eq!(project_point2_on_line2 (point, line).1, [2.0, 0.0].into());
3123    let line = Line2::<f64>::new ([0.0, 0.0].into(), Unit2::axis_y());
3124    assert_eq!(project_point2_on_line2 (point, line).1, [0.0, 2.0].into());
3125    let point : Point2 <f64> = [0.0, 1.0].into();
3126    let line = Line2::<f64>::new (
3127      [0.0, -1.0].into(), Unit2::normalize ([1.0, 1.0].into()));
3128    assert_relative_eq!(
3129      project_point2_on_line2 (point, line).1, [1.0, 0.0].into());
3130    // the answer should be the same for lines with equivalent definitions
3131    let point : Point2 <f64> = [1.0, 3.0].into();
3132    let line_a = Line2::<f64>::new (
3133      [0.0, -1.0].into(), Unit2::normalize ([2.0, 1.0].into()));
3134    let line_b = Line2::<f64>::new (
3135      [2.0, 0.0].into(),  Unit2::normalize ([2.0, 1.0].into()));
3136    assert_relative_eq!(
3137      project_point2_on_line2 (point, line_a).1,
3138      project_point2_on_line2 (point, line_b).1);
3139    let line_a = Line2::<f64>::new (
3140      [0.0, 0.0].into(),   Unit2::normalize ([1.0, 1.0].into()));
3141    let line_b = Line2::<f64>::new (
3142      [-2.0, -2.0].into(), Unit2::normalize ([1.0, 1.0].into()));
3143    assert_ulps_eq!(
3144      project_point2_on_line2 (point, line_a).1,
3145      project_point2_on_line2 (point, line_b).1
3146    );
3147    assert_relative_eq!(
3148      project_point2_on_line2 (point, line_a).1,
3149      [2.0, 2.0].into());
3150  }
3151
3152  #[test]
3153  fn project_point_on_line_3d() {
3154    use Unit3;
3155    use super::project_point3_on_line3;
3156    // all the tests from 2d projection with 0.0 for the Z component
3157    let point : Point3 <f64> = [2.0, 2.0, 0.0].into();
3158    let line = Line3::<f64>::new ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
3159    assert_eq!(project_point3_on_line3 (point, line).1, [2.0, 0.0, 0.0].into());
3160    let line = Line3::<f64>::new ([0.0, 0.0, 0.0].into(), Unit3::axis_y());
3161    assert_eq!(project_point3_on_line3 (point, line).1, [0.0, 2.0, 0.0].into());
3162    let point : Point3 <f64> = [0.0, 1.0, 0.0].into();
3163    let line = Line3::<f64>::new (
3164      [0.0, -1.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
3165    assert_relative_eq!(
3166      project_point3_on_line3 (point, line).1, [1.0, 0.0, 0.0].into());
3167    // the answer should be the same for lines with equivalent definitions
3168    let point : Point3 <f64> = [1.0, 3.0, 0.0].into();
3169    let line_a = Line3::<f64>::new (
3170      [0.0, -1.0, 0.0].into(), Unit3::normalize ([2.0, 1.0, 0.0].into()));
3171    let line_b = Line3::<f64>::new (
3172      [2.0, 0.0, 0.0].into(),  Unit3::normalize ([2.0, 1.0, 0.0].into()));
3173    assert_relative_eq!(
3174      project_point3_on_line3 (point, line_a).1,
3175      project_point3_on_line3 (point, line_b).1);
3176    let line_a = Line3::<f64>::new (
3177      [0.0, 0.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
3178    let line_b = Line3::<f64>::new (
3179      [2.0, 2.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
3180    assert_relative_eq!(
3181      project_point3_on_line3 (point, line_a).1,
3182      project_point3_on_line3 (point, line_b).1);
3183    assert_relative_eq!(
3184      project_point3_on_line3 (point, line_a).1,
3185      [2.0, 2.0, 0.0].into());
3186    // more tests
3187    let point : Point3 <f64> = [0.0, 0.0, 2.0].into();
3188    let line_a = Line3::<f64>::new (
3189      [-4.0, -4.0, -4.0].into(), Unit3::normalize ([1.0, 1.0, 1.0].into()));
3190    let line_b = Line3::<f64>::new (
3191      [4.0, 4.0, 4.0].into(),    Unit3::normalize ([1.0, 1.0, 1.0].into()));
3192    assert_relative_eq!(
3193      project_point3_on_line3 (point, line_a).1,
3194      project_point3_on_line3 (point, line_b).1,
3195      epsilon = 0.000_000_000_000_01
3196    );
3197    assert_relative_eq!(
3198      project_point3_on_line3 (point, line_a).1,
3199      [2.0/3.0, 2.0/3.0, 2.0/3.0].into(),
3200      epsilon = 0.000_000_000_000_01
3201    );
3202  }
3203
3204  #[test]
3205  fn smallest_rect() {
3206    use super::smallest_rect;
3207    // points are in counter-clockwise order
3208    let points : Vec <Point2 <f32>> = [
3209      [-3.0, -3.0],
3210      [ 3.0, -3.0],
3211      [ 3.0,  3.0],
3212      [ 0.0,  6.0],
3213      [-3.0,  3.0]
3214    ].map (Point2::from).into_iter().collect();
3215    let hull = Hull2::from_points (points.as_slice()).unwrap();
3216    assert_eq!(hull.points(), points);
3217    let rect = smallest_rect (0, 1, &hull);
3218    assert_eq!(rect.area, 54.0);
3219    // points are in counter-clockwise order
3220    let points : Vec <Point2 <f32>> = [
3221      [-1.0, -4.0],
3222      [ 2.0,  2.0],
3223      [-4.0, -1.0]
3224    ].map (Point2::from).into_iter().collect();
3225    let hull = Hull2::from_points (points.as_slice()).unwrap();
3226    assert_eq!(hull.points(), points);
3227    let rect = smallest_rect (0, 1, &hull);
3228    assert_eq!(rect.area, 27.0);
3229  }
3230
3231  #[test]
3232  fn capsule3() {
3233    use num::One;
3234    let capsule : Capsule3 <f32> = Capsule3 {
3235      center:      Point3::origin(),
3236      radius:      Positive::noisy (2.0),
3237      half_height: Positive::one()
3238    };
3239    let support = capsule.support (Unit3::axis_z().into()).0;
3240    assert_eq!(support, point3 (0.0, 0.0, 3.0));
3241    let support = capsule.support (Unit3::axis_x().into()).0;
3242    assert_eq!(support, point3 (2.0, 0.0, 0.0));
3243    let support = capsule.support (NonZero3::noisy (vector3 (1.0, 0.0, 1.0))).0;
3244    assert_eq!(support,
3245      point3 (0.0, 0.0, 1.0) + *Unit3::normalize (vector3 (1.0, 0.0, 1.0)) * 2.0);
3246  }
3247
3248  #[test]
3249  fn cylinder3() {
3250    use num::One;
3251    let cylinder : Cylinder3 <f32> = Cylinder3 {
3252      center:      Point3::origin(),
3253      radius:      Positive::noisy (2.0),
3254      half_height: Positive::one()
3255    };
3256    let support = cylinder.support (Unit3::axis_z().into()).0;
3257    assert_eq!(support, point3 (0.0, 0.0, 1.0));
3258    let support = cylinder.support (Unit3::axis_x().into()).0;
3259    assert_eq!(support, point3 (2.0, 0.0, 0.0));
3260    let support = cylinder.support (NonZero3::noisy (vector3 (1.0, 0.0, 1.0))).0;
3261    assert_eq!(support, point3 (2.0, 0.0, 1.0));
3262  }
3263
3264  #[test]
3265  fn obb2() {
3266    let points : Vec <Point2 <f32>> = [
3267      [-1.0, -4.0],
3268      [ 2.0,  2.0],
3269      [-4.0, -1.0]
3270    ].map (Point2::from).into_iter().collect();
3271    let obb = Obb2::containing_points (&points).unwrap();
3272    let corners = obb.corners();
3273    assert_eq!(obb.center, [-0.25, -0.25].into());
3274    approx::assert_relative_eq!(point2 (-4.0, -1.0), corners[0], max_relative = 0.00001);
3275    approx::assert_relative_eq!(point2 ( 0.5,  3.5), corners[1], max_relative = 0.00001);
3276    approx::assert_relative_eq!(point2 (-1.0, -4.0), corners[2], max_relative = 0.00001);
3277    approx::assert_relative_eq!(point2 ( 3.5,  0.5), corners[3], max_relative = 0.00001);
3278    // test support
3279    let points : Vec <Point2 <f32>> = [
3280      [-2.0,  0.0],
3281      [ 0.0,  2.0],
3282      [ 2.0,  0.0],
3283      [ 0.0, -2.0],
3284    ].map (Point2::from).into_iter().collect();
3285    let obb = Obb2::containing_points (&points).unwrap();
3286    let support = obb.support (Unit2::axis_y().into()).0;
3287    approx::assert_relative_eq!(support, point2 (0.0, 2.0), epsilon = 0.000_001);
3288  }
3289
3290  #[test]
3291  #[expect(clippy::suboptimal_flops)]
3292  fn obb3() {
3293    use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
3294    use approx::AbsDiffEq;
3295
3296    let points = [
3297      [ 1.0, -1.0, -1.0],
3298      [ 1.0, -1.0,  1.0],
3299      [ 1.0,  1.0, -1.0],
3300      [ 1.0,  1.0,  1.0],
3301      [-1.0, -1.0, -1.0],
3302      [-1.0, -1.0,  1.0],
3303      [-1.0,  1.0, -1.0],
3304      [-1.0,  1.0,  1.0]
3305    ].map (Point3::<f32>::from);
3306    let obb1 = Obb3::containing_points_with_orientation (&points, Angles3::zero())
3307      .unwrap();
3308    assert_eq!(
3309      SortedSet::from_unsorted (obb1.corners().to_vec()),
3310      SortedSet::from_unsorted (
3311        Aabb3::containing (&points).unwrap().corners().to_vec()));
3312    let obb2 = Obb3::containing_points_approx (&points).unwrap();
3313    assert_eq!(obb1, obb2);
3314
3315    let rotation = Rotation3::from_angle_y (FRAC_PI_4.into());
3316    let points = points.map (|p| rotation.rotate (p));
3317    let obb1 = Obb3::containing_points_with_orientation (&points,
3318      // because the cube is symmetrical, it gets oriented by one of the faces
3319      Angles3::wrap (
3320        (3.0 * FRAC_PI_2).into(),
3321        (2.0 * PI - FRAC_PI_4).into(),
3322        FRAC_PI_2.into())
3323    ).unwrap();
3324    let obb2 = Obb3::containing_points_approx (&points).unwrap();
3325    approx::assert_abs_diff_eq!(obb1.center, obb2.center);
3326    assert_eq!(obb1.orientation, obb2.orientation);
3327    approx::assert_abs_diff_eq!(*obb1.extents.x, *obb2.extents.x,
3328      epsilon=4.0 * f32::default_epsilon());
3329    approx::assert_abs_diff_eq!(*obb1.extents.y, *obb2.extents.y,
3330      epsilon=4.0 * f32::default_epsilon());
3331    approx::assert_abs_diff_eq!(*obb1.extents.z, *obb2.extents.z,
3332      epsilon=2.0 * f32::default_epsilon());
3333    // because of rounding errors we can't sort the points to compare
3334    let corners = obb2.corners();
3335    approx::assert_abs_diff_eq!(corners[0], points[7],
3336      epsilon=8.0 * f32::default_epsilon());
3337    approx::assert_abs_diff_eq!(corners[1], points[5],
3338      epsilon=8.0 * f32::default_epsilon());
3339    approx::assert_abs_diff_eq!(corners[2], points[3],
3340      epsilon=8.0 * f32::default_epsilon());
3341    approx::assert_abs_diff_eq!(corners[3], points[1],
3342      epsilon=8.0 * f32::default_epsilon());
3343    approx::assert_abs_diff_eq!(corners[4], points[6],
3344      epsilon=8.0 * f32::default_epsilon());
3345    approx::assert_abs_diff_eq!(corners[5], points[4],
3346      epsilon=8.0 * f32::default_epsilon());
3347    approx::assert_abs_diff_eq!(corners[6], points[2],
3348      epsilon=8.0 * f32::default_epsilon());
3349    approx::assert_abs_diff_eq!(corners[7], points[0],
3350      epsilon=8.0 * f32::default_epsilon());
3351
3352    // test support
3353    let points : Vec <Point3 <f32>> = [
3354      [-2.0,  0.0, -2.0],
3355      [ 0.0,  2.0, -2.0],
3356      [ 2.0,  0.0, -2.0],
3357      [ 0.0, -2.0, -2.0],
3358      [-2.0,  0.0,  2.0],
3359      [ 0.0,  2.0,  2.0],
3360      [ 2.0,  0.0,  2.0],
3361      [ 0.0, -2.0,  2.0]
3362    ].map (Point3::from).into_iter().collect();
3363    let obb = Obb3::containing_points_approx (&points).unwrap();
3364    let support = obb.support (Unit3::axis_y().into()).0;
3365    approx::assert_relative_eq!(support, point3 (0.0, 2.0, 0.0), epsilon = 0.000_001);
3366    let support = obb.support (NonZero3::noisy (vector3 (0.0, 1.0, 1.0))).0;
3367    approx::assert_relative_eq!(support, point3 (0.0, 2.0, 2.0), epsilon = 0.000_001);
3368  }
3369
3370  #[test]
3371  fn support_aabb3() {
3372    let aabb = Aabb3::with_minmax (
3373      [-1.0, -2.0, -3.0].into(),
3374      [ 1.0,  2.0,  3.0].into()).unwrap();
3375    assert_eq!(
3376      [-1.0, -2.0, -3.0],
3377      aabb.support (NonZero3::noisy ([-1.0, -1.0, -1.0].into())).0.0.into_array());
3378    assert_eq!(
3379      [-1.0, -2.0,  3.0],
3380      aabb.support (NonZero3::noisy ([-1.0, -1.0,  1.0].into())).0.0.into_array());
3381    assert_eq!(
3382      [-1.0,  2.0, -3.0],
3383      aabb.support (NonZero3::noisy ([-1.0,  1.0, -1.0].into())).0.0.into_array());
3384    assert_eq!(
3385      [-1.0,  2.0,  3.0],
3386      aabb.support (NonZero3::noisy ([-1.0,  1.0,  1.0].into())).0.0.into_array());
3387    assert_eq!(
3388      [ 1.0, -2.0, -3.0],
3389      aabb.support (NonZero3::noisy ([ 1.0, -1.0, -1.0].into())).0.0.into_array());
3390    assert_eq!(
3391      [ 1.0, -2.0,  3.0],
3392      aabb.support (NonZero3::noisy ([ 1.0, -1.0,  1.0].into())).0.0.into_array());
3393    assert_eq!(
3394      [ 1.0,  2.0, -3.0],
3395      aabb.support (NonZero3::noisy ([ 1.0,  1.0, -1.0].into())).0.0.into_array());
3396    assert_eq!(
3397      [ 1.0,  2.0,  3.0],
3398      aabb.support (NonZero3::noisy ([ 1.0,  1.0,  1.0].into())).0.0.into_array());
3399
3400    assert_eq!(
3401      [-1.0, -2.0,  0.0],
3402      aabb.support (NonZero3::noisy ([-1.0, -1.0,  0.0].into())).0.0.into_array());
3403    assert_eq!(
3404      [-1.0,  2.0,  0.0],
3405      aabb.support (NonZero3::noisy ([-1.0,  1.0,  0.0].into())).0.0.into_array());
3406    assert_eq!(
3407      [ 1.0, -2.0,  0.0],
3408      aabb.support (NonZero3::noisy ([ 1.0, -1.0,  0.0].into())).0.0.into_array());
3409    assert_eq!(
3410      [ 1.0,  2.0,  0.0],
3411      aabb.support (NonZero3::noisy ([ 1.0,  1.0,  0.0].into())).0.0.into_array());
3412
3413    assert_eq!(
3414      [-1.0,  0.0, -3.0],
3415      aabb.support (NonZero3::noisy ([-1.0,  0.0, -1.0].into())).0.0.into_array());
3416    assert_eq!(
3417      [-1.0,  0.0,  3.0],
3418      aabb.support (NonZero3::noisy ([-1.0,  0.0,  1.0].into())).0.0.into_array());
3419    assert_eq!(
3420      [ 1.0,  0.0, -3.0],
3421      aabb.support (NonZero3::noisy ([ 1.0,  0.0, -1.0].into())).0.0.into_array());
3422    assert_eq!(
3423      [ 1.0,  0.0,  3.0],
3424      aabb.support (NonZero3::noisy ([ 1.0,  0.0,  1.0].into())).0.0.into_array());
3425
3426    assert_eq!(
3427      [ 0.0, -2.0, -3.0],
3428      aabb.support (NonZero3::noisy ([ 0.0, -1.0, -1.0].into())).0.0.into_array());
3429    assert_eq!(
3430      [ 0.0, -2.0,  3.0],
3431      aabb.support (NonZero3::noisy ([ 0.0, -1.0,  1.0].into())).0.0.into_array());
3432    assert_eq!(
3433      [ 0.0,  2.0, -3.0],
3434      aabb.support (NonZero3::noisy ([ 0.0,  1.0, -1.0].into())).0.0.into_array());
3435    assert_eq!(
3436      [ 0.0,  2.0,  3.0],
3437      aabb.support (NonZero3::noisy ([ 0.0,  1.0,  1.0].into())).0.0.into_array());
3438
3439    assert_eq!(
3440      [-1.0,  0.0,  0.0],
3441      aabb.support (NonZero3::noisy ([-1.0,  0.0,  0.0].into())).0.0.into_array());
3442    assert_eq!(
3443      [ 1.0,  0.0,  0.0],
3444      aabb.support (NonZero3::noisy ([ 1.0,  0.0,  0.0].into())).0.0.into_array());
3445    assert_eq!(
3446      [ 0.0, -2.0,  0.0],
3447      aabb.support (NonZero3::noisy ([ 0.0, -1.0,  0.0].into())).0.0.into_array());
3448    assert_eq!(
3449      [ 0.0,  2.0,  0.0],
3450      aabb.support (NonZero3::noisy ([ 0.0,  1.0,  0.0].into())).0.0.into_array());
3451    assert_eq!(
3452      [ 0.0,  0.0, -3.0],
3453      aabb.support (NonZero3::noisy ([ 0.0,  0.0, -1.0].into())).0.0.into_array());
3454    assert_eq!(
3455      [ 0.0,  0.0,  3.0],
3456      aabb.support (NonZero3::noisy ([ 0.0,  0.0,  1.0].into())).0.0.into_array());
3457  }
3458} // end tests