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!(!vertices.is_empty(), "convex hull needs at least one vertex");
25
26 let mut center = SVector::zeros();
28 for v in &vertices {
29 center += v;
30 }
31 center /= vertices.len() as f64;
32
33 let radius = vertices
35 .iter()
36 .map(|v| (v - center).norm())
37 .fold(0.0f64, f64::max);
38
39 Self {
40 vertices,
41 center,
42 radius,
43 }
44 }
45
46 pub fn num_vertices(&self) -> usize {
48 self.vertices.len()
49 }
50
51 pub fn hyperbox(half_extents: [f64; D]) -> Self {
53 let n = 1 << D;
55 let mut vertices = Vec::with_capacity(n);
56 for mask in 0..n {
57 let mut v = SVector::zeros();
58 for axis in 0..D {
59 v[axis] = if (mask >> axis) & 1 == 0 {
60 -half_extents[axis]
61 } else {
62 half_extents[axis]
63 };
64 }
65 vertices.push(v);
66 }
67 Self::new(vertices)
68 }
69
70 pub fn unit_cube() -> Self {
72 Self::hyperbox([1.0; D])
73 }
74}
75
76impl<const D: usize> Shape<D> for ConvexHull<D> {
77 fn support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
78 let mut best = self.vertices[0];
79 let mut best_dot = direction.dot(&best);
80 for v in &self.vertices[1..] {
81 let d = direction.dot(v);
82 if d > best_dot {
83 best_dot = d;
84 best = *v;
85 }
86 }
87 best
88 }
89
90 fn bounding_sphere(&self) -> (Point<D>, f64) {
91 (Point(self.center), self.radius)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn unit_cube_2d() {
101 let cube = ConvexHull::<2>::unit_cube();
102 assert_eq!(cube.num_vertices(), 4); }
104
105 #[test]
106 fn unit_cube_3d() {
107 let cube = ConvexHull::<3>::unit_cube();
108 assert_eq!(cube.num_vertices(), 8); }
110
111 #[test]
112 fn unit_cube_4d() {
113 let cube = ConvexHull::<4>::unit_cube();
114 assert_eq!(cube.num_vertices(), 16); }
116
117 #[test]
118 fn support_returns_correct_vertex() {
119 let cube = ConvexHull::<3>::unit_cube();
120 let dir = SVector::from([1.0, 1.0, 1.0]);
121 let sp = cube.support(&dir);
122 assert!((sp[0] - 1.0).abs() < 1e-12);
124 assert!((sp[1] - 1.0).abs() < 1e-12);
125 assert!((sp[2] - 1.0).abs() < 1e-12);
126 }
127
128 #[test]
129 fn support_negative_direction() {
130 let cube = ConvexHull::<3>::unit_cube();
131 let dir = SVector::from([-1.0, 0.0, 0.0]);
132 let sp = cube.support(&dir);
133 assert!((sp[0] + 1.0).abs() < 1e-12); }
135
136 #[test]
137 fn bounding_sphere_contains_all_vertices() {
138 let cube = ConvexHull::<4>::unit_cube();
139 let (center, radius) = cube.bounding_sphere();
140 for v in &cube.vertices {
141 let dist = Point(*v).distance(¢er);
142 assert!(dist <= radius + 1e-10);
143 }
144 }
145
146 #[test]
147 fn custom_hyperbox() {
148 let box3 = ConvexHull::<3>::hyperbox([2.0, 3.0, 4.0]);
149 let dir = SVector::from([1.0, 0.0, 0.0]);
150 let sp = box3.support(&dir);
151 assert!((sp[0] - 2.0).abs() < 1e-12);
152 }
153
154 #[test]
155 fn triangle_support() {
156 let tri = ConvexHull::<2>::new(vec![
157 SVector::from([0.0, 0.0]),
158 SVector::from([1.0, 0.0]),
159 SVector::from([0.5, 1.0]),
160 ]);
161 let dir = SVector::from([0.0, 1.0]);
162 let sp = tri.support(&dir);
163 assert!((sp[1] - 1.0).abs() < 1e-12);
164 }
165}