use rand;
use crate::*;
use super::{intersect, shape};
use super::mesh::VertexEdgeTriangleMesh;
#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};
pub mod integer;
mod hull;
mod simplex;
pub use self::hull::*;
pub use self::simplex::{simplex2, simplex3, Simplex2, Simplex3};
pub use simplex2::{
Segment as Segment2,
Triangle as Triangle2,
SegmentPoint as Segment2Point,
TrianglePoint as Triangle2Point
};
pub use simplex3::{
Segment as Segment3,
Triangle as Triangle3,
Tetrahedron as Tetrahedron3,
SegmentPoint as Segment3Point,
TrianglePoint as Triangle3Point,
TetrahedronPoint as Tetrahedron3Point
};
pub type Line2Point <S> = (S, Point2 <S>);
pub type Line3Point <S> = (S, Point3 <S>);
pub type Ray2Point <S> = (NonNegative <S>, Point2 <S>);
pub type Ray3Point <S> = (NonNegative <S>, Point3 <S>);
pub trait Primitive <S, P> : std::fmt::Debug where
S : Field,
P : AffineSpace <S>
{
fn translate (&mut self, displacement : P::Translation);
fn scale (&mut self, scale : Positive <S>);
fn support (&self, direction : <P::Translation as VectorSpace <S>>::NonZero)
-> (P, S);
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Interval <S> {
min : S,
max : S
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Aabb2 <S> {
min : Point2 <S>,
max : Point2 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Aabb3 <S> {
min : Point3 <S>,
max : Point3 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Obb2 <S> {
pub center : Point2 <S>,
pub extents : Vector2 <Positive <S>>,
pub orientation : AngleWrapped <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Obb3 <S> {
pub center : Point3 <S>,
pub extents : Vector3 <Positive <S>>,
pub orientation : Angles3 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Cylinder3 <S> {
pub center : Point3 <S>,
pub half_height : Positive <S>,
pub radius : Positive <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Capsule3 <S> {
pub center : Point3 <S>,
pub half_height : Positive <S>,
pub radius : Positive <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Cone3 <S> {
pub center : Point3 <S>,
pub half_height : Positive <S>,
pub radius : Positive <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Line2 <S> {
pub base : Point2 <S>,
pub direction : Unit2 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Ray2 <S> {
pub base : Point2 <S>,
pub direction : Unit2 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Line3 <S> {
pub base : Point3 <S>,
pub direction : Unit3 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Ray3 <S> {
pub base : Point3 <S>,
pub direction : Unit3 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Plane3 <S> {
pub base : Point3 <S>,
pub normal : Unit3 <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Sphere2 <S> {
pub center : Point2 <S>,
pub radius : Positive <S>
}
#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Sphere3 <S> {
pub center : Point3 <S>,
pub radius : Positive <S>
}
#[derive(Clone, Debug)]
struct Rect <S> {
pub edge : Vector2 <S>,
pub perp : Vector2 <S>,
pub indices : [u32; 4],
pub area : S,
pub edge_len_squared : S
}
pub fn colinear_2d <S> (a : Point2 <S>, b : Point2 <S>, c : Point2 <S>) -> bool where
S : OrderedField + approx::AbsDiffEq <Epsilon = S>
{
use num::Zero;
let max_point_max_norm = a.0.norm_max().max (b.0.norm_max()).max (c.0.norm_max());
let min_edge_max_norm = (b - a).norm_max().min ((c - a).norm_max());
let ratio = if min_edge_max_norm == NonNegative::zero() {
NonNegative::zero()
} else {
max_point_max_norm / min_edge_max_norm
};
let epsilon = S::default_epsilon() * S::eight() * (S::one() + *ratio);
colinear_2d_epsilon (a, b, c, epsilon)
}
pub fn colinear_2d_epsilon <S> (
a : Point2 <S>, b : Point2 <S>, c : Point2 <S>, epsilon : S
) -> bool where S : OrderedField {
if a == b || a == c || b == c {
return true
}
let e1 = b - a;
let e2 = c - a;
let e1_max = e1.norm_max();
let e2_max = e2.norm_max();
if *e1_max == S::zero() || *e2_max == S::zero() {
return true
}
let u = e1 / *e1_max;
let v = e2 / *e2_max;
let u_mag_sq = u.magnitude_squared();
let v_mag_sq = v.magnitude_squared();
let cross = u.exterior_product (v);
cross * cross <= epsilon * epsilon * u_mag_sq * v_mag_sq
}
pub fn colinear_3d <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>) -> bool where
S : OrderedField + approx::AbsDiffEq <Epsilon = S>
{
use num::Zero;
let max_epsilon = S::two().powi (-3);
let max_point_max_norm = a.0.norm_max().max (b.0.norm_max()).max (c.0.norm_max());
let min_edge_max_norm = (b - a).norm_max().min ((c - a).norm_max());
let ratio = if min_edge_max_norm == NonNegative::zero() {
NonNegative::zero()
} else {
max_point_max_norm / min_edge_max_norm
};
let epsilon = (S::default_epsilon() * S::eight() * (S::one() + *ratio))
.min (max_epsilon);
colinear_3d_epsilon (a, b, c, epsilon)
}
pub fn colinear_3d_epsilon <S> (
a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, epsilon : S
) -> bool where
S : OrderedField
{
if a == b || a == c || b == c {
return true
}
let e1 = b - a;
let e2 = c - a;
let e1_max = e1.norm_max();
let e2_max = e2.norm_max();
if *e1_max == S::zero() || *e2_max == S::zero() {
return true
}
let u = e1 / *e1_max;
let v = e2 / *e2_max;
let u_mag_sq = u.magnitude_squared();
let v_mag_sq = v.magnitude_squared();
let cross_sq = u.cross (v).magnitude_squared();
cross_sq <= epsilon * epsilon * u_mag_sq * v_mag_sq
}
pub fn coplanar_3d <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>)
-> bool
where S : OrderedField + approx::AbsDiffEq <Epsilon = S> {
use num::Zero;
let max_epsilon = S::two().powi (-2);
let max_point_max_norm =
a.0.norm_max().max (b.0.norm_max()).max (c.0.norm_max()).max (d.0.norm_max());
let min_edge_max_norm =
(b - a).norm_max().min ((c - a).norm_max()).min ((d - a).norm_max());
let ratio = if min_edge_max_norm == NonNegative::zero() {
NonNegative::zero()
} else {
max_point_max_norm / min_edge_max_norm
};
let epsilon = (S::default_epsilon() * S::eight() * (S::one() + *ratio))
.min (max_epsilon);
coplanar_3d_epsilon (a, b, c, d, epsilon)
}
pub fn coplanar_3d_epsilon <S> (
a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>, epsilon : S
) -> bool where
S : OrderedField + approx::AbsDiffEq <Epsilon = S>
{
let ab = b - a;
let ac = c - a;
let ad = d - a;
let ab_max_norm = ab.norm_max();
let ac_max_norm = ac.norm_max();
let ad_max_norm = ad.norm_max();
if *ab_max_norm == S::zero() || *ac_max_norm == S::zero()
|| *ad_max_norm == S::zero()
{
return true
}
let u = ab / *ab_max_norm;
let v = ac / *ac_max_norm;
let w = ad / *ad_max_norm;
let n = u.cross (v);
let n_sq = n.magnitude_squared();
if n_sq == S::zero() {
return true
}
let tp = n.dot (w);
let w_sq = w.magnitude_squared();
tp * tp <= epsilon * epsilon * n_sq * w_sq
}
pub fn plane_side_3d <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>)
-> S
where S : OrderedField + approx::AbsDiffEq <Epsilon = S> {
use num::Zero;
let max_epsilon = S::two().powi (-2);
let max_point_max_norm =
a.0.norm_max().max (b.0.norm_max()).max (c.0.norm_max()).max (d.0.norm_max());
let min_edge_max_norm =
(b - a).norm_max().min ((c - a).norm_max()).min ((d - a).norm_max());
let ratio = if min_edge_max_norm == NonNegative::zero() {
NonNegative::zero()
} else {
max_point_max_norm / min_edge_max_norm
};
let epsilon = (S::default_epsilon() * S::eight() * (S::one() + *ratio))
.min (max_epsilon);
plane_side_3d_epsilon (a, b, c, d, epsilon)
}
pub fn plane_side_3d_epsilon <S> (
a : Point3 <S>, b : Point3 <S>, c : Point3 <S>, d : Point3 <S>, epsilon : S
) -> S where
S : OrderedField + approx::AbsDiffEq <Epsilon = S>
{
let ab = b - a;
let ac = c - a;
let ad = d - a;
let ab_max_norm = ab.norm_max();
let ac_max_norm = ac.norm_max();
let ad_max_norm = ad.norm_max();
if *ab_max_norm == S::zero() || *ac_max_norm == S::zero()
|| *ad_max_norm == S::zero()
{
return S::zero()
}
let u = ab / *ab_max_norm;
let v = ac / *ac_max_norm;
let w = ad / *ad_max_norm;
let n = u.cross (v);
let n_sq = n.magnitude_squared();
if n_sq == S::zero() {
return S::zero()
}
let tp = n.dot (w);
let w_sq = w.magnitude_squared();
if tp * tp <= epsilon * epsilon * n_sq * w_sq {
S::zero()
} else if tp > S::zero() {
S::one()
} else {
-S::one()
}
}
#[inline]
pub fn determinant_3d <S : Ring> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>) -> S {
Matrix3::from_col_arrays ([a.0.into_array(), b.0.into_array(), c.0.into_array()])
.determinant()
}
pub fn orthonormal_basis <S> (e1 : Unit3 <S>) -> [Unit3 <S>; 3] where S : Real {
use num::Zero;
let u = Unit3::axis_x();
let e2 = {
let mut e2 = (*e1).cross (*u);
e2.z += if e2.is_zero() {
S::one()
} else {
S::zero()
};
Unit3::new (e2).unwrap()
};
let e3 = Unit3::new ((*e1).cross (*e2)).unwrap();
[e1, e2, e3]
}
#[inline]
pub fn point2_min <S : PartialOrd> (a : Point2 <S>, b : Point2 <S>) -> Point2 <S> {
Vector2::partial_min (a.0, b.0).into()
}
#[inline]
pub fn point2_max <S : PartialOrd> (a : Point2 <S>, b : Point2 <S>) -> Point2 <S> {
Vector2::partial_max (a.0, b.0).into()
}
#[inline]
pub fn point3_min <S : PartialOrd> (a : Point3 <S>, b : Point3 <S>) -> Point3 <S> {
Vector3::partial_min (a.0, b.0).into()
}
#[inline]
pub fn point3_max <S : PartialOrd> (a : Point3 <S>, b : Point3 <S>) -> Point3 <S> {
Vector3::partial_max (a.0, b.0).into()
}
#[inline]
pub fn project_2d_point_on_line <S : OrderedRing> (point : Point2 <S>, line : Line2 <S>)
-> Line2Point <S>
{
let dot_dir = line.direction.dot (point - line.base);
(dot_dir, line.point (dot_dir))
}
#[inline]
pub fn project_3d_point_on_line <S : OrderedRing> (point : Point3 <S>, line : Line3 <S>)
-> Line3Point <S>
{
let dot_dir = line.direction.dot (point - line.base);
(dot_dir, line.point (dot_dir))
}
#[inline]
pub fn project_3d_point_on_plane <S : Real> (point : Point3 <S>, plane : Plane3 <S>)
-> Point3 <S>
{
let v = point - plane.base;
let distance = (*plane.normal).dot (v);
point - *plane.normal * distance
}
#[inline]
pub fn support2 <S> (points : &[Point2 <S>], direction : NonZero2 <S>) -> (Point2 <S>, S)
where S : Ring + PartialOrd
{
debug_assert!(!points.is_empty());
points.iter()
.map (|point| (*point, direction.dot (point.0)))
.max_by (|(_, dot_a), (_, dot_b)| dot_a.partial_cmp (dot_b).unwrap()).unwrap()
}
#[inline]
pub fn support3 <S> (points : &[Point3 <S>], direction : NonZero3 <S>) -> (Point3 <S>, S)
where S : Ring + PartialOrd
{
debug_assert!(!points.is_empty());
points.iter()
.map (|point| (*point, direction.dot (point.0)))
.max_by (|(_, dot_a), (_, dot_b)| dot_a.partial_cmp (dot_b).unwrap()).unwrap()
}
pub fn triangle_3d_area_squared <S> (a : Point3 <S>, b : Point3 <S>, c : Point3 <S>)
-> NonNegative <S>
where
S : OrderedField + Sqrt + approx::AbsDiffEq <Epsilon = S>
{
use num::Zero;
if a == b || a == c || b == c {
return NonNegative::zero()
}
let ab_mag = *(b-a).norm();
let ac_mag = *(c-a).norm();
let bc_mag = *(c-b).norm();
let min_ab_ac = S::min (ab_mag, ac_mag);
let max_ab_ac = S::max (ab_mag, ac_mag);
let (min, mid, max) = if bc_mag <= min_ab_ac {
(bc_mag, min_ab_ac, max_ab_ac)
} else if bc_mag >= max_ab_ac {
(min_ab_ac, max_ab_ac, bc_mag)
} else {
(min_ab_ac, bc_mag, max_ab_ac)
};
let frac = S::two().powi (-4);
let mut area_squared = frac
* (max + (mid + min))
* (min - (max - mid))
* (min + (max - mid))
* (max + (mid - min));
if area_squared < S::zero() {
if cfg!(debug_assertions) {
let lengths_squared = (b - a).magnitude_squared() + (c - b).magnitude_squared()
+ (a - c).magnitude_squared();
approx::assert_abs_diff_eq!(area_squared, S::zero(),
epsilon = S::default_epsilon() * S::two().powi (44) * lengths_squared)
}
area_squared = S::zero();
}
NonNegative::unchecked (area_squared)
}
fn smallest_rect <S : Real> (i0 : u32, i1 : u32, hull : &Hull2 <S>) -> Rect <S> {
let points = hull.points();
let edge = points[i1 as usize] - points[i0 as usize];
let perp = vector2 (-edge.y, edge.x);
let edge_len_squared = edge.magnitude_squared();
let origin = points[i1 as usize];
let mut support = [Point2::<S>::origin(); 4];
let mut rect_index = [i1, i1, i1, i1];
#[expect(clippy::cast_possible_truncation)]
for (i, point) in points.iter().enumerate() {
let diff = point - origin;
let v = point2 (edge.dot (diff), perp.dot (diff));
if v.x() > support[1].x() || v.x() == support[1].x() && v.y() > support[1].y() {
rect_index[1] = i as u32;
support[1] = v;
}
if v.y() > support[2].y() || v.y() == support[2].y() && v.x() < support[2].x() {
rect_index[2] = i as u32;
support[2] = v;
}
if v.x() < support[3].x() || v.x() == support[3].x() && v.y() < support[3].y() {
rect_index[3] = i as u32;
support[3] = v;
}
}
let scaled_width = support[1].x() - support[3].x();
let scaled_height = support[2].y();
let area = scaled_width * scaled_height / edge_len_squared;
Rect {
edge, perp, area, edge_len_squared,
indices: [rect_index[0], rect_index[1], rect_index[2], rect_index[3]]
}
}
impl <S : OrderedRing> Interval <S> {
#[inline]
pub fn from_points (a : S, b : S) -> Self {
let min = S::min (a, b);
let max = S::max (a, b);
Interval { min, max }
}
#[inline]
pub fn with_minmax (min : S, max : S) -> Option <Self> {
if min > max {
None
} else {
Some (Interval { min, max })
}
}
#[inline]
pub fn with_minmax_unchecked (min : S, max : S) -> Self {
debug_assert_eq!(min, S::min (min, max));
debug_assert_eq!(max, S::max (min, max));
Interval { min, max }
}
#[inline]
pub fn containing (points : &[S]) -> Option <Self> {
if points.len() < 2 {
None
} else {
debug_assert!(points.len() >= 2);
let mut min = S::min (points[0], points[1]);
let mut max = S::max (points[0], points[1]);
for point in points.iter().skip (2) {
min = S::min (min, *point);
max = S::max (max, *point);
}
Interval::with_minmax (min, max)
}
}
#[inline]
pub fn numcast <T> (self) -> Option <Interval <T>> where
S : num::ToPrimitive,
T : num::NumCast
{
Some (Interval {
min: T::from (self.min)?,
max: T::from (self.max)?
})
}
#[inline]
pub fn union (a : Interval <S>, b : Interval <S>) -> Self {
Interval::with_minmax_unchecked (
S::min (a.min(), b.min()), S::max (a.max(), b.max()))
}
#[inline]
pub const fn min (self) -> S {
self.min
}
#[inline]
pub const fn max (self) -> S {
self.max
}
#[inline]
pub fn length (self) -> NonNegative <S> {
self.width()
}
#[inline]
pub fn width (self) -> NonNegative <S> {
NonNegative::unchecked (self.max - self.min)
}
#[inline]
pub fn contains (self, point : S) -> bool {
self.min < point && point < self.max
}
#[inline]
pub fn clamp (self, point : S) -> S {
point.clamp (self.min, self.max)
}
#[inline]
pub fn rand_point <R> (self, rng : &mut R) -> S where
S : rand::distr::uniform::SampleUniform,
R : rand::RngExt
{
rng.random_range (self.min..self.max)
}
#[inline]
pub fn intersects (self, other : Interval <S>) -> bool {
intersect::discrete_interval (self, other)
}
#[inline]
pub fn intersection (self, other : Interval <S>) -> Option <Interval <S>> {
intersect::continuous_interval (self, other)
}
pub fn dilate (mut self, amount : S) -> Option <Self> {
self.min -= amount;
self.max += amount;
if self.min > self.max {
None
} else {
Some (self)
}
}
}
impl <S> Primitive <S, S> for Interval <S> where
S : OrderedField + AffineSpace <S, Translation=S>
+ VectorSpace <S, NonZero=NonZero <S>>
{
fn translate (&mut self, displacement : S) {
self.min += displacement;
self.max += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
let old_width = *self.width();
let new_width = old_width * *scale;
let half_difference = (new_width - old_width) / S::two();
self.min -= half_difference;
self.max += half_difference;
}
fn support (&self, direction : NonZero <S>) -> (S, S) {
if *direction > S::zero() {
(self.max, self.max * *direction)
} else {
(self.min, self.min * *direction)
}
}
}
impl <S> std::ops::Add <S> for Interval <S> where
S : Field + AffineSpace <S, Translation=S> + VectorSpace <S>,
Self : Primitive <S, S>
{
type Output = Self;
#[expect(clippy::renamed_function_params)]
fn add (mut self, displacement : S) -> Self {
self.translate (displacement);
self
}
}
impl <S> std::ops::Sub <S> for Interval <S> where
S : Field + AffineSpace <S, Translation=S> + VectorSpace <S>,
Self : Primitive <S, S>
{
type Output = Self;
#[expect(clippy::renamed_function_params)]
fn sub (mut self, displacement : S) -> Self {
self.translate (-displacement);
self
}
}
impl_numcast_fields!(Aabb2, min, max);
impl <S : OrderedRing> Aabb2 <S> {
#[inline]
pub fn with_minmax (min : Point2 <S>, max : Point2 <S>) -> Option <Self> {
if min == max || min != point2_min (min, max) || max != point2_max (min, max) {
None
} else {
Some (Aabb2 { min, max })
}
}
#[inline]
pub fn from_points (a : Point2 <S>, b : Point2 <S>) -> Option <Self> {
if a == b {
None
} else {
let min = point2_min (a, b);
let max = point2_max (a, b);
Some (Aabb2 { min, max })
}
}
#[inline]
pub fn with_minmax_unchecked (min : Point2 <S>, max : Point2 <S>) -> Self {
debug_assert_ne!(min, max);
debug_assert_eq!(min, point2_min (min, max));
debug_assert_eq!(max, point2_max (min, max));
Aabb2 { min, max }
}
#[inline]
pub fn from_points_unchecked (a : Point2 <S>, b : Point2 <S>) -> Self {
debug_assert_ne!(a, b);
let min = point2_min (a, b);
let max = point2_max (a, b);
Aabb2 { min, max }
}
#[inline]
pub fn containing (points : &[Point2 <S>]) -> Option <Self> {
if points.len() < 2 {
None
} else {
debug_assert!(points.len() >= 2);
let mut min = point2_min (points[0], points[1]);
let mut max = point2_max (points[0], points[1]);
for point in points.iter().skip (2) {
min = point2_min (min, *point);
max = point2_max (max, *point);
}
Aabb2::with_minmax (min, max)
}
}
#[inline]
pub fn union (a : Aabb2 <S>, b : Aabb2 <S>) -> Self {
Aabb2::with_minmax_unchecked (
point2_min (a.min(), b.min()), point2_max (a.max(), b.max()))
}
#[inline]
pub const fn min (self) -> Point2 <S> {
self.min
}
#[inline]
pub const fn max (self) -> Point2 <S> {
self.max
}
#[inline]
pub fn center (self) -> Point2 <S> {
Point2 ((self.min.0 + self.max.0) / S::two())
}
#[inline]
pub fn dimensions (self) -> Vector2 <NonNegative <S>> {
(self.max.0 - self.min.0).map (NonNegative::unchecked)
}
#[inline]
pub fn extents (self) -> Vector2 <NonNegative <S>> where S : Field {
self.dimensions().map (|d| d * NonNegative::unchecked (S::half()))
}
#[inline]
pub fn width (self) -> NonNegative <S> {
NonNegative::unchecked (self.max.x() - self.min.x())
}
#[inline]
pub fn height (self) -> NonNegative <S> {
NonNegative::unchecked (self.max.y() - self.min.y())
}
#[inline]
pub fn area (self) -> NonNegative <S> {
self.width() * self.height()
}
#[inline]
pub fn contains (self, point : Point2 <S>) -> bool {
self.min.x() < point.x() && point.x() < self.max.x() &&
self.min.y() < point.y() && point.y() < self.max.y()
}
pub fn clamp (self, point : Point2 <S>) -> Point2 <S> {
[ point.x().clamp (self.min.x(), self.max.x()),
point.y().clamp (self.min.y(), self.max.y())
].into()
}
#[inline]
pub fn rand_point <R> (self, rng : &mut R) -> Point2 <S> where
S : rand::distr::uniform::SampleUniform,
R : rand::RngExt
{
let x_range = self.min.x()..self.max.x();
let y_range = self.min.y()..self.max.y();
let x = if x_range.is_empty() {
self.min.x()
} else {
rng.random_range (x_range)
};
let y = if y_range.is_empty() {
self.min.y()
} else {
rng.random_range (y_range)
};
[x, y].into()
}
#[inline]
pub fn intersects (self, other : Aabb2 <S>) -> bool {
intersect::discrete_aabb2_aabb2 (self, other)
}
#[inline]
pub fn intersection (self, other : Aabb2 <S>) -> Option <Aabb2 <S>> {
intersect::continuous_aabb2_aabb2 (self, other)
}
pub fn corner (self, quadrant : Quadrant) -> Point2 <S> {
match quadrant {
Quadrant::PosPos => self.max,
Quadrant::PosNeg => [self.max.x(), self.min.y()].into(),
Quadrant::NegPos => [self.min.x(), self.max.y()].into(),
Quadrant::NegNeg => self.min
}
}
pub fn corners (self) -> [Point2 <S>; 4] {
[ self.min, [self.min.x(), self.max.y()].into(),
self.max, [self.max.x(), self.min.y()].into()
]
}
pub fn edge (self, direction : SignedAxis2) -> Self {
let (min, max) = match direction {
SignedAxis2::PosX => (
self.corner (Quadrant::PosNeg),
self.corner (Quadrant::PosPos) ),
SignedAxis2::NegX => (
self.corner (Quadrant::NegNeg),
self.corner (Quadrant::NegPos) ),
SignedAxis2::PosY => (
self.corner (Quadrant::NegPos),
self.corner (Quadrant::PosPos) ),
SignedAxis2::NegY => (
self.corner (Quadrant::NegNeg),
self.corner (Quadrant::PosNeg) )
};
Aabb2::with_minmax_unchecked (min, max)
}
pub fn extrude (self, axis : SignedAxis2, amount : Positive <S>) -> Self {
let (min, max) = match axis {
SignedAxis2::PosX => (
self.min + Vector2::zero().with_x (*self.width()),
self.max + Vector2::zero().with_x (*amount) ),
SignedAxis2::NegX => (
self.min - Vector2::zero().with_x (*amount),
self.max - Vector2::zero().with_x (*self.height()) ),
SignedAxis2::PosY => (
self.min + Vector2::zero().with_y (*self.height()),
self.max + Vector2::zero().with_y (*amount) ),
SignedAxis2::NegY => (
self.min - Vector2::zero().with_y (*amount),
self.max - Vector2::zero().with_y (*self.height()) )
};
Aabb2 { min, max }
}
pub fn extend (&mut self, axis : SignedAxis2, amount : Positive <S>) {
match axis {
SignedAxis2::PosX => self.max.0.x += *amount,
SignedAxis2::NegX => self.min.0.x -= *amount,
SignedAxis2::PosY => self.max.0.y += *amount,
SignedAxis2::NegY => self.min.0.y -= *amount
}
}
pub fn with_z (self, z : Interval <S>) -> Aabb3 <S> {
Aabb3::with_minmax_unchecked (
self.min.0.with_z (z.min()).into(),
self.max.0.with_z (z.max()).into()
)
}
pub fn dilate (mut self, amount : S) -> Option <Self> {
self.min -= Vector2::broadcast (amount);
self.max += Vector2::broadcast (amount);
if self.min == self.max || self.min != point2_min (self.min, self.max)
|| self.max != point2_max (self.min, self.max)
{
None
} else {
Some (self)
}
}
pub fn project (self, axis : Axis2) -> Interval <S> {
let (min, max) = match axis {
Axis2::X => (self.min.y(), self.max.y()),
Axis2::Y => (self.min.x(), self.max.x())
};
Interval::with_minmax_unchecked (min, max)
}
}
impl <S> Primitive <S, Point2 <S>> for Aabb2 <S> where S : OrderedField {
fn translate (&mut self, displacement : Vector2 <S>) {
self.min += displacement;
self.max += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
let center = self.center();
self.translate (-center.0);
self.min.0 *= *scale;
self.max.0 *= *scale;
self.translate (center.0)
}
fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
let out = if let Some (quadrant) = Quadrant::from_vec_strict (*direction) {
self.corner (quadrant)
} else if direction.x > S::zero() {
debug_assert_eq!(direction.y, S::zero());
point2 (self.max.x(), (self.min.y() + self.max.y()) / S::two())
} else if direction.x < S::zero() {
debug_assert_eq!(direction.y, S::zero());
point2 (self.min.x(), (self.min.y() + self.max.y()) / S::two())
} else {
debug_assert_eq!(direction.x, S::zero());
if direction.y > S::zero() {
point2 ((self.min.x() + self.max.x()) / S::two(), self.max.y())
} else {
debug_assert!(direction.y < S::zero());
point2 ((self.min.x() + self.max.x()) / S::two(), self.min.y())
}
};
(out, out.0.dot (*direction))
}
}
impl <S> std::ops::Add <Vector2 <S>> for Aabb2 <S> where
S : Field,
Self : Primitive <S, Point2 <S>>
{
type Output = Self;
#[expect(clippy::renamed_function_params)]
fn add (mut self, displacement : Vector2 <S>) -> Self {
self.translate (displacement);
self
}
}
impl <S> std::ops::Sub <Vector2 <S>> for Aabb2 <S> where
S : Field,
Self : Primitive <S, Point2 <S>>
{
type Output = Self;
#[expect(clippy::renamed_function_params)]
fn sub (mut self, displacement : Vector2 <S>) -> Self {
self.translate (-displacement);
self
}
}
impl_numcast_fields!(Aabb3, min, max);
impl <S : OrderedRing> Aabb3 <S> {
#[inline]
pub fn with_minmax (min : Point3 <S>, max : Point3 <S>) -> Option <Self> {
if min == max || min != point3_min (min, max) || max != point3_max (min, max) {
None
} else {
Some (Aabb3 { min, max })
}
}
#[inline]
pub fn from_points (a : Point3 <S>, b : Point3 <S>) -> Option <Self> {
if a == b {
None
} else {
let min = point3_min (a, b);
let max = point3_max (a, b);
Some (Aabb3 { min, max })
}
}
#[inline]
pub fn with_minmax_unchecked (min : Point3 <S>, max : Point3 <S>) -> Self {
debug_assert_ne!(min, max);
debug_assert_eq!(min, point3_min (min, max));
debug_assert_eq!(max, point3_max (min, max));
Aabb3 { min, max }
}
#[inline]
pub fn from_points_unchecked (a : Point3 <S>, b : Point3 <S>) -> Self {
debug_assert_ne!(a, b);
let min = point3_min (a, b);
let max = point3_max (a, b);
Aabb3 { min, max }
}
#[inline]
pub fn containing (points : &[Point3 <S>]) -> Option <Self> {
debug_assert!(points.len() >= 2);
let mut min = point3_min (points[0], points[1]);
let mut max = point3_max (points[0], points[1]);
for point in points.iter().skip (2) {
min = point3_min (min, *point);
max = point3_max (max, *point);
}
Aabb3::with_minmax (min, max)
}
#[inline]
pub fn union (a : Aabb3 <S>, b : Aabb3 <S>) -> Self {
Aabb3::with_minmax_unchecked (
point3_min (a.min(), b.min()), point3_max (a.max(), b.max()))
}
#[inline]
pub const fn min (self) -> Point3 <S> {
self.min
}
#[inline]
pub const fn max (self) -> Point3 <S> {
self.max
}
#[inline]
pub fn center (self) -> Point3 <S> {
Point3 ((self.min.0 + self.max.0) / S::two())
}
#[inline]
pub fn dimensions (self) -> Vector3 <NonNegative <S>> {
(self.max.0 - self.min.0).map (NonNegative::unchecked)
}
#[inline]
pub fn extents (self) -> Vector3 <NonNegative <S>> where S : Field {
self.dimensions().map (|d| d * NonNegative::unchecked (S::half()))
}
#[inline]
pub fn width (self) -> NonNegative <S> {
NonNegative::unchecked (self.max.x() - self.min.x())
}
#[inline]
pub fn depth (self) -> NonNegative <S> {
NonNegative::unchecked (self.max.y() - self.min.y())
}
#[inline]
pub fn height (self) -> NonNegative <S> {
NonNegative::unchecked (self.max.z() - self.min.z())
}
#[inline]
pub fn volume (self) -> NonNegative <S> {
self.width() * self.depth() * self.height()
}
#[inline]
pub fn contains (self, point : Point3 <S>) -> bool {
self.min.x() < point.x() && point.x() < self.max.x() &&
self.min.y() < point.y() && point.y() < self.max.y() &&
self.min.z() < point.z() && point.z() < self.max.z()
}
pub fn clamp (self, point : Point3 <S>) -> Point3 <S> {
[ point.x().clamp (self.min.x(), self.max.x()),
point.y().clamp (self.min.y(), self.max.y()),
point.z().clamp (self.min.z(), self.max.z())
].into()
}
#[inline]
pub fn rand_point <R> (self, rng : &mut R) -> Point3 <S> where
S : rand::distr::uniform::SampleUniform,
R : rand::RngExt
{
let x_range = self.min.x()..self.max.x();
let y_range = self.min.y()..self.max.y();
let z_range = self.min.z()..self.max.z();
let x = if x_range.is_empty() {
self.min.x()
} else {
rng.random_range (x_range)
};
let y = if y_range.is_empty() {
self.min.y()
} else {
rng.random_range (y_range)
};
let z = if z_range.is_empty() {
self.min.z()
} else {
rng.random_range (z_range)
};
[x, y, z].into()
}
#[inline]
pub fn intersects (self, other : Aabb3 <S>) -> bool {
intersect::discrete_aabb3_aabb3 (self, other)
}
#[inline]
pub fn intersection (self, other : Aabb3 <S>) -> Option <Aabb3 <S>> {
intersect::continuous_aabb3_aabb3 (self, other)
}
pub fn corner (self, octant : Octant) -> Point3 <S> {
match octant {
Octant::PosPosPos => self.max,
Octant::NegPosPos => [self.min.x(), self.max.y(), self.max.z()].into(),
Octant::PosNegPos => [self.max.x(), self.min.y(), self.max.z()].into(),
Octant::NegNegPos => [self.min.x(), self.min.y(), self.max.z()].into(),
Octant::PosPosNeg => [self.max.x(), self.max.y(), self.min.z()].into(),
Octant::NegPosNeg => [self.min.x(), self.max.y(), self.min.z()].into(),
Octant::PosNegNeg => [self.max.x(), self.min.y(), self.min.z()].into(),
Octant::NegNegNeg => self.min
}
}
pub fn corners (self) -> [Point3 <S>; 8] {
[ self.min,
[self.min.x(), self.max.y(), self.max.z()].into(),
[self.max.x(), self.min.y(), self.max.z()].into(),
[self.min.x(), self.min.y(), self.max.z()].into(),
[self.max.x(), self.max.y(), self.min.z()].into(),
[self.min.x(), self.max.y(), self.min.z()].into(),
[self.max.x(), self.min.y(), self.min.z()].into(),
self.max
]
}
pub fn face (self, direction : SignedAxis3) -> Self {
let (min, max) = match direction {
SignedAxis3::PosX => (
self.corner (Octant::PosNegNeg),
self.corner (Octant::PosPosPos) ),
SignedAxis3::NegX => (
self.corner (Octant::NegNegNeg),
self.corner (Octant::NegPosPos) ),
SignedAxis3::PosY => (
self.corner (Octant::NegPosNeg),
self.corner (Octant::PosPosPos) ),
SignedAxis3::NegY => (
self.corner (Octant::NegNegNeg),
self.corner (Octant::PosNegPos) ),
SignedAxis3::PosZ => (
self.corner (Octant::NegNegPos),
self.corner (Octant::PosPosPos) ),
SignedAxis3::NegZ => (
self.corner (Octant::NegNegNeg),
self.corner (Octant::PosPosNeg) )
};
Aabb3::with_minmax_unchecked (min, max)
}
pub fn extrude (self, axis : SignedAxis3, amount : Positive <S>) -> Self {
let (min, max) = match axis {
SignedAxis3::PosX => (
self.min + Vector3::zero().with_x (*self.width()),
self.max + Vector3::zero().with_x (*amount)
),
SignedAxis3::NegX => (
self.min - Vector3::zero().with_x (*amount),
self.max - Vector3::zero().with_x (*self.width())
),
SignedAxis3::PosY => (
self.min + Vector3::zero().with_y (*self.depth()),
self.max + Vector3::zero().with_y (*amount)
),
SignedAxis3::NegY => (
self.min - Vector3::zero().with_y (*amount),
self.max - Vector3::zero().with_y (*self.depth())
),
SignedAxis3::PosZ => (
self.min + Vector3::zero().with_z (*self.height()),
self.max + Vector3::zero().with_z (*amount)
),
SignedAxis3::NegZ => (
self.min - Vector3::zero().with_z (*amount),
self.max - Vector3::zero().with_z (*self.height())
)
};
Aabb3 { min, max }
}
pub fn extend (&mut self, axis : SignedAxis3, amount : Positive <S>) {
match axis {
SignedAxis3::PosX => self.max.0.x += *amount,
SignedAxis3::NegX => self.min.0.x -= *amount,
SignedAxis3::PosY => self.max.0.y += *amount,
SignedAxis3::NegY => self.min.0.y -= *amount,
SignedAxis3::PosZ => self.max.0.z += *amount,
SignedAxis3::NegZ => self.min.0.z -= *amount
}
}
pub fn dilate (mut self, amount : S) -> Option <Self> {
self.min -= Vector3::broadcast (amount);
self.max += Vector3::broadcast (amount);
if self.min == self.max || self.min != point3_min (self.min, self.max)
|| self.max != point3_max (self.min, self.max)
{
None
} else {
Some (self)
}
}
pub fn project (self, axis : Axis3) -> Aabb2 <S> {
let (min, max) = match axis {
Axis3::X => ([self.min.y(), self.min.z()], [self.max.y(), self.max.z()]),
Axis3::Y => ([self.min.x(), self.min.z()], [self.max.x(), self.max.z()]),
Axis3::Z => ([self.min.x(), self.min.y()], [self.max.x(), self.max.y()]),
};
Aabb2::with_minmax_unchecked (min.into(), max.into())
}
}
impl <S> Primitive <S, Point3 <S>> for Aabb3 <S> where S : OrderedField {
fn translate (&mut self, displacement : Vector3 <S>) {
self.min += displacement;
self.max += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
let center = self.center();
self.translate (-center.0);
self.min.0 *= *scale;
self.max.0 *= *scale;
self.translate (center.0);
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let out = if let Some (octant) = Octant::from_vec_strict (*direction) {
self.corner (octant)
} else {
use Sign::*;
let center = self.center();
match direction.map (S::sign).into_array() {
[Positive, Zero, Zero] => center.0.with_x (self.max.x()).into(),
[Negative, Zero, Zero] => center.0.with_x (self.min.x()).into(),
[Zero, Positive, Zero] => center.0.with_y (self.max.y()).into(),
[Zero, Negative, Zero] => center.0.with_y (self.min.y()).into(),
[Zero, Zero, Positive] => center.0.with_z (self.max.z()).into(),
[Zero, Zero, Negative] => center.0.with_z (self.min.z()).into(),
[Positive, Positive, Zero] =>
center.0.with_x (self.max.x()).with_y (self.max.y()).into(),
[Positive, Negative, Zero] =>
center.0.with_x (self.max.x()).with_y (self.min.y()).into(),
[Negative, Positive, Zero] =>
center.0.with_x (self.min.x()).with_y (self.max.y()).into(),
[Negative, Negative, Zero] =>
center.0.with_x (self.min.x()).with_y (self.min.y()).into(),
[Positive, Zero, Positive] =>
center.0.with_x (self.max.x()).with_z (self.max.z()).into(),
[Positive, Zero, Negative] =>
center.0.with_x (self.max.x()).with_z (self.min.z()).into(),
[Negative, Zero, Positive] =>
center.0.with_x (self.min.x()).with_z (self.max.z()).into(),
[Negative, Zero, Negative] =>
center.0.with_x (self.min.x()).with_z (self.min.z()).into(),
[Zero, Positive, Positive] =>
center.0.with_y (self.max.y()).with_z (self.max.z()).into(),
[Zero, Positive, Negative] =>
center.0.with_y (self.max.y()).with_z (self.min.z()).into(),
[Zero, Negative, Positive] =>
center.0.with_y (self.min.y()).with_z (self.max.z()).into(),
[Zero, Negative, Negative] =>
center.0.with_y (self.min.y()).with_z (self.min.z()).into(),
[_, _, _] => unreachable!()
}
};
(out, out.0.dot (*direction))
}
}
impl <S> std::ops::Add <Vector3 <S>> for Aabb3 <S> where
S : Field,
Self : Primitive <S, Point3 <S>>
{
type Output = Self;
#[expect(clippy::renamed_function_params)]
fn add (mut self, displacement : Vector3 <S>) -> Self {
self.translate (displacement);
self
}
}
impl <S> std::ops::Sub <Vector3 <S>> for Aabb3 <S> where
S : Field,
Self : Primitive <S, Point3 <S>>
{
type Output = Self;
#[expect(clippy::renamed_function_params)]
fn sub (mut self, displacement : Vector3 <S>) -> Self {
self.translate (-displacement);
self
}
}
impl_numcast_fields!(Obb2, center, extents, orientation);
impl <S : OrderedRing> Obb2 <S> {
#[inline]
pub const fn new (
center : Point2 <S>,
extents : Vector2 <Positive <S>>,
orientation : AngleWrapped <S>
) -> Self {
Obb2 { center, extents, orientation }
}
pub fn containing_points_with_orientation (
points : &[Point2 <S>], orientation : AngleWrapped <S>
) -> Option <Self> where
S : Real + num::real::Real + MaybeSerDes
{
let [x, y] = Rotation2::from_angle (orientation.angle()).cols
.map (NonZero2::unchecked).into_array();
let min_dot_x = -support2 (points, -x).1;
let max_dot_x = support2 (points, x).1;
let min_dot_y = -support2 (points, -y).1;
let max_dot_y = support2 (points, y).1;
let center = Point2 (
(*x * min_dot_x + *x * max_dot_x) * S::half() +
(*y * min_dot_y + *y * max_dot_y) * S::half());
let extents = vector2 (
Positive::new (S::half() * (max_dot_x - min_dot_x))?,
Positive::new (S::half() * (max_dot_y - min_dot_y))?);
Some (Obb2 { center, extents, orientation })
}
pub fn containing_points (points : &[Point2 <S>]) -> Option <Self> where S : Real {
let hull = Hull2::from_points (points)?;
Self::containing (&hull)
}
pub fn containing (hull : &Hull2 <S>) -> Option <Self> where S : Real {
let points = hull.points();
if points.len() < 3 {
return None
}
let mut visited = vec![false; points.len()];
#[expect(clippy::cast_possible_truncation)]
let mut min_rect = smallest_rect (points.len() as u32 - 1, 0, hull);
visited[min_rect.indices[0] as usize] = true;
let mut rect = min_rect.clone();
for _ in 0..points.len() {
let mut angles = [(S::zero(), u32::MAX); 4];
let mut nangles = u32::MAX;
if !compute_angles (hull, &rect, &mut angles, &mut nangles) {
break
}
let sorted = sort_angles (&angles, nangles);
if !update_support (&angles, nangles, &sorted, hull, &mut visited, &mut rect) {
break
}
if rect.area < min_rect.area {
min_rect = rect.clone();
}
}
let sum = [
points[min_rect.indices[1] as usize].0 + points[min_rect.indices[3] as usize].0,
points[min_rect.indices[2] as usize].0 + points[min_rect.indices[0] as usize].0
];
let diff = [
points[min_rect.indices[1] as usize].0 - points[min_rect.indices[3] as usize].0,
points[min_rect.indices[2] as usize].0 - points[min_rect.indices[0] as usize].0
];
let obb = {
let center = Point2::from ((min_rect.edge * min_rect.edge.dot (sum[0]) +
min_rect.perp * min_rect.perp.dot (sum[1]))
* S::half() / min_rect.edge_len_squared);
let extents = {
let extent_x = Positive::unchecked (S::sqrt (
(S::half() * min_rect.edge.dot (diff[0])).squared()
/ min_rect.edge_len_squared));
let extent_y = Positive::unchecked (S::sqrt (
(S::half() * min_rect.perp.dot (diff[1])).squared()
/ min_rect.edge_len_squared));
vector2 (extent_x, extent_y)
};
let orientation =
AngleWrapped::wrap (Rad (min_rect.edge.y.atan2 (min_rect.edge.x)));
Obb2 { center, extents, orientation }
};
fn compute_angles <S : Real> (
hull : &Hull2 <S>,
rect : &Rect <S>,
angles : &mut [(S, u32); 4],
nangles : &mut u32
) -> bool {
*nangles = 0;
let points = hull.points();
let mut k0 = 3;
let mut k1 = 0;
while k1 < 4 {
if rect.indices[k0] != rect.indices[k1] {
let d = {
let mut d = if k0 & 1 != 0 {
rect.perp
} else {
rect.edge
};
if k0 & 2 != 0 {
d = -d;
}
d
};
let j0 = rect.indices[k0];
let mut j1 = j0 + 1;
#[expect(clippy::cast_possible_truncation)]
if j1 == points.len() as u32 {
j1 = 0;
}
let e = points[j1 as usize] - points[j0 as usize];
let dp = d.dot ([e.y, -e.x].into());
let e_lensq = e.magnitude_squared();
let sin_theta_squared = (dp * dp) / e_lensq;
angles[*nangles as usize] =
#[expect(clippy::cast_possible_truncation)]
(sin_theta_squared, k0 as u32);
*nangles += 1;
}
k0 = k1;
k1 += 1;
}
*nangles > 0
}
fn sort_angles <S : PartialOrd> (angles : &[(S, u32); 4], nangles : u32)
-> [u32; 4]
{
let mut sorted = [0u32, 1, 2, 3];
match nangles {
0 | 1 => {}
2 => if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
sorted.swap (0, 1)
}
3 => {
if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
sorted.swap (0, 1)
}
if angles[sorted[0] as usize].0 > angles[sorted[2] as usize].0 {
sorted.swap (0, 2)
}
if angles[sorted[1] as usize].0 > angles[sorted[2] as usize].0 {
sorted.swap (1, 2)
}
}
4 => {
if angles[sorted[0] as usize].0 > angles[sorted[1] as usize].0 {
sorted.swap (0, 1)
}
if angles[sorted[2] as usize].0 > angles[sorted[3] as usize].0 {
sorted.swap (2, 3)
}
if angles[sorted[0] as usize].0 > angles[sorted[2] as usize].0 {
sorted.swap (0, 2)
}
if angles[sorted[1] as usize].0 > angles[sorted[3] as usize].0 {
sorted.swap (1, 3)
}
if angles[sorted[1] as usize].0 > angles[sorted[2] as usize].0 {
sorted.swap (1, 2)
}
}
_ => unreachable!()
}
sorted
}
fn update_support <S : Real> (
angles : &[(S, u32); 4],
nangles : u32,
sorted : &[u32; 4],
hull : &Hull2 <S>,
visited : &mut [bool],
rect : &mut Rect <S>
) -> bool {
let points = hull.points();
let min_angle = angles[sorted[0] as usize];
for k in 0..nangles as usize {
let (angle, index) = angles[sorted[k] as usize];
if angle == min_angle.0 {
rect.indices[index as usize] += 1;
#[expect(clippy::cast_possible_truncation)]
if rect.indices[index as usize] == points.len() as u32 {
rect.indices[index as usize] = 0;
}
}
}
let bottom = rect.indices[min_angle.1 as usize];
if visited[bottom as usize] {
return false
}
visited[bottom as usize] = true;
let mut next_index = [u32::MAX; 4];
for k in 0u32..4 {
next_index[k as usize] = rect.indices[((min_angle.1 + k) % 4) as usize];
}
rect.indices = next_index;
let j1 = rect.indices[0] as usize;
let j0 = if j1 == 0 {
points.len() - 1
} else {
j1 - 1
};
rect.edge = points[j1] - points[j0];
rect.perp = vector2 (-rect.edge.y, rect.edge.x);
let edge_len_squared = rect.edge.magnitude_squared();
let diff = [
points[rect.indices[1] as usize] - points[rect.indices[3] as usize],
points[rect.indices[2] as usize] - points[rect.indices[0] as usize]
];
rect.area = rect.edge.dot (diff[0]) * rect.perp.dot (diff[1]) / edge_len_squared;
true
}
Some (obb)
}
#[inline]
pub fn dimensions (self) -> Vector2 <Positive <S>> {
self.extents * Positive::unchecked (S::two())
}
#[inline]
pub fn width (self) -> Positive <S> {
self.extents.x * Positive::unchecked (S::two())
}
#[inline]
pub fn height (self) -> Positive <S> {
self.extents.y * Positive::unchecked (S::two())
}
#[inline]
pub fn area (self) -> Positive <S> {
self.width() * self.height()
}
#[inline]
pub fn corners (self) -> [Point2 <S>; 4] where
S : Real + num::real::Real + MaybeSerDes
{
let mut points = [Point2::origin(); 4];
for i in 0..2 {
for j in 0..2 {
let sign_x = if i % 2 == 0 { -S::one() } else { S::one() };
let sign_y = if j % 2 == 0 { -S::one() } else { S::one() };
points[i * 2 + j] = [*self.extents.x * sign_x, *self.extents.y * sign_y].into();
}
}
let rotation = Rotation2::from_angle (self.orientation.angle());
points.map (|point| rotation.rotate (point) + self.center.0)
}
pub fn aabb (self) -> Aabb2 <S> where S : Real + num::real::Real + MaybeSerDes {
Aabb2::containing (&self.corners()).unwrap()
}
pub fn contains (self, point : Point2 <S>) -> bool where
S : Real + num::real::Real + MaybeSerDes
{
let p = point - self.center.0;
let p = Rotation2::from_angle (-self.orientation.angle()).rotate (p);
p.x().abs() < *self.extents.x && p.y().abs() < *self.extents.y
}
pub fn dilate (mut self, amount : S) -> Self {
self.extents = self.extents.map (|e| e.map_noisy (|e| e + amount));
self
}
}
impl <S : Real> From <Aabb2 <S>> for Obb2 <S> {
fn from (aabb : Aabb2 <S>) -> Self {
use num::Zero;
let center = aabb.center();
let extents = vector2 (
Positive::noisy (*aabb.width() / S::two()),
Positive::noisy (*aabb.height() / S::two()));
let orientation = AngleWrapped::zero();
Obb2::new (center, extents, orientation)
}
}
impl <S> Primitive <S, Point2 <S>> for Obb2 <S> where
S : Real + num::real::Real + MaybeSerDes
{
fn translate (&mut self, displacement : Vector2 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.extents *= scale;
}
fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
use num::Inv;
let rotation = Rotation2::from_angle (self.orientation.angle());
let aabb = Aabb2::with_minmax_unchecked (
self.center - self.extents.map (|e| *e),
self.center + self.extents.map (|e| *e));
let aabb_direction = rotation.inv() * direction;
let (aabb_support, _) = aabb.support (aabb_direction);
let out = rotation.rotate_around (aabb_support, self.center);
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Obb3, center, extents, orientation);
impl <S : OrderedRing> Obb3 <S> {
#[inline]
pub const fn new (
center : Point3 <S>,
extents : Vector3 <Positive <S>>,
orientation : Angles3 <S>
) -> Self {
Obb3 { center, extents, orientation }
}
pub fn containing_points_with_orientation (
points : &[Point3 <S>], orientation : Angles3 <S>
) -> Option <Self> where
S : Real + num::real::Real + MaybeSerDes
{
let [x, y, z] = Rotation3::from (orientation).cols.map (NonZero3::unchecked)
.into_array();
let min_dot_x = -support3 (points, -x).1;
let max_dot_x = support3 (points, x).1;
let min_dot_y = -support3 (points, -y).1;
let max_dot_y = support3 (points, y).1;
let min_dot_z = -support3 (points, -z).1;
let max_dot_z = support3 (points, z).1;
let center = Point3 (
(*x * min_dot_x + *x * max_dot_x) * S::half() +
(*y * min_dot_y + *y * max_dot_y) * S::half() +
(*z * min_dot_z + *z * max_dot_z) * S::half());
let extents = vector3 (
Positive::new (S::half() * (max_dot_x - min_dot_x))?,
Positive::new (S::half() * (max_dot_y - min_dot_y))?,
Positive::new (S::half() * (max_dot_z - min_dot_z))?);
Some (Obb3 { center, extents, orientation })
}
pub fn containing_points_approx (points : &[Point3 <S>]) -> Option <Self> where
S : Real + num::real::Real + approx::RelativeEq <Epsilon=S> + MaybeSerDes
{
if points.len() < 4 {
return None
}
let (hull, mesh) = Hull3::from_points_with_mesh (points)?;
Self::containing_approx (&hull, &mesh)
}
pub fn containing_approx (hull : &Hull3 <S>, mesh : &VertexEdgeTriangleMesh)
-> Option <Self>
where S : Real + num::NumCast + approx::RelativeEq <Epsilon=S> + MaybeSerDes {
use num::Inv;
if hull.points().len() < 4 {
return None
}
let mut min_obb : Obb3 <S> = Aabb3::containing (hull.points()).unwrap().into();
let mut min_vol = min_obb.volume();
for triangle in mesh.triangles().values() {
let [a, b, c] = triangle.vertices().map (|i| hull.points()[i as usize]);
let mut u = b - a;
let mut v = c - a;
let mut w = u.cross (v);
v = w.cross (u);
u = *u.normalize();
v = *v.normalize();
w = *w.normalize();
let m = Matrix3::from_row_arrays (std::array::from_fn (|i| [u[i], v[i], w[i]]));
let m_rot = Rotation3::new_approx (m).unwrap();
let rot_inv = m_rot.inv();
let center = a;
let rotated = hull.points().iter().copied()
.map (|p| rot_inv.rotate_around (p, center))
.collect::<Vec <_>>();
let aabb = Aabb3::containing (rotated.as_slice()).unwrap();
let aabb_volume = aabb.volume();
if approx::abs_diff_eq!(*aabb_volume, S::zero()) {
return None
}
let volume = Positive::new (*aabb_volume).unwrap();
if volume < min_vol {
min_vol = volume;
min_obb = aabb.into();
min_obb.orientation = m_rot.into();
min_obb.center = m_rot.rotate_around (min_obb.center, center);
}
}
Some (min_obb)
}
pub fn containing_approx_pca (hull : &Hull3 <S>) -> Option <Self> where
S : Real + num::real::Real + num::NumCast + approx::RelativeEq <Epsilon=S>
+ MaybeSerDes
{
if hull.points().len() < 4 {
return None
}
let [p1, p2, p3] = &hull.points()[..3] else { unreachable!() };
let mut coplanar = true;
for p in &hull.points()[3..] {
if !coplanar_3d (*p1, *p2, *p3, *p) {
coplanar = false;
break
}
}
if coplanar {
return None
}
let orientation = data::pca_3 (hull.points()).into();
Some (Self::containing_points_with_orientation (hull.points(), orientation).unwrap())
}
#[inline]
pub fn dimensions (self) -> Vector3 <Positive <S>> {
self.extents * Positive::unchecked (S::two())
}
#[inline]
pub fn width (self) -> Positive <S> {
self.extents.x * Positive::unchecked (S::two())
}
#[inline]
pub fn depth (self) -> Positive <S> {
self.extents.y * Positive::unchecked (S::two())
}
#[inline]
pub fn height (self) -> Positive <S> {
self.extents.z * Positive::unchecked (S::two())
}
#[inline]
pub fn volume (self) -> Positive <S> {
self.width() * self.depth() * self.height()
}
#[inline]
pub fn corners (self) -> [Point3 <S>; 8] where
S : Real + num::real::Real + MaybeSerDes
{
let mut points = [Point3::origin(); 8];
for i in 0..2 {
for j in 0..2 {
for k in 0..2 {
let sign_x = if i % 2 == 0 { -S::one() } else { S::one() };
let sign_y = if j % 2 == 0 { -S::one() } else { S::one() };
let sign_z = if k % 2 == 0 { -S::one() } else { S::one() };
points[i * 4 + j * 2 + k] = [
*self.extents.x * sign_x,
*self.extents.y * sign_y,
*self.extents.z * sign_z
].into();
}
}
}
let rotation = Rotation3::from (self.orientation);
points.map (|point| rotation.rotate (point) + self.center.0)
}
pub fn aabb (self) -> Aabb3 <S> where S : Real + num::real::Real + MaybeSerDes {
Aabb3::containing (&self.corners()).unwrap()
}
pub fn contains (self, point : Point3 <S>) -> bool where
S : Real + num::real::Real + MaybeSerDes
{
use num::Inv;
let p = point - self.center.0;
let p = Rotation3::from (self.orientation).inv().rotate (p);
p.x().abs() < *self.extents.x &&
p.y().abs() < *self.extents.y &&
p.z().abs() < *self.extents.z
}
pub fn dilate (mut self, amount : S) -> Self {
self.extents = self.extents.map (|e| e.map_noisy (|e| e + amount));
self
}
}
impl <S : Real> From <Aabb3 <S>> for Obb3 <S> {
fn from (aabb : Aabb3 <S>) -> Self {
let center = aabb.center();
let extents = aabb.extents().map (|d| Positive::noisy (*d));
let orientation = Angles3::zero();
Obb3::new (center, extents, orientation)
}
}
impl <S> Primitive <S, Point3 <S>> for Obb3 <S> where
S : Real + num::real::Real + MaybeSerDes
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.extents *= scale;
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
use num::Inv;
let rotation = Rotation3::from (self.orientation);
let aabb = Aabb3::with_minmax_unchecked (
self.center - self.extents.map (|e| *e),
self.center + self.extents.map (|e| *e));
let aabb_direction = rotation.inv() * direction;
let (aabb_support, _) = aabb.support (aabb_direction);
let out = rotation.rotate_around (aabb_support, self.center);
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Capsule3, center, half_height, radius);
impl <S : OrderedRing> Capsule3 <S> {
pub fn aabb3 (self) -> Aabb3 <S> where S : Real {
use shape::Aabb;
let shape_aabb = shape::Capsule {
radius: self.radius,
half_height: self.half_height
}.aabb();
let center_vec = self.center.0;
let min = shape_aabb.min() + center_vec;
let max = shape_aabb.max() + center_vec;
Aabb3::with_minmax_unchecked (min, max)
}
pub fn decompose (self) -> (Sphere3 <S>, Option <Cylinder3 <S>>, Sphere3 <S>) {
let cylinder = if *self.half_height > S::zero() {
Some (Cylinder3 {
center: self.center,
radius: self.radius,
half_height: Positive::unchecked (*self.half_height)
})
} else {
None
};
let upper_sphere = Sphere3 {
center: self.center + (Vector3::unit_z() * *self.half_height),
radius: self.radius
};
let lower_sphere = Sphere3 {
center: self.center - (Vector3::unit_z() * *self.half_height),
radius: self.radius
};
(upper_sphere, cylinder, lower_sphere)
}
}
impl <S> Primitive <S, Point3 <S>> for Capsule3 <S> where
S : Real + num::real::Real + MaybeSerDes
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.half_height *= scale;
self.radius *= scale;
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let dir_unit = direction.normalized();
let out = self.center + if direction.z > S::zero() {
Vector3::unit_z() * *self.half_height + dir_unit * *self.radius
} else if direction.z < S::zero() {
-Vector3::unit_z() * *self.half_height + dir_unit * *self.radius
} else {
dir_unit * *self.radius
};
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Cylinder3, center, half_height, radius);
impl <S : OrderedRing> Cylinder3 <S> {
pub fn aabb3 (self) -> Aabb3 <S> where S : Real {
use shape::Aabb;
let shape_aabb = shape::Cylinder::unchecked (*self.radius, *self.half_height).aabb();
let center_vec = self.center.0;
let min = shape_aabb.min() + center_vec;
let max = shape_aabb.max() + center_vec;
Aabb3::with_minmax_unchecked (min, max)
}
}
impl <S> Primitive <S, Point3 <S>> for Cylinder3 <S> where
S : Real + num::real::Real + MaybeSerDes
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.half_height *= scale;
self.radius *= scale;
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let out = self.center + if *direction == Vector3::unit_z() {
Vector3::unit_z() * *self.half_height
} else if *direction == -Vector3::unit_z() {
-Vector3::unit_z() * *self.half_height
} else if direction.z == S::zero() {
direction.normalized() * *self.radius
} else if direction.z > S::zero() {
Vector3::unit_z() * *self.half_height
+ vector3 (direction.x, direction.y, S::zero()).normalized() * *self.radius
} else {
debug_assert!(direction.z < S::zero());
-Vector3::unit_z() * *self.half_height
+ vector3 (direction.x, direction.y, S::zero()).normalized() * *self.radius
};
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Cone3, center, half_height, radius);
impl <S : OrderedRing> Cone3 <S> {
pub fn height (self) -> Positive <S> {
self.half_height * Positive::unchecked (S::two())
}
pub fn aabb3 (self) -> Aabb3 <S> where S : Real {
use shape::Aabb;
let shape_aabb = shape::Cone {
radius: self.radius,
half_height: self.half_height
}.aabb();
let center_vec = self.center.0;
let min = shape_aabb.min() + center_vec;
let max = shape_aabb.max() + center_vec;
Aabb3::with_minmax_unchecked (min, max)
}
}
impl <S> Primitive <S, Point3 <S>> for Cone3 <S> where
S : Real + num::real::Real + approx::RelativeEq + MaybeSerDes
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.half_height *= scale;
self.radius *= scale;
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let top = || Vector3::unit_z() * *self.half_height;
let bottom = || -top();
let edge = || bottom()
+ vector3 (direction.x, direction.y, S::zero()).normalized() * *self.radius;
let out = self.center + if *direction == Vector3::unit_z() {
top()
} else if *direction == -Vector3::unit_z() {
bottom()
} else if direction.z <= S::zero() {
edge()
} else {
let angle_dir =
Trig::atan2 (direction.z, Sqrt::sqrt (S::one() - direction.z.squared()));
debug_assert!(angle_dir > S::zero());
let angle_cone = Trig::atan2 (-*self.height(), *self.radius);
debug_assert!(angle_cone < S::zero());
let pi_2 = S::pi() / S::two();
let angle_total = angle_dir - angle_cone;
if approx::relative_eq!(angle_total, pi_2) {
(top() + edge()) * S::half()
} else if angle_total > pi_2 {
top()
} else {
debug_assert!(angle_total < pi_2);
edge()
}
};
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Line2, base, direction);
impl <S : OrderedRing> Line2 <S> {
#[inline]
pub const fn new (base : Point2 <S>, direction : Unit2 <S>) -> Self {
Line2 { base, direction }
}
#[inline]
pub fn point (self, t : S) -> Point2 <S> {
self.base + *self.direction * t
}
#[inline]
pub fn affine_line (self) -> frame::Line2 <S> {
frame::Line2 {
origin: self.base,
basis: self.direction.into()
}
}
#[inline]
pub fn nearest_point (self, point : Point2 <S>) -> Line2Point <S> {
project_2d_point_on_line (point, self)
}
#[inline]
pub fn intersect_aabb (self, aabb : Aabb2 <S>)
-> Option <(Line2Point <S>, Line2Point <S>)>
{
intersect::continuous_line2_aabb2 (self, aabb)
}
#[inline]
pub fn intersect_sphere (self, sphere : Sphere2 <S>)
-> Option <(Line2Point <S>, Line2Point <S>)>
where S : Field + Sqrt {
intersect::continuous_line2_sphere2 (self, sphere)
}
}
impl <S : Real> Default for Line2 <S> {
fn default() -> Self {
Line2 {
base: Point2::origin(),
direction: Unit2::axis_y()
}
}
}
impl <S> Primitive <S, Point2 <S>> for Line2 <S> where
S : OrderedField + num::float::FloatCore + approx::AbsDiffEq
{
fn translate (&mut self, displacement : Vector2 <S>) {
self.base += displacement;
}
#[expect(unused_variables)]
fn scale (&mut self, scale : Positive <S>) {
}
fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
let dot = self.direction.dot (*direction);
let out = if approx::abs_diff_eq!(dot, S::zero()) {
self.base
} else if dot > S::zero() {
Point2 (self.direction.sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
} else {
debug_assert!(dot < S::zero());
Point2 ((-self.direction).sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
};
(out, out.0.dot (*direction))
}
}
impl <S> From <Ray2 <S>> for Line2 <S> {
fn from (Ray2 { base, direction } : Ray2 <S>) -> Line2 <S> {
Line2 { base, direction }
}
}
impl_numcast_fields!(Ray2, base, direction);
impl <S : OrderedRing> Ray2 <S> {
#[inline]
pub const fn new (base : Point2 <S>, direction : Unit2 <S>) -> Self {
Ray2 { base, direction }
}
#[inline]
pub fn point (self, t : S) -> Point2 <S> {
self.base + *self.direction * t
}
#[inline]
pub fn affine_line (self) -> frame::Line2 <S> {
frame::Line2 {
origin: self.base,
basis: self.direction.into()
}
}
pub fn nearest_point (self, point : Point2 <S>) -> Ray2Point <S> {
use num::Zero;
let (t, point) = Line2::from (self).nearest_point (point);
if t < S::zero() {
(NonNegative::zero(), self.base)
} else {
(NonNegative::unchecked (t), point)
}
}
#[inline]
pub fn intersect_aabb (self, aabb : Aabb2 <S>)
-> Option <(Ray2Point <S>, Ray2Point <S>)>
{
intersect::continuous_ray2_aabb2 (self, aabb)
}
#[inline]
pub fn intersect_sphere (self, sphere : Sphere2 <S>)
-> Option <(Ray2Point <S>, Ray2Point <S>)>
{
intersect::continuous_ray2_sphere2 (self, sphere)
}
}
impl <S : Real> Default for Ray2 <S> {
fn default() -> Self {
Ray2 {
base: Point2::origin(),
direction: Unit2::axis_y()
}
}
}
impl <S> Primitive <S, Point2 <S>> for Ray2 <S> where
S : OrderedField + num::float::FloatCore + approx::AbsDiffEq <Epsilon = S>
{
fn translate (&mut self, displacement : Vector2 <S>) {
self.base += displacement;
}
#[expect(unused_variables)]
fn scale (&mut self, scale : Positive <S>) {
}
fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
let dot = self.direction.dot (*direction);
let out = if dot < S::default_epsilon() {
self.base
} else {
Point2 (self.direction.sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
};
(out, out.0.dot (*direction))
}
}
impl <S> From <Line2 <S>> for Ray2 <S> {
fn from (Line2 { base, direction } : Line2 <S>) -> Ray2 <S> {
Ray2 { base, direction }
}
}
impl_numcast_fields!(Line3, base, direction);
impl <S : OrderedRing> Line3 <S> {
#[inline]
pub const fn new (base : Point3 <S>, direction : Unit3 <S>) -> Self {
Line3 { base, direction }
}
#[inline]
pub fn point (self, t : S) -> Point3 <S> {
self.base + *self.direction * t
}
#[inline]
pub fn affine_line (self) -> frame::Line3 <S> {
frame::Line3 {
origin: self.base,
basis: self.direction.into()
}
}
#[inline]
pub fn nearest_point (self, point : Point3 <S>) -> Line3Point <S> {
project_3d_point_on_line (point, self)
}
#[inline]
pub fn intersect_plane (self, plane : Plane3 <S>) -> Option <Line3Point <S>> where
S : Field + approx::RelativeEq
{
intersect::continuous_line3_plane3 (self, plane)
}
#[inline]
pub fn intersect_aabb (self, aabb : Aabb3 <S>)
-> Option <(Line3Point <S>, Line3Point <S>)>
where S : num::Float + approx::RelativeEq <Epsilon=S> {
intersect::continuous_line3_aabb3 (self, aabb)
}
#[inline]
pub fn intersect_sphere (self, sphere : Sphere3 <S>)
-> Option <(Line3Point <S>, Line3Point <S>)>
where S : Field + Sqrt {
intersect::continuous_line3_sphere3 (self, sphere)
}
#[inline]
pub fn intersect_triangle (self, triangle : Triangle3 <S>)
-> Option <Line3Point <S>>
where S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
intersect::continuous_line3_triangle3 (self, triangle)
}
}
impl <S : Real> Default for Line3 <S> {
fn default() -> Self {
Line3 {
base: Point3::origin(),
direction: Unit3::axis_z()
}
}
}
impl <S> Primitive <S, Point3 <S>> for Line3 <S> where
S : OrderedField + num::float::FloatCore + approx::AbsDiffEq
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.base += displacement;
}
#[expect(unused_variables)]
fn scale (&mut self, scale : Positive <S>) {
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let dot = self.direction.dot (*direction);
let out = if approx::abs_diff_eq!(dot, S::zero()) {
self.base
} else if dot > S::zero() {
Point3 (self.direction.sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
} else {
debug_assert!(dot < S::zero());
Point3 (-self.direction.sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
};
(out, out.0.dot (*direction))
}
}
impl <S> From <Ray3 <S>> for Line3 <S> {
fn from (Ray3 { base, direction } : Ray3 <S>) -> Line3 <S> {
Line3 { base, direction }
}
}
impl_numcast_fields!(Ray3, base, direction);
impl <S : OrderedRing> Ray3 <S> {
#[inline]
pub const fn new (base : Point3 <S>, direction : Unit3 <S>) -> Self {
Ray3 { base, direction }
}
#[inline]
pub fn point (self, t : S) -> Point3 <S> {
self.base + *self.direction * t
}
#[inline]
pub fn affine_line (self) -> frame::Line3 <S> {
frame::Line3 {
origin: self.base,
basis: self.direction.into()
}
}
pub fn nearest_point (self, point : Point3 <S>) -> Ray3Point <S> {
use num::Zero;
let (t, point) = Line3::from (self).nearest_point (point);
if t < S::zero() {
(NonNegative::zero(), self.base)
} else {
(NonNegative::unchecked (t), point)
}
}
#[inline]
pub fn intersect_plane (self, plane : Plane3 <S>) -> Option <Ray3Point <S>> where
S : approx::RelativeEq
{
intersect::continuous_ray3_plane3 (self, plane)
}
#[inline]
pub fn intersect_aabb (self, aabb : Aabb3 <S>)
-> Option <(Ray3Point <S>, Ray3Point <S>)>
where S : num::Float + approx::RelativeEq <Epsilon=S> {
intersect::continuous_ray3_aabb3 (self, aabb)
}
#[inline]
pub fn intersect_sphere (self, sphere : Sphere3 <S>)
-> Option <(Ray3Point <S>, Ray3Point <S>)>
{
intersect::continuous_ray3_sphere3 (self, sphere)
}
#[inline]
pub fn intersect_triangle (self, triangle : Triangle3 <S>)
-> Option <Ray3Point <S>>
where S : Real + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
intersect::continuous_ray3_triangle3 (self, triangle)
}
}
impl <S : Real> Default for Ray3 <S> {
fn default() -> Self {
Ray3 {
base: Point3::origin(),
direction: Unit3::axis_z()
}
}
}
impl <S> Primitive <S, Point3 <S>> for Ray3 <S> where
S : OrderedField + num::float::FloatCore + approx::AbsDiffEq <Epsilon = S>
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.base += displacement;
}
#[expect(unused_variables)]
fn scale (&mut self, scale : Positive <S>) {
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let dot = self.direction.dot (*direction);
let out = if dot < S::default_epsilon() {
self.base
} else {
Point3 (self.direction.sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
};
(out, out.0.dot (*direction))
}
}
impl <S> From <Line3 <S>> for Ray3 <S> {
fn from (Line3 { base, direction } : Line3 <S>) -> Ray3 <S> {
Ray3 { base, direction }
}
}
impl_numcast_fields!(Plane3, base, normal);
impl <S> Plane3 <S> {
#[inline]
pub const fn new (base : Point3 <S>, normal : Unit3 <S>) -> Self {
Plane3 { base, normal }
}
}
impl <S : Real> Default for Plane3 <S> {
fn default() -> Self {
Plane3 {
base: Point3::origin(),
normal: Unit3::axis_z()
}
}
}
impl <S> Primitive <S, Point3 <S>> for Plane3 <S> where
S : Real + num::float::FloatCore + approx::RelativeEq
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.base += displacement;
}
#[expect(unused_variables)]
fn scale (&mut self, scale : Positive <S>) {
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let dot = self.normal.dot (*direction);
let out = if approx::relative_eq!(dot, S::one()) {
self.base
} else {
let projected =
project_3d_point_on_plane (Point3 (self.base.0 + *direction), *self).0;
Point3 (projected.sigvec().map (|x| if x == S::zero() {
S::zero()
} else {
x * S::infinity()
}))
};
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Sphere2, center, radius);
impl <S : OrderedRing> Sphere2 <S> {
#[inline]
pub fn unit() -> Self {
Sphere2 {
center: Point2::origin(),
radius: num::One::one()
}
}
#[inline]
pub fn intersects (self, other : Self) -> bool {
intersect::discrete_sphere2_sphere2 (self, other)
}
}
impl <S> Primitive <S, Point2 <S>> for Sphere2 <S> where
S : Real + num::real::Real + MaybeSerDes
{
fn translate (&mut self, displacement : Vector2 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.radius *= scale;
}
fn support (&self, direction : NonZero2 <S>) -> (Point2 <S>, S) {
let out = self.center + direction.normalized() * *self.radius;
(out, out.0.dot (*direction))
}
}
impl_numcast_fields!(Sphere3, center, radius);
impl <S : OrderedRing> Sphere3 <S> {
#[inline]
pub fn unit() -> Self where S : Field {
Sphere3 {
center: Point3::origin(),
radius: num::One::one()
}
}
#[inline]
pub fn intersects (self, other : Self) -> bool {
intersect::discrete_sphere3_sphere3 (self, other)
}
}
impl <S> Primitive <S, Point3 <S>> for Sphere3 <S> where
S : Real + num::real::Real + std::fmt::Debug + MaybeSerDes
{
fn translate (&mut self, displacement : Vector3 <S>) {
self.center += displacement;
}
fn scale (&mut self, scale : Positive <S>) {
self.radius *= scale;
}
fn support (&self, direction : NonZero3 <S>) -> (Point3 <S>, S) {
let out = self.center + direction.normalized() * *self.radius;
(out, out.0.dot (*direction))
}
}
#[cfg(test)]
mod tests {
use approx::{assert_relative_eq, assert_ulps_eq};
use rand;
use rand_distr;
use rand_xorshift;
use sorted_vec::partial::SortedSet;
use super::*;
#[test]
fn colinear_2() {
use rand::{RngExt, SeedableRng};
use rand_xorshift::XorShiftRng;
use rand_distr::Distribution;
macro test ($t:ty) {
let mut rng = XorShiftRng::seed_from_u64 (0);
let normal = rand_distr::StandardNormal;
let cauchy = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
let randn = |rng : &mut XorShiftRng| normal.sample (rng);
let randf = |rng : &mut XorShiftRng| cauchy.sample (rng);
for _ in 0..100000 {
let dir = Unit2::<$t>::normalize ([randn (&mut rng), randn (&mut rng)].into());
let base = point2 (randf (&mut rng), randf (&mut rng));
let line = Line2::new (base, dir);
let p1 = line.point (randf (&mut rng));
let p2 = line.point (randf (&mut rng));
let p3 = line.point (randf (&mut rng));
assert!(colinear_2d (p1, p2, p3));
}
for _ in 0..100000 {
let p1 = point2 (randf (&mut rng), randf (&mut rng));
let v = p1.0;
let len = 0.1 + randf (&mut rng).abs();
let angle = Deg (rng.random_range (-180.0..180.0)).into();
let rotation = Rotation2::from_angle (angle);
let p2 = p1 + rotation * (v.normalized() * len);
let v = p2 - p1;
let len = 0.1 + randf (&mut rng).abs();
let mut angle = rng.random_range (5.0..175.0);
if rng.random_bool (0.5) {
angle = -angle;
}
let rotation = Rotation2::from_angle (Deg (angle).into());
let p3 = p2 + rotation * v * len;
assert!(!colinear_2d (p1, p2, p3));
}
}
test!(f32);
test!(f64);
}
#[test]
fn colinear_3() {
use rand::{RngExt, SeedableRng};
use rand_xorshift::XorShiftRng;
use rand_distr::Distribution;
macro test ($t:ty) {
let mut rng = XorShiftRng::seed_from_u64 (0);
let normal = rand_distr::StandardNormal;
let cauchy = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
let randf = |rng : &mut XorShiftRng| cauchy.sample (rng);
let randn = |rng : &mut XorShiftRng| normal.sample (rng);
for _ in 0..50000 {
let dir = Unit3::<$t>::normalize (
[randn (&mut rng), randn (&mut rng), randn (&mut rng)].into());
let base = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let line = Line3::new (base, dir);
let p1 = line.point (randf (&mut rng));
let p2 = line.point (randf (&mut rng));
let p3 = line.point (randf (&mut rng));
assert!(colinear_3d (p1, p2, p3));
}
for _ in 0..50000 {
let p1 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let v = p1.0;
let len = 0.1 + randf (&mut rng).abs();
let axis = vector3 (randn (&mut rng), randn (&mut rng), randn (&mut rng));
let angle = Deg (rng.random_range (-180.0..180.0)).into();
let rotation = Rotation3::from_axis_angle (axis, angle);
let p2 = p1 + rotation * (v.normalized() * len);
let v = p2 - p1;
let len = 0.1 + randf (&mut rng).abs();
let axis = loop {
let axis = vector3 (randn (&mut rng), randn (&mut rng), randn (&mut rng));
if axis.normalized().cross (v.normalized()).magnitude() > 0.1 {
break axis
}
};
let mut angle = rng.random_range (5.0..175.0);
if rng.random_bool (0.5) {
angle = -angle;
}
let rotation = Rotation3::from_axis_angle (axis, Deg (angle).into());
let p3 = p2 + rotation * v * len;
assert!(!colinear_3d (p1, p2, p3));
}
}
test!(f32);
test!(f64);
}
#[test]
fn coplanar_3() {
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use rand_distr::Distribution;
macro test ($t:ty) {
let mut rng = XorShiftRng::seed_from_u64 (0);
let cauchy = rand_distr::Cauchy::new (0.0, 1.0).unwrap();
let randf = |rng : &mut XorShiftRng| cauchy.sample (rng);
for _ in 0..50000 {
let p1 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let p2 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let p3 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let s = randf (&mut rng);
let t = randf (&mut rng);
let p4 = p1 + (p2 - p1) * s + (p3 - p1) * t;
assert!(coplanar_3d::<$t> (p1, p2, p3, p4));
}
for _ in 0..50000 {
let p1 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let p2 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let p3 = point3 (randf (&mut rng), randf (&mut rng), randf (&mut rng));
let s = randf (&mut rng);
let t = randf (&mut rng);
let e1 = p2 - p1;
let e2 = p3 - p1;
let n = e1.cross (e2).normalized();
let rn = randf (&mut rng);
let pnew = p1 + e1 * s + e2 * t;
let n_len = (0.3 * (pnew - p1).magnitude()).mul_add (rn.signum(), rn);
let p4 = pnew + n * n_len;
assert!(!coplanar_3d::<$t> (p1, p2, p3, p4));
}
}
test!(f32);
test!(f64);
}
#[test]
fn triangle_area_squared() {
assert_relative_eq!(
81.0,
*triangle_3d_area_squared (
[-2.0, -1.0, 0.0].into(),
[ 1.0, -1.0, 0.0].into(),
[ 1.0, 5.0, 0.0].into())
);
assert_relative_eq!(
20.25,
*triangle_3d_area_squared (
[-2.0, -1.0, 0.0].into(),
[ 1.0, -1.0, 0.0].into(),
[ 1.0, 2.0, 0.0].into())
);
assert_relative_eq!(
20.25,
*triangle_3d_area_squared (
[ 1.0, -1.0, 0.0].into(),
[-2.0, -1.0, 0.0].into(),
[ 1.0, 2.0, 0.0].into())
);
assert_relative_eq!(
20.25,
*triangle_3d_area_squared (
[ 1.0, -1.0, 0.0].into(),
[ 1.0, 2.0, 0.0].into(),
[-2.0, -1.0, 0.0].into())
);
}
#[test]
fn project_2d_point_on_line() {
use super::project_2d_point_on_line;
use Unit2;
let point : Point2 <f64> = [2.0, 2.0].into();
let line = Line2::<f64>::new ([0.0, 0.0].into(), Unit2::axis_x());
assert_eq!(project_2d_point_on_line (point, line).1, [2.0, 0.0].into());
let line = Line2::<f64>::new ([0.0, 0.0].into(), Unit2::axis_y());
assert_eq!(project_2d_point_on_line (point, line).1, [0.0, 2.0].into());
let point : Point2 <f64> = [0.0, 1.0].into();
let line = Line2::<f64>::new (
[0.0, -1.0].into(), Unit2::normalize ([1.0, 1.0].into()));
assert_relative_eq!(
project_2d_point_on_line (point, line).1, [1.0, 0.0].into());
let point : Point2 <f64> = [1.0, 3.0].into();
let line_a = Line2::<f64>::new (
[0.0, -1.0].into(), Unit2::normalize ([2.0, 1.0].into()));
let line_b = Line2::<f64>::new (
[2.0, 0.0].into(), Unit2::normalize ([2.0, 1.0].into()));
assert_relative_eq!(
project_2d_point_on_line (point, line_a).1,
project_2d_point_on_line (point, line_b).1);
let line_a = Line2::<f64>::new (
[0.0, 0.0].into(), Unit2::normalize ([1.0, 1.0].into()));
let line_b = Line2::<f64>::new (
[-2.0, -2.0].into(), Unit2::normalize ([1.0, 1.0].into()));
assert_ulps_eq!(
project_2d_point_on_line (point, line_a).1,
project_2d_point_on_line (point, line_b).1
);
assert_relative_eq!(
project_2d_point_on_line (point, line_a).1,
[2.0, 2.0].into());
}
#[test]
fn project_3d_point_on_line() {
use Unit3;
use super::project_3d_point_on_line;
let point : Point3 <f64> = [2.0, 2.0, 0.0].into();
let line = Line3::<f64>::new ([0.0, 0.0, 0.0].into(), Unit3::axis_x());
assert_eq!(project_3d_point_on_line (point, line).1, [2.0, 0.0, 0.0].into());
let line = Line3::<f64>::new ([0.0, 0.0, 0.0].into(), Unit3::axis_y());
assert_eq!(project_3d_point_on_line (point, line).1, [0.0, 2.0, 0.0].into());
let point : Point3 <f64> = [0.0, 1.0, 0.0].into();
let line = Line3::<f64>::new (
[0.0, -1.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
assert_relative_eq!(
project_3d_point_on_line (point, line).1, [1.0, 0.0, 0.0].into());
let point : Point3 <f64> = [1.0, 3.0, 0.0].into();
let line_a = Line3::<f64>::new (
[0.0, -1.0, 0.0].into(), Unit3::normalize ([2.0, 1.0, 0.0].into()));
let line_b = Line3::<f64>::new (
[2.0, 0.0, 0.0].into(), Unit3::normalize ([2.0, 1.0, 0.0].into()));
assert_relative_eq!(
project_3d_point_on_line (point, line_a).1,
project_3d_point_on_line (point, line_b).1);
let line_a = Line3::<f64>::new (
[0.0, 0.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
let line_b = Line3::<f64>::new (
[2.0, 2.0, 0.0].into(), Unit3::normalize ([1.0, 1.0, 0.0].into()));
assert_relative_eq!(
project_3d_point_on_line (point, line_a).1,
project_3d_point_on_line (point, line_b).1);
assert_relative_eq!(
project_3d_point_on_line (point, line_a).1,
[2.0, 2.0, 0.0].into());
let point : Point3 <f64> = [0.0, 0.0, 2.0].into();
let line_a = Line3::<f64>::new (
[-4.0, -4.0, -4.0].into(), Unit3::normalize ([1.0, 1.0, 1.0].into()));
let line_b = Line3::<f64>::new (
[4.0, 4.0, 4.0].into(), Unit3::normalize ([1.0, 1.0, 1.0].into()));
assert_relative_eq!(
project_3d_point_on_line (point, line_a).1,
project_3d_point_on_line (point, line_b).1,
epsilon = 0.000_000_000_000_01
);
assert_relative_eq!(
project_3d_point_on_line (point, line_a).1,
[2.0/3.0, 2.0/3.0, 2.0/3.0].into(),
epsilon = 0.000_000_000_000_01
);
}
#[test]
fn smallest_rect() {
use super::smallest_rect;
let points : Vec <Point2 <f32>> = [
[-3.0, -3.0],
[ 3.0, -3.0],
[ 3.0, 3.0],
[ 0.0, 6.0],
[-3.0, 3.0]
].map (Point2::from).into_iter().collect();
let hull = Hull2::from_points (points.as_slice()).unwrap();
assert_eq!(hull.points(), points);
let rect = smallest_rect (0, 1, &hull);
assert_eq!(rect.area, 54.0);
let points : Vec <Point2 <f32>> = [
[-1.0, -4.0],
[ 2.0, 2.0],
[-4.0, -1.0]
].map (Point2::from).into_iter().collect();
let hull = Hull2::from_points (points.as_slice()).unwrap();
assert_eq!(hull.points(), points);
let rect = smallest_rect (0, 1, &hull);
assert_eq!(rect.area, 27.0);
}
#[test]
fn capsule3() {
use num::One;
let capsule : Capsule3 <f32> = Capsule3 {
center: Point3::origin(),
radius: Positive::noisy (2.0),
half_height: Positive::one()
};
let support = capsule.support (Unit3::axis_z().into()).0;
assert_eq!(support, point3 (0.0, 0.0, 3.0));
let support = capsule.support (Unit3::axis_x().into()).0;
assert_eq!(support, point3 (2.0, 0.0, 0.0));
let support = capsule.support (NonZero3::noisy (vector3 (1.0, 0.0, 1.0))).0;
assert_eq!(support,
point3 (0.0, 0.0, 1.0) + *Unit3::normalize (vector3 (1.0, 0.0, 1.0)) * 2.0);
}
#[test]
fn cylinder3() {
use num::One;
let cylinder : Cylinder3 <f32> = Cylinder3 {
center: Point3::origin(),
radius: Positive::noisy (2.0),
half_height: Positive::one()
};
let support = cylinder.support (Unit3::axis_z().into()).0;
assert_eq!(support, point3 (0.0, 0.0, 1.0));
let support = cylinder.support (Unit3::axis_x().into()).0;
assert_eq!(support, point3 (2.0, 0.0, 0.0));
let support = cylinder.support (NonZero3::noisy (vector3 (1.0, 0.0, 1.0))).0;
assert_eq!(support, point3 (2.0, 0.0, 1.0));
}
#[test]
fn obb2() {
let points : Vec <Point2 <f32>> = [
[-1.0, -4.0],
[ 2.0, 2.0],
[-4.0, -1.0]
].map (Point2::from).into_iter().collect();
let obb = Obb2::containing_points (&points).unwrap();
let corners = obb.corners();
assert_eq!(obb.center, [-0.25, -0.25].into());
approx::assert_relative_eq!(point2 (-4.0, -1.0), corners[0], max_relative = 0.00001);
approx::assert_relative_eq!(point2 ( 0.5, 3.5), corners[1], max_relative = 0.00001);
approx::assert_relative_eq!(point2 (-1.0, -4.0), corners[2], max_relative = 0.00001);
approx::assert_relative_eq!(point2 ( 3.5, 0.5), corners[3], max_relative = 0.00001);
let points : Vec <Point2 <f32>> = [
[-2.0, 0.0],
[ 0.0, 2.0],
[ 2.0, 0.0],
[ 0.0, -2.0],
].map (Point2::from).into_iter().collect();
let obb = Obb2::containing_points (&points).unwrap();
let support = obb.support (Unit2::axis_y().into()).0;
approx::assert_relative_eq!(support, point2 (0.0, 2.0), epsilon = 0.000_001);
}
#[test]
#[expect(clippy::suboptimal_flops)]
fn obb3() {
use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
use approx::AbsDiffEq;
let points = [
[ 1.0, -1.0, -1.0],
[ 1.0, -1.0, 1.0],
[ 1.0, 1.0, -1.0],
[ 1.0, 1.0, 1.0],
[-1.0, -1.0, -1.0],
[-1.0, -1.0, 1.0],
[-1.0, 1.0, -1.0],
[-1.0, 1.0, 1.0]
].map (Point3::<f32>::from);
let obb1 = Obb3::containing_points_with_orientation (&points, Angles3::zero())
.unwrap();
assert_eq!(
SortedSet::from_unsorted (obb1.corners().to_vec()),
SortedSet::from_unsorted (
Aabb3::containing (&points).unwrap().corners().to_vec()));
let obb2 = Obb3::containing_points_approx (&points).unwrap();
assert_eq!(obb1, obb2);
let rotation = Rotation3::from_angle_y (FRAC_PI_4.into());
let points = points.map (|p| rotation.rotate (p));
let obb1 = Obb3::containing_points_with_orientation (&points,
Angles3::wrap (
(3.0 * FRAC_PI_2).into(),
(2.0 * PI - FRAC_PI_4).into(),
FRAC_PI_2.into())
).unwrap();
let obb2 = Obb3::containing_points_approx (&points).unwrap();
approx::assert_abs_diff_eq!(obb1.center, obb2.center);
assert_eq!(obb1.orientation, obb2.orientation);
approx::assert_abs_diff_eq!(*obb1.extents.x, *obb2.extents.x,
epsilon=4.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(*obb1.extents.y, *obb2.extents.y,
epsilon=4.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(*obb1.extents.z, *obb2.extents.z,
epsilon=2.0 * f32::default_epsilon());
let corners = obb2.corners();
approx::assert_abs_diff_eq!(corners[0], points[7],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[1], points[5],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[2], points[3],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[3], points[1],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[4], points[6],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[5], points[4],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[6], points[2],
epsilon=8.0 * f32::default_epsilon());
approx::assert_abs_diff_eq!(corners[7], points[0],
epsilon=8.0 * f32::default_epsilon());
let points : Vec <Point3 <f32>> = [
[-2.0, 0.0, -2.0],
[ 0.0, 2.0, -2.0],
[ 2.0, 0.0, -2.0],
[ 0.0, -2.0, -2.0],
[-2.0, 0.0, 2.0],
[ 0.0, 2.0, 2.0],
[ 2.0, 0.0, 2.0],
[ 0.0, -2.0, 2.0]
].map (Point3::from).into_iter().collect();
let obb = Obb3::containing_points_approx (&points).unwrap();
let support = obb.support (Unit3::axis_y().into()).0;
approx::assert_relative_eq!(support, point3 (0.0, 2.0, 0.0), epsilon = 0.000_001);
let support = obb.support (NonZero3::noisy (vector3 (0.0, 1.0, 1.0))).0;
approx::assert_relative_eq!(support, point3 (0.0, 2.0, 2.0), epsilon = 0.000_001);
}
#[test]
fn support_aabb3() {
let aabb = Aabb3::with_minmax (
[-1.0, -2.0, -3.0].into(),
[ 1.0, 2.0, 3.0].into()).unwrap();
assert_eq!(
[-1.0, -2.0, -3.0],
aabb.support (NonZero3::noisy ([-1.0, -1.0, -1.0].into())).0.0.into_array());
assert_eq!(
[-1.0, -2.0, 3.0],
aabb.support (NonZero3::noisy ([-1.0, -1.0, 1.0].into())).0.0.into_array());
assert_eq!(
[-1.0, 2.0, -3.0],
aabb.support (NonZero3::noisy ([-1.0, 1.0, -1.0].into())).0.0.into_array());
assert_eq!(
[-1.0, 2.0, 3.0],
aabb.support (NonZero3::noisy ([-1.0, 1.0, 1.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, -2.0, -3.0],
aabb.support (NonZero3::noisy ([ 1.0, -1.0, -1.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, -2.0, 3.0],
aabb.support (NonZero3::noisy ([ 1.0, -1.0, 1.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, 2.0, -3.0],
aabb.support (NonZero3::noisy ([ 1.0, 1.0, -1.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, 2.0, 3.0],
aabb.support (NonZero3::noisy ([ 1.0, 1.0, 1.0].into())).0.0.into_array());
assert_eq!(
[-1.0, -2.0, 0.0],
aabb.support (NonZero3::noisy ([-1.0, -1.0, 0.0].into())).0.0.into_array());
assert_eq!(
[-1.0, 2.0, 0.0],
aabb.support (NonZero3::noisy ([-1.0, 1.0, 0.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, -2.0, 0.0],
aabb.support (NonZero3::noisy ([ 1.0, -1.0, 0.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, 2.0, 0.0],
aabb.support (NonZero3::noisy ([ 1.0, 1.0, 0.0].into())).0.0.into_array());
assert_eq!(
[-1.0, 0.0, -3.0],
aabb.support (NonZero3::noisy ([-1.0, 0.0, -1.0].into())).0.0.into_array());
assert_eq!(
[-1.0, 0.0, 3.0],
aabb.support (NonZero3::noisy ([-1.0, 0.0, 1.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, 0.0, -3.0],
aabb.support (NonZero3::noisy ([ 1.0, 0.0, -1.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, 0.0, 3.0],
aabb.support (NonZero3::noisy ([ 1.0, 0.0, 1.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, -2.0, -3.0],
aabb.support (NonZero3::noisy ([ 0.0, -1.0, -1.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, -2.0, 3.0],
aabb.support (NonZero3::noisy ([ 0.0, -1.0, 1.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, 2.0, -3.0],
aabb.support (NonZero3::noisy ([ 0.0, 1.0, -1.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, 2.0, 3.0],
aabb.support (NonZero3::noisy ([ 0.0, 1.0, 1.0].into())).0.0.into_array());
assert_eq!(
[-1.0, 0.0, 0.0],
aabb.support (NonZero3::noisy ([-1.0, 0.0, 0.0].into())).0.0.into_array());
assert_eq!(
[ 1.0, 0.0, 0.0],
aabb.support (NonZero3::noisy ([ 1.0, 0.0, 0.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, -2.0, 0.0],
aabb.support (NonZero3::noisy ([ 0.0, -1.0, 0.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, 2.0, 0.0],
aabb.support (NonZero3::noisy ([ 0.0, 1.0, 0.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, 0.0, -3.0],
aabb.support (NonZero3::noisy ([ 0.0, 0.0, -1.0].into())).0.0.into_array());
assert_eq!(
[ 0.0, 0.0, 3.0],
aabb.support (NonZero3::noisy ([ 0.0, 0.0, 1.0].into())).0.0.into_array());
}
}