symtropy_math/
convex_hull.rs1use crate::point::Point;
5use crate::shape::Shape;
6use nalgebra::SVector;
7
8#[derive(Clone, Debug)]
14pub struct ConvexHull<const D: usize> {
15 pub vertices: Vec<SVector<f64, D>>,
16 center: SVector<f64, D>,
18 radius: f64,
19}
20
21impl<const D: usize> ConvexHull<D> {
22 pub fn new(vertices: Vec<SVector<f64, D>>) -> Self {
24 assert!(
25 !vertices.is_empty(),
26 "convex hull needs at least one vertex"
27 );
28
29 let mut center = SVector::zeros();
31 for v in &vertices {
32 center += v;
33 }
34 center /= vertices.len() as f64;
35
36 let radius = vertices
38 .iter()
39 .map(|v| (v - center).norm())
40 .fold(0.0f64, f64::max);
41
42 Self {
43 vertices,
44 center,
45 radius,
46 }
47 }
48
49 pub fn num_vertices(&self) -> usize {
51 self.vertices.len()
52 }
53
54 pub fn hyperbox(half_extents: [f64; D]) -> Self {
56 let n = 1 << D;
58 let mut vertices = Vec::with_capacity(n);
59 for mask in 0..n {
60 let mut v = SVector::zeros();
61 for axis in 0..D {
62 v[axis] = if (mask >> axis) & 1 == 0 {
63 -half_extents[axis]
64 } else {
65 half_extents[axis]
66 };
67 }
68 vertices.push(v);
69 }
70 Self::new(vertices)
71 }
72
73 pub fn unit_cube() -> Self {
75 Self::hyperbox([1.0; D])
76 }
77}
78
79impl<const D: usize> Shape<D> for ConvexHull<D> {
80 fn support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
81 let mut best = self.vertices[0];
82 let mut best_dot = direction.dot(&best);
83 for v in &self.vertices[1..] {
84 let d = direction.dot(v);
85 if d > best_dot {
86 best_dot = d;
87 best = *v;
88 }
89 }
90 best
91 }
92
93 fn bounding_sphere(&self) -> (Point<D>, f64) {
94 (Point(self.center), self.radius)
95 }
96
97 fn as_any(&self) -> &dyn std::any::Any {
98 self
99 }
100
101 fn clone_box(&self) -> Box<dyn Shape<D>> {
102 Box::new(self.clone())
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn unit_cube_2d() {
112 let cube = ConvexHull::<2>::unit_cube();
113 assert_eq!(cube.num_vertices(), 4); }
115
116 #[test]
117 fn unit_cube_3d() {
118 let cube = ConvexHull::<3>::unit_cube();
119 assert_eq!(cube.num_vertices(), 8); }
121
122 #[test]
123 fn unit_cube_4d() {
124 let cube = ConvexHull::<4>::unit_cube();
125 assert_eq!(cube.num_vertices(), 16); }
127
128 #[test]
129 fn support_returns_correct_vertex() {
130 let cube = ConvexHull::<3>::unit_cube();
131 let dir = SVector::from([1.0, 1.0, 1.0]);
132 let sp = cube.support(&dir);
133 assert!((sp[0] - 1.0).abs() < 1e-12);
135 assert!((sp[1] - 1.0).abs() < 1e-12);
136 assert!((sp[2] - 1.0).abs() < 1e-12);
137 }
138
139 #[test]
140 fn support_negative_direction() {
141 let cube = ConvexHull::<3>::unit_cube();
142 let dir = SVector::from([-1.0, 0.0, 0.0]);
143 let sp = cube.support(&dir);
144 assert!((sp[0] + 1.0).abs() < 1e-12); }
146
147 #[test]
148 fn bounding_sphere_contains_all_vertices() {
149 let cube = ConvexHull::<4>::unit_cube();
150 let (center, radius) = cube.bounding_sphere();
151 for v in &cube.vertices {
152 let dist = Point(*v).distance(¢er);
153 assert!(dist <= radius + 1e-10);
154 }
155 }
156
157 #[test]
158 fn custom_hyperbox() {
159 let box3 = ConvexHull::<3>::hyperbox([2.0, 3.0, 4.0]);
160 let dir = SVector::from([1.0, 0.0, 0.0]);
161 let sp = box3.support(&dir);
162 assert!((sp[0] - 2.0).abs() < 1e-12);
163 }
164
165 #[test]
166 fn triangle_support() {
167 let tri = ConvexHull::<2>::new(vec![
168 SVector::from([0.0, 0.0]),
169 SVector::from([1.0, 0.0]),
170 SVector::from([0.5, 1.0]),
171 ]);
172 let dir = SVector::from([0.0, 1.0]);
173 let sp = tri.support(&dir);
174 assert!((sp[1] - 1.0).abs() < 1e-12);
175 }
176}