Skip to main content

math_utils/geometry/
shape.rs

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