math_utils/geometry/
shape.rs

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