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  show!(size_of::<Variant   <f32>>());
251  show!(size_of::<Variant   <f64>>());
252
253  show!(size_of::<Bounded   <f32>>());
254  show!(size_of::<Bounded   <f64>>());
255
256  show!(size_of::<Unbounded <f32>>());
257  show!(size_of::<Unbounded <f64>>());
258
259  show!(size_of::<Sphere    <f32>>());
260  show!(size_of::<Capsule   <f32>>());
261  show!(size_of::<Cylinder  <f32>>());
262  show!(size_of::<Cone      <f32>>());
263  show!(size_of::<Cube      <f32>>());
264  show!(size_of::<Cuboid    <f32>>());
265
266  show!(size_of::<Orthant>());
267  show!(size_of::<Halfspace <f32>>());
268
269  show!(size_of::<Sphere    <f64>>());
270  show!(size_of::<Capsule   <f64>>());
271  show!(size_of::<Cylinder  <f64>>());
272  show!(size_of::<Cone      <f64>>());
273  show!(size_of::<Cube      <f64>>());
274  show!(size_of::<Cuboid    <f64>>());
275
276  show!(size_of::<Orthant>());
277  show!(size_of::<Halfspace <f64>>());
278
279  println!("...shape report sizes");
280}
281
282////////////////////////////////////////////////////////////////////////////////
283//  impls                                                                     //
284////////////////////////////////////////////////////////////////////////////////
285
286//
287//  impl Variant
288//
289impl <S> Shape <S> for Variant <S> where
290  S : Real + num::float::FloatCore + std::fmt::Debug
291{ }
292impl <S> Aabb <S> for Variant <S> where
293  S : Real + num::float::FloatCore + std::fmt::Debug
294{
295  /// Note that unbounded shapes will return a Cuboid with some infinite half
296  /// extents
297  fn aabb (&self) -> Aabb3 <S> {
298    match self {
299      Variant::Bounded   (bounded)   => bounded.aabb(),
300      Variant::Unbounded (unbounded) => unbounded.aabb()
301    }
302  }
303}
304impl <S> Stereometric <S> for Variant <S> where
305  S : Real + num::float::FloatCore + std::fmt::Debug
306{
307  /// Note that unbounded shapes will return a Cuboid with some infinite half
308  /// extents
309  fn volume (&self) -> Positive <S> {
310    match self {
311      Variant::Bounded   (bounded)   => bounded.volume(),
312      Variant::Unbounded (unbounded) => unbounded.volume()
313    }
314  }
315}
316
317//
318//  impl Bounded
319//
320impl <S> TryFrom <Variant <S>> for Bounded <S> where S : Real + std::fmt::Debug {
321  type Error = Variant <S>;
322  fn try_from (variant : Variant <S>) -> Result <Self, Self::Error> {
323    match variant {
324      Variant::Bounded (bounded) => Ok (bounded),
325      _ => Err (variant)
326    }
327  }
328}
329impl <S> Shape <S> for Bounded <S> where S : Real + std::fmt::Debug { }
330impl <S> Aabb <S> for Bounded <S> where S : Real + std::fmt::Debug {
331  fn aabb (&self) -> Aabb3 <S> {
332    match self {
333      Bounded::Sphere   (sphere)   => sphere.aabb(),
334      Bounded::Capsule  (capsule)  => capsule.aabb(),
335      Bounded::Cylinder (cylinder) => cylinder.aabb(),
336      Bounded::Cone     (cone)     => cone.aabb(),
337      Bounded::Cube     (cube)     => cube.aabb(),
338      Bounded::Cuboid   (cuboid)   => cuboid.aabb()
339    }
340  }
341}
342impl <S : Real> Stereometric <S> for Bounded <S> {
343  /// Volume of a bounded solid is always finite
344  fn volume (&self) -> Positive <S> {
345    match self {
346      Bounded::Sphere   (sphere)   => sphere.volume(),
347      Bounded::Capsule  (capsule)  => capsule.volume(),
348      Bounded::Cylinder (cylinder) => cylinder.volume(),
349      Bounded::Cone     (cone)     => cone.volume(),
350      Bounded::Cube     (cube)     => cube.volume(),
351      Bounded::Cuboid   (cuboid)   => cuboid.volume()
352    }
353  }
354}
355
356//
357//  impl Unbounded
358//
359impl <S : Real> TryFrom <Variant <S>> for Unbounded <S> {
360  type Error = Variant <S>;
361  fn try_from (variant : Variant <S>) -> Result <Self, Self::Error> {
362    match variant {
363      Variant::Unbounded (unbounded) => Ok (unbounded),
364      _ => Err (variant)
365    }
366  }
367}
368impl <S> Shape <S> for Unbounded <S> where
369  S : Real + num::float::FloatCore + std::fmt::Debug
370{ }
371impl <S> Aabb <S> for Unbounded <S> where
372  S : Real + num::float::FloatCore + std::fmt::Debug
373{
374  fn aabb (&self) -> Aabb3 <S> {
375    match self {
376      Unbounded::Orthant   (orthant)   => orthant.aabb(),
377      Unbounded::Halfspace (halfspace) => halfspace.aabb()
378    }
379  }
380}
381impl <S> Stereometric <S> for Unbounded <S> where
382  S : Real + num::float::FloatCore + std::fmt::Debug
383{
384  /// Volume of an unbounded solid is always infinite
385  fn volume (&self) -> Positive <S> {
386    if cfg!(debug_assertions) {
387      let volume = match self {
388        Unbounded::Orthant   (orthant)   => orthant.volume(),
389        Unbounded::Halfspace (halfspace) => halfspace.volume()
390      };
391      debug_assert_eq!(*volume, S::infinity());
392    }
393    Positive::infinity()
394  }
395}
396
397//
398//  impl Sphere
399//
400impl <S : Ring> Sphere <S> {
401  #[inline]
402  /// Sphere with radius 1.0
403  pub fn unit() -> Self where S : Field {
404    use num::One;
405    Sphere { radius: Positive::one() }
406  }
407  /// Create a new sphere with the absolute value of the given radius.
408  ///
409  /// Panics if radius is zero:
410  ///
411  /// ```should_panic
412  /// # use math_utils::geometry::shape::Sphere;
413  /// let s = Sphere::noisy (0.0);
414  /// ```
415  #[inline]
416  pub fn noisy (radius : S) -> Self where S : std::fmt::Debug {
417    assert_ne!(radius, S::zero());
418    Sphere { radius: Positive::unchecked (radius.abs()) }
419  }
420  /// Create a new sphere with the absolute value of the given radius.
421  ///
422  /// Debug panic if radius is zero:
423  ///
424  /// ```should_panic
425  /// # use math_utils::geometry::shape::Sphere;
426  /// let s = Sphere::noisy (0.0);
427  /// ```
428  #[inline]
429  pub fn unchecked (radius : S) -> Self where S : std::fmt::Debug {
430    debug_assert_ne!(radius, S::zero());
431    Sphere { radius: Positive::unchecked (radius.abs()) }
432  }
433  #[inline]
434  pub fn sphere3 (&self, center : Point3 <S>) -> Sphere3 <S> {
435    Sphere3 { center, radius: self.radius }
436  }
437}
438impl <S : Real> TryFrom <Bounded <S>> for Sphere <S> {
439  type Error = Bounded <S>;
440  fn try_from (bounded : Bounded <S>) -> Result <Self, Self::Error> {
441    match bounded {
442      Bounded::Sphere (sphere) => Ok (sphere),
443      _ => Err (bounded)
444    }
445  }
446}
447impl <S> Shape <S> for Sphere <S> where S : Real + std::fmt::Debug { }
448impl <S> Aabb <S> for Sphere <S> where S : Real + std::fmt::Debug {
449  fn aabb (&self) -> Aabb3 <S> {
450    Aabb3::with_minmax ([-*self.radius; 3].into(), [ *self.radius; 3].into())
451  }
452}
453impl <S : Real> Stereometric <S> for Sphere <S> {
454  fn volume (&self) -> Positive <S> {
455    let four      = Positive::unchecked (S::four());
456    let frac_pi_3 = Positive::unchecked (S::frac_pi_3());
457    four * frac_pi_3 * self.radius * self.radius * self.radius
458  }
459}
460
461//
462//  impl Capsule
463//
464impl <S : Ring> Capsule <S> {
465  /// Create a new capsule with the absolute values of the given radius and
466  /// half-height.
467  ///
468  /// Panics if radius is zero:
469  ///
470  /// ```should_panic
471  /// # use math_utils::geometry::shape::Capsule;
472  /// let s = Capsule::noisy (0.0, 2.0);
473  /// ```
474  ///
475  /// Panics if half-height is zero:
476  ///
477  /// ```should_panic
478  /// # use math_utils::geometry::shape::Capsule;
479  /// let s = Capsule::noisy (2.0, 0.0);
480  /// ```
481  #[inline]
482  pub fn noisy (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
483    assert_ne!(radius, S::zero());
484    assert_ne!(half_height, S::zero());
485    let radius      = Positive::unchecked (radius.abs());
486    let half_height = Positive::unchecked (half_height.abs());
487    Capsule { radius, half_height }
488  }
489  /// Create a new capsule with the absolute values of the given radius and
490  /// half-height.
491  ///
492  /// Debug panic if radius is zero:
493  ///
494  /// ```should_panic
495  /// # use math_utils::geometry::shape::Capsule;
496  /// let s = Capsule::unchecked (0.0, 2.0);
497  /// ```
498  ///
499  /// Debug panic if half-height is zero:
500  ///
501  /// ```should_panic
502  /// # use math_utils::geometry::shape::Capsule;
503  /// let s = Capsule::noisy (2.0, 0.0);
504  /// ```
505  #[inline]
506  pub fn unchecked (radius : S, half_height : S) -> Self where
507    S : std::fmt::Debug
508  {
509    debug_assert_ne!(radius, S::zero());
510    let radius      = Positive::unchecked (radius.abs());
511    let half_height = Positive::unchecked (half_height.abs());
512    Capsule { radius, half_height }
513  }
514  /// Height of the cylinder portion
515  #[inline]
516  pub fn height (&self) -> NonNegative <S> where S : Field {
517    self.half_height * NonNegative::unchecked (S::two())
518  }
519  #[inline]
520  pub fn capsule3 (&self, center : Point3 <S>) -> Capsule3 <S> {
521    Capsule3 { center, radius: self.radius, half_height: self.half_height }
522  }
523}
524impl <S> Shape <S> for Capsule <S> where S : Real + std::fmt::Debug { }
525impl <S> Aabb <S> for Capsule <S> where S : Real + std::fmt::Debug {
526  fn aabb (&self) -> Aabb3 <S> {
527    let r  = *self.radius;
528    let hh = *self.half_height;
529    Aabb3::with_minmax ([-r, -r, -r - hh].into(), [ r,  r,  r + hh].into())
530  }
531}
532impl <S : Real> Stereometric <S> for Capsule <S> {
533  fn volume (&self) -> Positive <S> {
534    let r               = self.radius;
535    let h               = self.height();
536    let r2              = r * r;
537    let r3              = r2 * r;
538    let pi              = Positive::unchecked (S::pi());
539    let four            = Positive::unchecked (S::four());
540    let frac_pi_3       = Positive::unchecked (S::frac_pi_3());
541    let cylinder_volume = pi * r2 * h;
542    let sphere_volume   = four * frac_pi_3 * r3;
543    sphere_volume + cylinder_volume
544  }
545}
546
547//
548//  impl Cylinder
549//
550impl <S : Ring> Cylinder <S> {
551  #[inline]
552  /// Cylinder with radius 1.0 and half-height 1.0
553  pub fn unit() -> Self where S : Field {
554    use num::One;
555    Cylinder {
556      radius: Positive::one(), half_height: Positive::one()
557    }
558  }
559  /// Create a new cylinder with the absolute values of the given radius and
560  /// half-height.
561  ///
562  /// Panics if radius or half-height are zero:
563  ///
564  /// ```should_panic
565  /// # use math_utils::geometry::shape::Cylinder;
566  /// let s = Cylinder::noisy (0.0, 0.0);
567  /// ```
568  #[inline]
569  pub fn noisy (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
570    assert_ne!(radius,      S::zero());
571    assert_ne!(half_height, S::zero());
572    let radius      = Positive::unchecked (radius.abs());
573    let half_height = Positive::unchecked (half_height.abs());
574    Cylinder { radius, half_height }
575  }
576  /// Create a new cylinder with the absolute values of the given radius and
577  /// half-height.
578  ///
579  /// Debug panic if radius or half-height are zero:
580  ///
581  /// ```should_panic
582  /// # use math_utils::geometry::shape::Cylinder;
583  /// let s = Cylinder::unchecked (0.0, 0.0);
584  /// ```
585  #[inline]
586  pub fn unchecked (radius : S, half_height : S) -> Self where
587    S : std::fmt::Debug
588  {
589    debug_assert_ne!(radius,      S::zero());
590    debug_assert_ne!(half_height, S::zero());
591    let radius      = Positive::unchecked (radius.abs());
592    let half_height = Positive::unchecked (half_height.abs());
593    Cylinder { radius, half_height }
594  }
595  #[inline]
596  pub fn cylinder3 (&self, center : Point3 <S>) -> Cylinder3 <S> {
597    Cylinder3 { center, radius: self.radius, half_height: self.half_height }
598  }
599  #[inline]
600  pub fn height (&self) -> Positive <S> where S : Field {
601    self.half_height * Positive::unchecked (S::two())
602  }
603}
604impl <S> Shape <S> for Cylinder <S> where S : Real + std::fmt::Debug { }
605impl <S> Aabb <S> for Cylinder <S> where S : Real + std::fmt::Debug {
606  fn aabb (&self) -> Aabb3 <S> {
607    let r  = *self.radius;
608    let hh = *self.half_height;
609    Aabb3::with_minmax ([-r, -r, -hh].into(), [ r,  r,  hh].into())
610  }
611}
612impl <S : Real> Stereometric <S> for Cylinder <S> {
613  fn volume (&self) -> Positive <S> {
614    let pi = Positive::unchecked (S::pi());
615    pi * self.radius * self.radius * self.height()
616  }
617}
618
619//
620//  impl Cone
621//
622impl <S : Ring> Cone <S> {
623  /// Create a new cone with the absolute values of the given radius and
624  /// half-height.
625  ///
626  /// Panics if radius or half-height are zero:
627  ///
628  /// ```should_panic
629  /// # use math_utils::geometry::shape::Cone;
630  /// let s = Cone::noisy (0.0, 0.0);
631  /// ```
632  #[inline]
633  pub fn noisy (radius : S, half_height : S) -> Self where S : std::fmt::Debug {
634    assert_ne!(radius,      S::zero());
635    assert_ne!(half_height, S::zero());
636    let radius      = Positive::unchecked (radius.abs());
637    let half_height = Positive::unchecked (half_height.abs());
638    Cone { radius, half_height }
639  }
640}
641impl <S> Shape <S> for Cone <S> where S : Real + std::fmt::Debug { }
642impl <S> Aabb <S> for Cone <S> where S : Real + std::fmt::Debug {
643  fn aabb (&self) -> Aabb3 <S> {
644    let r  = *self.radius;
645    let hh = *self.half_height;
646    Aabb3::with_minmax ([-r, -r, -hh].into(), [ r,  r,  hh].into())
647  }
648}
649impl <S : Real> Stereometric <S> for Cone <S> {
650  fn volume (&self) -> Positive <S> {
651    let frac_pi_3 = Positive::unchecked (S::frac_pi_3());
652    let two       = Positive::unchecked (S::two());
653    frac_pi_3 * self.radius * self.radius * two * self.half_height
654  }
655}
656
657//
658//  impl Cube
659//
660impl <S : Ring> Cube <S> {
661  /// Create a new cube with the absolute value of the given half-extent.
662  ///
663  /// Panics if half-extent is zero:
664  ///
665  /// ```should_panic
666  /// # use math_utils::geometry::shape::Cube;
667  /// let s = Cube::noisy (0.0);
668  /// ```
669  #[inline]
670  pub fn noisy (half_extent : S) -> Self where S : std::fmt::Debug {
671    assert_ne!(half_extent, S::zero());
672    let half_extent = Positive::unchecked (half_extent.abs());
673    Cube { half_extent }
674  }
675  #[inline]
676  pub fn extent (&self) -> Positive <S> where S : Field {
677    self.half_extent * Positive::unchecked (S::two())
678  }
679}
680impl <S> Shape <S> for Cube <S> where S : Field + std::fmt::Debug { }
681impl <S> Aabb <S> for Cube <S> where S : Field + std::fmt::Debug {
682  fn aabb (&self) -> Aabb3 <S> {
683    Aabb3::with_minmax (
684      [-*self.half_extent; 3].into(),
685      [ *self.half_extent; 3].into())
686  }
687}
688impl <S : Field> Stereometric <S> for Cube <S> {
689  fn volume (&self) -> Positive <S> {
690    let extent = self.extent();
691    extent * extent * extent
692  }
693}
694
695//
696//  impl Cuboid
697//
698impl <S : Ring> Cuboid <S> {
699  /// Create a new cuboid with the absolute values of the given half-extents.
700  ///
701  /// Panics if any half-extent is zero:
702  ///
703  /// ```should_panic
704  /// # use math_utils::geometry::shape::Cuboid;
705  /// let s = Cuboid::noisy ([0.0, 0.0, 0.0]);
706  /// ```
707  #[inline]
708  pub fn noisy (half_extents : [S; 3]) -> Self where S : std::fmt::Debug {
709    assert_ne!(half_extents[0], S::zero());
710    assert_ne!(half_extents[1], S::zero());
711    assert_ne!(half_extents[2], S::zero());
712    let half_extent_x = Positive::unchecked (half_extents[0].abs());
713    let half_extent_y = Positive::unchecked (half_extents[1].abs());
714    let half_extent_z = Positive::unchecked (half_extents[2].abs());
715    Cuboid { half_extent_x, half_extent_y, half_extent_z }
716  }
717  #[inline]
718  pub fn extents (&self) -> [Positive <S>; 3] where S : Field {
719    let two = Positive::unchecked (S::two());
720    [
721      self.half_extent_x * two,
722      self.half_extent_y * two,
723      self.half_extent_z * two
724    ]
725  }
726  #[inline]
727  pub fn half_extents_vec (&self) -> Vector3 <S> {
728    [ *self.half_extent_x,
729      *self.half_extent_y,
730      *self.half_extent_z
731    ].into()
732  }
733  #[inline]
734  pub fn max (&self) -> Point3 <S> {
735    Vector3 {
736      x: *self.half_extent_x,
737      y: *self.half_extent_y,
738      z: *self.half_extent_z
739    }.into()
740  }
741  #[inline]
742  pub fn min (&self) -> Point3 <S> {
743    Vector3 {
744      x: -*self.half_extent_x,
745      y: -*self.half_extent_y,
746      z: -*self.half_extent_z
747    }.into()
748  }
749  #[inline]
750  pub fn aabb3 (&self, center : Point3 <S>) -> Aabb3 <S> where
751    S : std::fmt::Debug
752  {
753    Aabb3::with_minmax (
754      center + self.min().0,
755      center + self.max().0
756    )
757  }
758}
759impl <S> From <Aabb3 <S>> for Cuboid <S> where S : Field {
760  fn from (aabb : Aabb3 <S>) -> Self {
761    // TODO: aabbs do not enforce zero width/depth/height so this is unsafe
762    Cuboid {
763      half_extent_x: Positive::unchecked (*aabb.width()  / S::two()),
764      half_extent_y: Positive::unchecked (*aabb.depth()  / S::two()),
765      half_extent_z: Positive::unchecked (*aabb.height() / S::two())
766    }
767  }
768}
769impl <S> Shape <S> for Cuboid <S> where S : Field + std::fmt::Debug { }
770impl <S> Aabb <S>  for Cuboid <S> where S : Field + std::fmt::Debug {
771  fn aabb (&self) -> Aabb3 <S> {
772    Aabb3::with_minmax (self.min(), self.max())
773  }
774}
775impl <S : Field> Stereometric <S> for Cuboid <S> {
776  fn volume (&self) -> Positive <S> {
777    let [x, y, z] = self.extents();
778    x * y * z
779  }
780}
781
782//
783//  impl Orthant
784//
785impl Orthant {
786  pub fn try_from <S : Real> (halfspace : &Halfspace <S>) -> Option <Self> {
787    SignedAxis3::try_from (&halfspace.normal_vector)
788      .map (|normal_axis| Orthant { normal_axis })
789  }
790}
791impl From <SignedAxis3> for Orthant {
792  fn from (normal_axis : SignedAxis3) -> Self {
793    Orthant { normal_axis }
794  }
795}
796impl <S> Shape <S> for Orthant where
797  S : Field + num::float::FloatCore + std::fmt::Debug
798{ }
799impl <S> Aabb <S> for Orthant where
800  S : Field + num::float::FloatCore + std::fmt::Debug
801{
802  /// Computes the containing Aabb3.
803  ///
804  /// ```
805  /// # use math_utils::geometry::shape::Orthant;
806  /// # use math_utils::geometry::Aabb3;
807  /// # use math_utils::SignedAxis3;
808  /// use math_utils::geometry::shape::Aabb;
809  /// let s = Orthant::from (SignedAxis3::PosZ);
810  /// assert_eq!(s.aabb(), Aabb3::with_minmax (
811  ///   [f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY].into(),
812  ///   [f32::INFINITY,     f32::INFINITY,     0.0].into()
813  /// ));
814  /// let s = Orthant::from (SignedAxis3::NegZ);
815  /// assert_eq!(s.aabb(), Aabb3::with_minmax (
816  ///   [f32::NEG_INFINITY, f32::NEG_INFINITY, 0.0].into(),
817  ///   [f32::INFINITY,     f32::INFINITY,     f32::INFINITY].into()
818  /// ));
819  /// ```
820  fn aabb (&self) -> Aabb3 <S> {
821    use num::float::FloatCore;
822    let axis_vec = self.normal_axis.to_vec::<S>();
823    let surface  = Vector3::broadcast (S::one()) - axis_vec.map (FloatCore::abs);
824
825    let do_min = |i| if surface[i] == S::one() || axis_vec[i] == S::one() {
826      S::neg_infinity()
827    } else {
828      debug_assert_eq!(axis_vec[i], -S::one());
829      S::zero()
830    };
831    let do_max = |i| if surface[i] == S::one() {
832      S::infinity()
833    } else if axis_vec[i] == S::one() {
834      S::zero()
835    } else {
836      debug_assert_eq!(axis_vec[i], -S::one());
837      S::infinity()
838    };
839    let min = [ do_min (0), do_min (1), do_min (2) ].into();
840    let max = [ do_max (0), do_max (1), do_max (2) ].into();
841
842    Aabb3::with_minmax (min, max)
843  }
844}
845impl <S> Stereometric <S> for Orthant where S : Ring + num::float::FloatCore {
846  /// Volume of an unbounded solid is always infinite
847  fn volume (&self) -> Positive <S> {
848    Positive::infinity()
849  }
850}
851
852//
853//  impl Halfspace
854//
855impl <S : Real> From <Unit3 <S>> for Halfspace <S> {
856  fn from (normal_vector : Unit3 <S>) -> Self {
857    Halfspace { normal_vector }
858  }
859}
860impl <S> Shape <S> for Halfspace <S> where
861  S : Real + num::float::FloatCore + std::fmt::Debug
862{ }
863impl <S> Aabb <S> for Halfspace <S> where
864  S : Real + num::float::FloatCore + std::fmt::Debug
865{
866  fn aabb (&self) -> Aabb3 <S> {
867    if let Some (orthant) = Orthant::try_from (self) {
868      orthant.aabb()
869    } else {
870      Aabb3::with_minmax (
871        [S::neg_infinity(); 3].into(),
872        [S::infinity(); 3].into())
873    }
874  }
875}
876impl <S> Stereometric <S> for Halfspace <S> where
877  S : Real + num::float::FloatCore
878{
879  /// Volume of an unbounded solid is always infinite
880  fn volume (&self) -> Positive <S> {
881    Positive::infinity()
882  }
883}