math_utils/geometry/
shape.rs

1//! 3D volumetric forms
2
3use std::convert::TryFrom;
4use derive_more::{From, TryFrom};
5use num_traits as num;
6#[cfg(feature = "derive_serdes")]
7use serde::{Deserialize, Serialize};
8
9use crate::*;
10
11use super::{Aabb3, Capsule3, Cylinder3, Sphere3};
12
13////////////////////////////////////////////////////////////////////////////////////////
14//  traits                                                                            //
15////////////////////////////////////////////////////////////////////////////////////////
16
17/// A trait for bounded and unbounded shapes.
18///
19/// A 'Shape' implements the following traits:
20///
21/// - 'Aabb' -- a shape can compute the axis-aligned bounding volume that encloses it
22/// - 'Bsphere' -- TODO: a shape can compute its bounding sphere
23///
24/// Both of these traits carry the constraint that a shape implements the 'Stereometric'
25/// trait which allows a shape to compute its volume (non-zero, possibly infinite).
26pub trait Shape <S : Ring> : Aabb <S> /*+ Bsphere <S>*/ { } // TODO: bspheres
27
28/// Trait for computing the axis-aligned bounding volume of a given shape.
29///
30/// Note for 'Unbounded' shapes some of the components will contain positive or negative
31/// infinity.
32pub trait Aabb <S : Ring> : Stereometric <S> {
33  fn aabb (&self) -> Aabb3 <S>;
34}
35
36// TODO: implement bounding spheres
37/// A trait for computing bounding spheres
38pub trait Bsphere <S : Ring> : Stereometric <S> {
39  fn sphere (&self) -> Sphere3 <S>;
40}
41
42/// Trait for computing volumes of solid figures.
43///
44/// Note for 'Unbounded' shapes this will be positive infinitiy.
45pub trait Stereometric <S : Ring> {
46  fn volume (&self) -> Positive <S>;
47}
48
49////////////////////////////////////////////////////////////////////////////////////////
50//  enums                                                                             //
51////////////////////////////////////////////////////////////////////////////////////////
52
53/// A shape that is either bounded (encloses a finite volume) or unbounded (delimits an
54/// infinite volume)
55#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
56#[derive(From, Clone, Debug, Eq, PartialEq, TryFrom)]
57pub enum Variant <S> {
58  Bounded   (Bounded   <S>),
59  Unbounded (Unbounded <S>)
60}
61
62/// A (totally) bounded shape
63#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
64#[derive(From, Clone, Debug, Eq, PartialEq)]
65pub enum Bounded <S> {
66  Sphere   (Sphere   <S>),
67  Capsule  (Capsule  <S>),
68  Cylinder (Cylinder <S>),
69  Cone     (Cone     <S>),
70  Cube     (Cube     <S>),
71  Cuboid   (Cuboid   <S>)
72}
73
74/// A shape that is only partly bounded
75#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
76#[derive(From, Clone, Debug, Eq, PartialEq)]
77pub enum Unbounded <S> {
78  Orthant   (Orthant),
79  Halfspace (Halfspace <S>)
80}
81
82////////////////////////////////////////////////////////////////////////////////////////
83//  structs                                                                           //
84////////////////////////////////////////////////////////////////////////////////////////
85
86/// An axis-aligned halfspace
87#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
88#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89pub struct Orthant {
90  pub normal_axis : SignedAxis3
91}
92
93/// A halfspace defined by a normal vector
94#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
95#[derive(Clone, Copy, Debug, Eq, PartialEq)]
96pub struct Halfspace <S> {
97  pub normal_vector : Unit3 <S>
98}
99
100/// A sphere defined by a positive radius
101#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub struct Sphere <S> {
104  pub radius : Positive <S>
105}
106
107/// A capsule defined by strictly positive radius and half-height
108#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
110pub struct Capsule <S> {
111  pub radius      : Positive <S>,
112  pub half_height : Positive <S>
113}
114
115/// A cylinder defined by strictly positive radius and half-height
116#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
117#[derive(Clone, Copy, Debug, Eq, PartialEq)]
118pub struct Cylinder <S> {
119  pub radius      : Positive <S>,
120  pub half_height : Positive <S>
121}
122
123/// A cone defined by strictly positive radius and half-height
124#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub struct Cone <S> {
127  pub radius      : Positive <S>,
128  pub half_height : Positive <S>
129}
130
131/// A cube defined by a strictly positive half-extent
132#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub struct Cube <S> {
135  pub half_extent : Positive <S>
136}
137
138/// A box defined by three strictly positive half extents
139#[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
141pub struct Cuboid <S> {
142  pub half_extent_x : Positive <S>,
143  pub half_extent_y : Positive <S>,
144  pub half_extent_z : Positive <S>
145}
146
147////////////////////////////////////////////////////////////////////////////////////////
148//  functions                                                                         //
149////////////////////////////////////////////////////////////////////////////////////////
150
151pub fn report_sizes() {
152  use std::mem::size_of;
153  println!("shape report sizes...");
154
155  show!(size_of::<Variant   <f32>>());
156  show!(size_of::<Variant   <f64>>());
157
158  show!(size_of::<Bounded   <f32>>());
159  show!(size_of::<Bounded   <f64>>());
160
161  show!(size_of::<Unbounded <f32>>());
162  show!(size_of::<Unbounded <f64>>());
163
164  show!(size_of::<Sphere    <f32>>());
165  show!(size_of::<Capsule   <f32>>());
166  show!(size_of::<Cylinder  <f32>>());
167  show!(size_of::<Cone      <f32>>());
168  show!(size_of::<Cube      <f32>>());
169  show!(size_of::<Cuboid    <f32>>());
170
171  show!(size_of::<Orthant>());
172  show!(size_of::<Halfspace <f32>>());
173
174  show!(size_of::<Sphere    <f64>>());
175  show!(size_of::<Capsule   <f64>>());
176  show!(size_of::<Cylinder  <f64>>());
177  show!(size_of::<Cone      <f64>>());
178  show!(size_of::<Cube      <f64>>());
179  show!(size_of::<Cuboid    <f64>>());
180
181  show!(size_of::<Orthant>());
182  show!(size_of::<Halfspace <f64>>());
183
184  println!("...shape report sizes");
185}
186
187////////////////////////////////////////////////////////////////////////////////////////
188//  impls                                                                             //
189////////////////////////////////////////////////////////////////////////////////////////
190
191//
192//  impl Variant
193//
194impl <S> Shape <S> for Variant <S> where
195  S : Real + num::float::FloatCore + std::fmt::Debug
196{ }
197impl <S> Aabb <S> for Variant <S> where
198  S : Real + num::float::FloatCore + std::fmt::Debug
199{
200  /// Note that unbounded shapes will return a Cuboid with some infinite half extents
201  fn aabb (&self) -> Aabb3 <S> {
202    match self {
203      Variant::Bounded   (bounded)   => bounded.aabb(),
204      Variant::Unbounded (unbounded) => unbounded.aabb()
205    }
206  }
207}
208impl <S> Stereometric <S> for Variant <S> where
209  S : Real + num::float::FloatCore + std::fmt::Debug
210{
211  /// Note that unbounded shapes will return a Cuboid with some infinite half extents
212  fn volume (&self) -> Positive <S> {
213    match self {
214      Variant::Bounded   (bounded)   => bounded.volume(),
215      Variant::Unbounded (unbounded) => unbounded.volume()
216    }
217  }
218}
219
220//
221//  impl Bounded
222//
223impl <S> Shape <S> for Bounded <S> where S : Real + std::fmt::Debug { }
224impl <S> Aabb <S> for Bounded <S> where S : Real + std::fmt::Debug {
225  fn aabb (&self) -> Aabb3 <S> {
226    match self {
227      Bounded::Sphere   (sphere)   => sphere.aabb(),
228      Bounded::Capsule  (capsule)  => capsule.aabb(),
229      Bounded::Cylinder (cylinder) => cylinder.aabb(),
230      Bounded::Cone     (cone)     => cone.aabb(),
231      Bounded::Cube     (cube)     => cube.aabb(),
232      Bounded::Cuboid   (cuboid)   => cuboid.aabb()
233    }
234  }
235}
236impl <S : Real> Stereometric <S> for Bounded <S> {
237  /// Volume of a bounded solid is always finite
238  fn volume (&self) -> Positive <S> {
239    match self {
240      Bounded::Sphere   (sphere)   => sphere.volume(),
241      Bounded::Capsule  (capsule)  => capsule.volume(),
242      Bounded::Cylinder (cylinder) => cylinder.volume(),
243      Bounded::Cone     (cone)     => cone.volume(),
244      Bounded::Cube     (cube)     => cube.volume(),
245      Bounded::Cuboid   (cuboid)   => cuboid.volume()
246    }
247  }
248}
249
250//
251//  impl Unbounded
252//
253impl <S> Shape <S> for Unbounded <S> where
254  S : Real + num::float::FloatCore + std::fmt::Debug
255{ }
256impl <S> Aabb <S> for Unbounded <S> where
257  S : Real + num::float::FloatCore + std::fmt::Debug
258{
259  fn aabb (&self) -> Aabb3 <S> {
260    match self {
261      Unbounded::Orthant   (orthant)   => orthant.aabb(),
262      Unbounded::Halfspace (halfspace) => halfspace.aabb()
263    }
264  }
265}
266impl <S> Stereometric <S> for Unbounded <S> where
267  S : Real + num::float::FloatCore + std::fmt::Debug
268{
269  /// Volume of an unbounded solid is always infinite
270  fn volume (&self) -> Positive <S> {
271    if cfg!(debug_assertions) {
272      let volume = match self {
273        Unbounded::Orthant   (orthant)   => orthant.volume(),
274        Unbounded::Halfspace (halfspace) => halfspace.volume()
275      };
276      debug_assert_eq!(*volume, S::infinity());
277    }
278    Positive::infinity()
279  }
280}
281
282//
283//  impl Sphere
284//
285impl <S : Ring> Sphere <S> {
286  #[inline]
287  /// Sphere with radius 1.0
288  pub fn unit() -> Self where S : Field {
289    use num::One;
290    Sphere { radius: Positive::one() }
291  }
292  /// Create a new sphere with the absolute value of the given radius.
293  ///
294  /// Panics if radius is zero:
295  ///
296  /// ```should_panic
297  /// # use math_utils::geometry::shape::Sphere;
298  /// let s = Sphere::noisy (0.0);
299  /// ```
300  #[inline]
301  pub fn noisy (radius : S) -> Self where S : std::fmt::Debug {
302    assert_ne!(radius, S::zero());
303    Sphere { radius: Positive::unchecked (radius.abs()) }
304  }
305  /// Create a new sphere with the absolute value of the given radius.
306  ///
307  /// Debug panic if radius is zero:
308  ///
309  /// ```should_panic
310  /// # use math_utils::geometry::shape::Sphere;
311  /// let s = Sphere::noisy (0.0);
312  /// ```
313  #[inline]
314  pub fn unchecked (radius : S) -> Self where S : std::fmt::Debug {
315    debug_assert_ne!(radius, S::zero());
316    Sphere { radius: Positive::unchecked (radius.abs()) }
317  }
318  #[inline]
319  pub const fn sphere3 (&self, center : Point3 <S>) -> Sphere3 <S> {
320    Sphere3 { center, radius: self.radius }
321  }
322}
323impl <S : Real> TryFrom <Bounded <S>> for Sphere <S> {
324  type Error = Bounded <S>;
325  fn try_from (bounded : Bounded <S>) -> Result <Self, Self::Error> {
326    match bounded {
327      Bounded::Sphere (sphere) => Ok (sphere),
328      _ => Err (bounded)
329    }
330  }
331}
332impl <S> Shape <S> for Sphere <S> where S : Real + std::fmt::Debug { }
333impl <S> Aabb <S> for Sphere <S> where S : Real + std::fmt::Debug {
334  fn aabb (&self) -> Aabb3 <S> {
335    Aabb3::with_minmax ([-*self.radius; 3].into(), [ *self.radius; 3].into())
336  }
337}
338impl <S : Real> Stereometric <S> for Sphere <S> {
339  fn volume (&self) -> Positive <S> {
340    let four      = Positive::unchecked (S::four());
341    let frac_pi_3 = Positive::unchecked (S::frac_pi_3());
342    four * frac_pi_3 * self.radius * self.radius * self.radius
343  }
344}
345
346//
347//  impl Capsule
348//
349impl <S : Ring> Capsule <S> {
350  /// Create a new capsule with the absolute values of the given radius and half-height.
351  ///
352  /// Panics if radius is zero:
353  ///
354  /// ```should_panic
355  /// # use math_utils::geometry::shape::Capsule;
356  /// let s = Capsule::noisy (0.0, 2.0);
357  /// ```
358  ///
359  /// Panics if half-height is zero:
360  ///
361  /// ```should_panic
362  /// # use math_utils::geometry::shape::Capsule;
363  /// let s = Capsule::noisy (2.0, 0.0);
364  /// ```
365  #[inline]
366  pub fn noisy (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
367    assert_ne!(radius, S::zero());
368    assert_ne!(half_height, S::zero());
369    let radius      = Positive::unchecked (radius.abs());
370    let half_height = Positive::unchecked (half_height.abs());
371    Capsule { radius, half_height }
372  }
373  /// Create a new capsule with the absolute values of the given radius and half-height.
374  ///
375  /// Debug panic if radius is zero:
376  ///
377  /// ```should_panic
378  /// # use math_utils::geometry::shape::Capsule;
379  /// let s = Capsule::unchecked (0.0, 2.0);
380  /// ```
381  ///
382  /// Debug panic if half-height is zero:
383  ///
384  /// ```should_panic
385  /// # use math_utils::geometry::shape::Capsule;
386  /// let s = Capsule::noisy (2.0, 0.0);
387  /// ```
388  #[inline]
389  pub fn unchecked (radius : S, half_height : S) -> Self where
390    S : std::fmt::Debug
391  {
392    debug_assert_ne!(radius, S::zero());
393    let radius      = Positive::unchecked (radius.abs());
394    let half_height = Positive::unchecked (half_height.abs());
395    Capsule { radius, half_height }
396  }
397  /// Height of the cylinder portion
398  #[inline]
399  pub fn height (&self) -> NonNegative <S> where S : Field {
400    self.half_height * NonNegative::unchecked (S::two())
401  }
402  #[inline]
403  pub const fn capsule3 (&self, center : Point3 <S>) -> Capsule3 <S> {
404    Capsule3 { center, radius: self.radius, half_height: self.half_height }
405  }
406}
407impl <S> Shape <S> for Capsule <S> where S : Real + std::fmt::Debug { }
408impl <S> Aabb <S> for Capsule <S> where S : Real + std::fmt::Debug {
409  fn aabb (&self) -> Aabb3 <S> {
410    let r  = *self.radius;
411    let hh = *self.half_height;
412    Aabb3::with_minmax ([-r, -r, -r - hh].into(), [ r,  r,  r + hh].into())
413  }
414}
415impl <S : Real> Stereometric <S> for Capsule <S> {
416  fn volume (&self) -> Positive <S> {
417    let r               = self.radius;
418    let h               = self.height();
419    let r2              = r * r;
420    let r3              = r2 * r;
421    let pi              = Positive::unchecked (S::pi());
422    let four            = Positive::unchecked (S::four());
423    let frac_pi_3       = Positive::unchecked (S::frac_pi_3());
424    let cylinder_volume = pi * r2 * h;
425    let sphere_volume   = four * frac_pi_3 * r3;
426    sphere_volume + cylinder_volume
427  }
428}
429
430//
431//  impl Cylinder
432//
433impl <S : Ring> Cylinder <S> {
434  #[inline]
435  /// Cylinder with radius 1.0 and half-height 1.0
436  pub fn unit() -> Self where S : Field {
437    use num::One;
438    Cylinder {
439      radius: Positive::one(), half_height: Positive::one()
440    }
441  }
442  /// Create a new cylinder with the absolute values of the given radius and
443  /// half-height.
444  ///
445  /// Panics if radius or half-height are zero:
446  ///
447  /// ```should_panic
448  /// # use math_utils::geometry::shape::Cylinder;
449  /// let s = Cylinder::noisy (0.0, 0.0);
450  /// ```
451  #[inline]
452  pub fn noisy (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
453    assert_ne!(radius,      S::zero());
454    assert_ne!(half_height, S::zero());
455    let radius      = Positive::unchecked (radius.abs());
456    let half_height = Positive::unchecked (half_height.abs());
457    Cylinder { radius, half_height }
458  }
459  /// Create a new cylinder with the absolute values of the given radius and
460  /// half-height.
461  ///
462  /// Debug panic if radius or half-height are zero:
463  ///
464  /// ```should_panic
465  /// # use math_utils::geometry::shape::Cylinder;
466  /// let s = Cylinder::unchecked (0.0, 0.0);
467  /// ```
468  #[inline]
469  pub fn unchecked (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
470    debug_assert_ne!(radius,      S::zero());
471    debug_assert_ne!(half_height, S::zero());
472    let radius      = Positive::unchecked (radius.abs());
473    let half_height = Positive::unchecked (half_height.abs());
474    Cylinder { radius, half_height }
475  }
476  #[inline]
477  pub const fn cylinder3 (&self, center : Point3 <S>) -> Cylinder3 <S> {
478    Cylinder3 { center, radius: self.radius, half_height: self.half_height }
479  }
480  #[inline]
481  pub fn height (&self) -> Positive <S> where S : Field {
482    self.half_height * Positive::unchecked (S::two())
483  }
484}
485impl <S> Shape <S> for Cylinder <S> where S : Real + std::fmt::Debug { }
486impl <S> Aabb <S> for Cylinder <S> where S : Real + std::fmt::Debug {
487  fn aabb (&self) -> Aabb3 <S> {
488    let r  = *self.radius;
489    let hh = *self.half_height;
490    Aabb3::with_minmax ([-r, -r, -hh].into(), [ r,  r,  hh].into())
491  }
492}
493impl <S : Real> Stereometric <S> for Cylinder <S> {
494  fn volume (&self) -> Positive <S> {
495    let pi = Positive::unchecked (S::pi());
496    pi * self.radius * self.radius * self.height()
497  }
498}
499
500//
501//  impl Cone
502//
503impl <S : Ring> Cone <S> {
504  /// Create a new cone with the absolute values of the given radius and half-height.
505  ///
506  /// Panics if radius or half-height are zero:
507  ///
508  /// ```should_panic
509  /// # use math_utils::geometry::shape::Cone;
510  /// let s = Cone::noisy (0.0, 0.0);
511  /// ```
512  #[inline]
513  pub fn noisy (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
514    assert_ne!(radius,      S::zero());
515    assert_ne!(half_height, S::zero());
516    let radius      = Positive::unchecked (radius.abs());
517    let half_height = Positive::unchecked (half_height.abs());
518    Cone { radius, half_height }
519  }
520}
521impl <S> Shape <S> for Cone <S> where S : Real + std::fmt::Debug { }
522impl <S> Aabb <S> for Cone <S> where S : Real + std::fmt::Debug {
523  fn aabb (&self) -> Aabb3 <S> {
524    let r  = *self.radius;
525    let hh = *self.half_height;
526    Aabb3::with_minmax ([-r, -r, -hh].into(), [ r,  r,  hh].into())
527  }
528}
529impl <S : Real> Stereometric <S> for Cone <S> {
530  fn volume (&self) -> Positive <S> {
531    let frac_pi_3 = Positive::unchecked (S::frac_pi_3());
532    let two       = Positive::unchecked (S::two());
533    frac_pi_3 * self.radius * self.radius * two * self.half_height
534  }
535}
536
537//
538//  impl Cube
539//
540impl <S : Ring> Cube <S> {
541  /// Create a new cube with the absolute value of the given half-extent.
542  ///
543  /// Panics if half-extent is zero:
544  ///
545  /// ```should_panic
546  /// # use math_utils::geometry::shape::Cube;
547  /// let s = Cube::noisy (0.0);
548  /// ```
549  #[inline]
550  pub fn noisy (half_extent : S) -> Self where S : std::fmt::Debug {
551    assert_ne!(half_extent, S::zero());
552    let half_extent = Positive::unchecked (half_extent.abs());
553    Cube { half_extent }
554  }
555  #[inline]
556  pub fn extent (&self) -> Positive <S> where S : Field {
557    self.half_extent * Positive::unchecked (S::two())
558  }
559}
560impl <S> Shape <S> for Cube <S> where S : Field + std::fmt::Debug { }
561impl <S> Aabb <S> for Cube <S> where S : Field + std::fmt::Debug {
562  fn aabb (&self) -> Aabb3 <S> {
563    Aabb3::with_minmax (
564      [-*self.half_extent; 3].into(),
565      [ *self.half_extent; 3].into())
566  }
567}
568impl <S : Field> Stereometric <S> for Cube <S> {
569  fn volume (&self) -> Positive <S> {
570    let extent = self.extent();
571    extent * extent * extent
572  }
573}
574
575//
576//  impl Cuboid
577//
578impl <S : Ring> Cuboid <S> {
579  /// Create a new cuboid with the absolute values of the given half-extents.
580  ///
581  /// Panics if any half-extent is zero:
582  ///
583  /// ```should_panic
584  /// # use math_utils::geometry::shape::Cuboid;
585  /// let s = Cuboid::noisy ([0.0, 0.0, 0.0]);
586  /// ```
587  #[inline]
588  pub fn noisy (half_extents : [S; 3]) -> Self where S : std::fmt::Debug {
589    assert_ne!(half_extents[0], S::zero());
590    assert_ne!(half_extents[1], S::zero());
591    assert_ne!(half_extents[2], S::zero());
592    let half_extent_x = Positive::unchecked (half_extents[0].abs());
593    let half_extent_y = Positive::unchecked (half_extents[1].abs());
594    let half_extent_z = Positive::unchecked (half_extents[2].abs());
595    Cuboid { half_extent_x, half_extent_y, half_extent_z }
596  }
597  #[inline]
598  pub fn extents (&self) -> [Positive <S>; 3] where S : Field {
599    let two = Positive::unchecked (S::two());
600    [ self.half_extent_x * two,
601      self.half_extent_y * two,
602      self.half_extent_z * two
603    ]
604  }
605  #[inline]
606  pub fn half_extents_vec (&self) -> Vector3 <S> {
607    [ *self.half_extent_x,
608      *self.half_extent_y,
609      *self.half_extent_z
610    ].into()
611  }
612  #[inline]
613  pub fn max (&self) -> Point3 <S> {
614    Vector3 {
615      x: *self.half_extent_x,
616      y: *self.half_extent_y,
617      z: *self.half_extent_z
618    }.into()
619  }
620  #[inline]
621  pub fn min (&self) -> Point3 <S> {
622    Vector3 {
623      x: -*self.half_extent_x,
624      y: -*self.half_extent_y,
625      z: -*self.half_extent_z
626    }.into()
627  }
628  #[inline]
629  pub fn aabb3 (&self, center : Point3 <S>) -> Aabb3 <S> where S : std::fmt::Debug {
630    Aabb3::with_minmax (
631      center + self.min().0,
632      center + self.max().0
633    )
634  }
635}
636impl <S> From <Aabb3 <S>> for Cuboid <S> where S : Field {
637  fn from (aabb : Aabb3 <S>) -> Self {
638    // TODO: aabbs do not enforce zero width/depth/height so this is unsafe
639    Cuboid {
640      half_extent_x: Positive::unchecked (*aabb.width()  / S::two()),
641      half_extent_y: Positive::unchecked (*aabb.depth()  / S::two()),
642      half_extent_z: Positive::unchecked (*aabb.height() / S::two())
643    }
644  }
645}
646impl <S> Shape <S> for Cuboid <S> where S : Field + std::fmt::Debug { }
647impl <S> Aabb <S>  for Cuboid <S> where S : Field + std::fmt::Debug {
648  fn aabb (&self) -> Aabb3 <S> {
649    Aabb3::with_minmax (self.min(), self.max())
650  }
651}
652impl <S : Field> Stereometric <S> for Cuboid <S> {
653  fn volume (&self) -> Positive <S> {
654    let [x, y, z] = self.extents();
655    x * y * z
656  }
657}
658
659//
660//  impl Orthant
661//
662impl Orthant {
663  pub fn try_from <S : Real> (halfspace : &Halfspace <S>) -> Option <Self> {
664    SignedAxis3::try_from (&halfspace.normal_vector)
665      .map (|normal_axis| Orthant { normal_axis })
666  }
667}
668impl From <SignedAxis3> for Orthant {
669  fn from (normal_axis : SignedAxis3) -> Self {
670    Orthant { normal_axis }
671  }
672}
673impl <S> Shape <S> for Orthant where
674  S : Field + num::float::FloatCore + std::fmt::Debug
675{ }
676impl <S> Aabb <S> for Orthant where
677  S : Field + num::float::FloatCore + std::fmt::Debug
678{
679  /// Computes the containing Aabb3.
680  ///
681  /// ```
682  /// # use math_utils::geometry::shape::Orthant;
683  /// # use math_utils::geometry::Aabb3;
684  /// # use math_utils::SignedAxis3;
685  /// use math_utils::geometry::shape::Aabb;
686  /// let s = Orthant::from (SignedAxis3::PosZ);
687  /// assert_eq!(s.aabb(), Aabb3::with_minmax (
688  ///   [f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY].into(),
689  ///   [f32::INFINITY,     f32::INFINITY,     0.0].into()
690  /// ));
691  /// let s = Orthant::from (SignedAxis3::NegZ);
692  /// assert_eq!(s.aabb(), Aabb3::with_minmax (
693  ///   [f32::NEG_INFINITY, f32::NEG_INFINITY, 0.0].into(),
694  ///   [f32::INFINITY,     f32::INFINITY,     f32::INFINITY].into()
695  /// ));
696  /// ```
697  fn aabb (&self) -> Aabb3 <S> {
698    use num::float::FloatCore;
699    let axis_vec = self.normal_axis.to_vec::<S>();
700    let surface  = Vector3::broadcast (S::one()) - axis_vec.map (FloatCore::abs);
701
702    let do_min = |i| if surface[i] == S::one() || axis_vec[i] == S::one() {
703      S::neg_infinity()
704    } else {
705      debug_assert_eq!(axis_vec[i], -S::one());
706      S::zero()
707    };
708    let do_max = |i| if surface[i] == S::one() {
709      S::infinity()
710    } else if axis_vec[i] == S::one() {
711      S::zero()
712    } else {
713      debug_assert_eq!(axis_vec[i], -S::one());
714      S::infinity()
715    };
716    let min = [ do_min (0), do_min (1), do_min (2) ].into();
717    let max = [ do_max (0), do_max (1), do_max (2) ].into();
718
719    Aabb3::with_minmax (min, max)
720  }
721}
722impl <S> Stereometric <S> for Orthant where S : Ring + num::float::FloatCore {
723  /// Volume of an unbounded solid is always infinite
724  fn volume (&self) -> Positive <S> {
725    Positive::infinity()
726  }
727}
728
729//
730//  impl Halfspace
731//
732impl <S : Real> From <Unit3 <S>> for Halfspace <S> {
733  fn from (normal_vector : Unit3 <S>) -> Self {
734    Halfspace { normal_vector }
735  }
736}
737impl <S> Shape <S> for Halfspace <S> where
738  S : Real + num::float::FloatCore + std::fmt::Debug
739{ }
740impl <S> Aabb <S> for Halfspace <S> where
741  S : Real + num::float::FloatCore + std::fmt::Debug
742{
743  fn aabb (&self) -> Aabb3 <S> {
744    if let Some (orthant) = Orthant::try_from (self) {
745      orthant.aabb()
746    } else {
747      Aabb3::with_minmax ([S::neg_infinity(); 3].into(), [S::infinity(); 3].into())
748    }
749  }
750}
751impl <S> Stereometric <S> for Halfspace <S> where S : Real + num::float::FloatCore {
752  /// Volume of an unbounded solid is always infinite
753  fn volume (&self) -> Positive <S> {
754    Positive::infinity()
755  }
756}
757
758////////////////////////////////////////////////////////////////////////////////////////
759//  numcast impls                                                                     //
760////////////////////////////////////////////////////////////////////////////////////////
761
762impl <S> Halfspace <S> {
763  #[inline]
764  pub fn numcast <T> (self) -> Option <Halfspace <T>> where
765    S : num::NumCast,
766    T : num::NumCast
767  {
768    Some (Halfspace {
769      normal_vector: self.normal_vector.numcast()?
770    })
771  }
772}
773
774impl <S> Sphere <S> {
775  #[inline]
776  pub fn numcast <T> (self) -> Option <Sphere <T>> where
777    S : num::NumCast,
778    T : num::NumCast
779  {
780    Some (Sphere {
781      radius: self.radius.numcast()?
782    })
783  }
784}
785
786impl <S> Capsule <S> {
787  #[inline]
788  pub fn numcast <T> (self) -> Option <Capsule <T>> where
789    S : num::NumCast,
790    T : num::NumCast
791  {
792    Some (Capsule {
793      radius:      self.radius.numcast()?,
794      half_height: self.half_height.numcast()?
795    })
796  }
797}
798
799impl <S> Cylinder <S> {
800  #[inline]
801  pub fn numcast <T> (self) -> Option <Cylinder <T>> where
802    S : num::NumCast,
803    T : num::NumCast
804  {
805    Some (Cylinder {
806      radius:      self.radius.numcast()?,
807      half_height: self.half_height.numcast()?
808    })
809  }
810}
811
812impl <S> Cone <S> {
813  #[inline]
814  pub fn numcast <T> (self) -> Option <Cone <T>> where
815    S : num::NumCast,
816    T : num::NumCast
817  {
818    Some (Cone {
819      radius:      self.radius.numcast()?,
820      half_height: self.half_height.numcast()?
821    })
822  }
823}
824
825impl <S> Cube <S> {
826  #[inline]
827  pub fn numcast <T> (self) -> Option <Cube <T>> where
828    S : num::NumCast,
829    T : num::NumCast
830  {
831    Some (Cube {
832      half_extent: self.half_extent.numcast()?
833    })
834  }
835}
836
837impl <S> Cuboid <S> {
838  #[inline]
839  pub fn numcast <T> (self) -> Option <Cuboid <T>> where
840    S : num::NumCast,
841    T : num::NumCast
842  {
843    Some (Cuboid {
844      half_extent_x: self.half_extent_x.numcast()?,
845      half_extent_y: self.half_extent_y.numcast()?,
846      half_extent_z: self.half_extent_z.numcast()?
847    })
848  }
849}