symtropy_math/
hyperbox.rs1use crate::point::Point;
11use crate::shape::Shape;
12use nalgebra::SVector;
13
14#[derive(Clone, Copy, Debug)]
19pub struct HyperBox<const D: usize> {
20 pub half_extents: [f64; D],
21}
22
23impl<const D: usize> HyperBox<D> {
24 pub fn new(half_extents: [f64; D]) -> Self {
26 Self { half_extents }
27 }
28
29 pub fn cube(half_extent: f64) -> Self {
31 Self {
32 half_extents: [half_extent; D],
33 }
34 }
35
36 pub fn volume(&self) -> f64 {
38 self.half_extents.iter().map(|h| 2.0 * h).product()
39 }
40
41 pub fn contains_local(&self, point: &Point<D>) -> bool {
43 (0..D).all(|i| point.coord(i).abs() <= self.half_extents[i])
44 }
45}
46
47impl<const D: usize> Shape<D> for HyperBox<D> {
48 fn support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
53 SVector::from_fn(|i, _| {
54 if direction[i] >= 0.0 {
55 self.half_extents[i]
56 } else {
57 -self.half_extents[i]
58 }
59 })
60 }
61
62 fn bounding_sphere(&self) -> (Point<D>, f64) {
63 let radius = self.half_extents.iter().map(|h| h * h).sum::<f64>().sqrt();
64 (Point::origin(), radius)
65 }
66
67 fn as_any(&self) -> &dyn std::any::Any {
68 self
69 }
70
71 fn clone_box(&self) -> Box<dyn Shape<D>> {
72 Box::new(*self)
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn support_axis_aligned() {
82 let b = HyperBox::<3>::new([2.0, 3.0, 1.0]);
83 let dir = SVector::from([1.0, 0.0, 0.0]);
84 let sp = b.support(&dir);
85 assert!((sp[0] - 2.0).abs() < 1e-12);
86 assert!((sp[1] - 3.0).abs() < 1e-12); assert!((sp[2] - 1.0).abs() < 1e-12);
88 }
89
90 #[test]
91 fn support_negative() {
92 let b = HyperBox::<3>::cube(1.0);
93 let dir = SVector::from([-1.0, -1.0, -1.0]);
94 let sp = b.support(&dir);
95 assert!((sp[0] - (-1.0)).abs() < 1e-12);
96 assert!((sp[1] - (-1.0)).abs() < 1e-12);
97 assert!((sp[2] - (-1.0)).abs() < 1e-12);
98 }
99
100 #[test]
101 fn support_dot_matches_convex_hull() {
102 use crate::convex_hull::ConvexHull;
103
104 let hb = HyperBox::<3>::new([2.0, 1.0, 3.0]);
105 let ch = ConvexHull::<3>::hyperbox([2.0, 1.0, 3.0]);
106
107 let dirs = [
110 SVector::from([1.0, 0.0, 0.0]),
111 SVector::from([0.0, -1.0, 0.0]),
112 SVector::from([1.0, 1.0, 1.0]),
113 SVector::from([-0.5, 0.3, 0.8]),
114 SVector::from([0.7, -0.7, 0.1]),
115 ];
116 for dir in &dirs {
117 let dot_hb = hb.support(dir).dot(dir);
118 let dot_ch = ch.support(dir).dot(dir);
119 assert!(
120 (dot_hb - dot_ch).abs() < 1e-10,
121 "support·dir differs for {:?}: hb={dot_hb}, ch={dot_ch}",
122 dir
123 );
124 }
125 }
126
127 #[test]
128 fn bounding_sphere_radius() {
129 let b = HyperBox::<3>::new([3.0, 4.0, 0.0]);
130 let (_, radius) = b.bounding_sphere();
131 assert!((radius - 5.0).abs() < 1e-12); }
133
134 #[test]
135 fn volume() {
136 let b = HyperBox::<3>::new([1.0, 2.0, 3.0]);
137 assert!((b.volume() - 48.0).abs() < 1e-12); }
139
140 #[test]
141 fn contains_local() {
142 let b = HyperBox::<3>::cube(1.0);
143 assert!(b.contains_local(&Point::origin()));
144 assert!(b.contains_local(&Point::new([0.5, 0.5, 0.5])));
145 assert!(!b.contains_local(&Point::new([1.5, 0.0, 0.0])));
146 }
147
148 #[test]
149 fn tesseract_4d() {
150 let b = HyperBox::<4>::cube(1.0);
151 let dir = SVector::from([1.0, 1.0, 1.0, 1.0]);
152 let sp = b.support(&dir);
153 for i in 0..4 {
154 assert!((sp[i] - 1.0).abs() < 1e-12);
155 }
156 let (_, radius) = b.bounding_sphere();
157 assert!((radius - 2.0).abs() < 1e-12); }
159
160 #[test]
161 fn hyperbox_2d() {
162 let b = HyperBox::<2>::new([3.0, 1.0]);
163 let dir = SVector::from([1.0, -1.0]);
164 let sp = b.support(&dir);
165 assert!((sp[0] - 3.0).abs() < 1e-12);
166 assert!((sp[1] - (-1.0)).abs() < 1e-12);
167 }
168
169 #[test]
170 fn cube_is_uniform() {
171 let b = HyperBox::<3>::cube(2.5);
172 assert!((b.half_extents[0] - 2.5).abs() < 1e-12);
173 assert!((b.half_extents[1] - 2.5).abs() < 1e-12);
174 assert!((b.half_extents[2] - 2.5).abs() < 1e-12);
175 }
176}