1use glam::{Vec2, Vec3};
4
5#[derive(Debug, Clone)]
7pub struct ConvexHull2D {
8 pub points: Vec<Vec2>,
9 pub hull_indices: Vec<usize>,
10}
11
12#[derive(Debug, Clone)]
14pub struct ConvexHull {
15 pub points: Vec<Vec3>,
16 pub hull_faces: Vec<[usize; 3]>,
17 pub hull_vertices: Vec<usize>,
18}
19
20impl ConvexHull2D {
21 pub fn compute(points: &[Vec2]) -> Self {
23 let n = points.len();
24 if n < 3 {
25 return Self { points: points.to_vec(), hull_indices: (0..n).collect() };
26 }
27
28 let mut sorted: Vec<(usize, Vec2)> = points.iter().copied().enumerate().collect();
29 sorted.sort_by(|a, b| a.1.x.partial_cmp(&b.1.x).unwrap().then(a.1.y.partial_cmp(&b.1.y).unwrap()));
30
31 let mut hull = Vec::with_capacity(2 * n);
32
33 for &(i, p) in &sorted {
35 while hull.len() >= 2 {
36 let a = points[hull[hull.len() - 2]];
37 let b = points[hull[hull.len() - 1]];
38 if cross2d(a, b, p) <= 0.0 { hull.pop(); } else { break; }
39 }
40 hull.push(i);
41 }
42
43 let lower_len = hull.len() + 1;
45 for &(i, p) in sorted.iter().rev() {
46 while hull.len() >= lower_len {
47 let a = points[hull[hull.len() - 2]];
48 let b = points[hull[hull.len() - 1]];
49 if cross2d(a, b, p) <= 0.0 { hull.pop(); } else { break; }
50 }
51 hull.push(i);
52 }
53 hull.pop(); Self { points: points.to_vec(), hull_indices: hull }
56 }
57
58 pub fn hull_points(&self) -> Vec<Vec2> {
59 self.hull_indices.iter().map(|&i| self.points[i]).collect()
60 }
61
62 pub fn perimeter(&self) -> f32 {
63 let pts = self.hull_points();
64 let n = pts.len();
65 (0..n).map(|i| (pts[(i + 1) % n] - pts[i]).length()).sum()
66 }
67
68 pub fn area(&self) -> f32 {
69 let pts = self.hull_points();
70 let n = pts.len();
71 if n < 3 { return 0.0; }
72 let mut area = 0.0;
73 for i in 0..n {
74 let j = (i + 1) % n;
75 area += pts[i].x * pts[j].y - pts[j].x * pts[i].y;
76 }
77 area.abs() * 0.5
78 }
79}
80
81impl ConvexHull {
82 pub fn compute(points: &[Vec3]) -> Self {
84 let n = points.len();
85 if n < 4 {
86 return Self {
87 points: points.to_vec(),
88 hull_faces: Vec::new(),
89 hull_vertices: (0..n).collect(),
90 };
91 }
92
93 let mut faces: Vec<[usize; 3]> = vec![
95 [0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2],
96 ];
97
98 let center = (points[0] + points[1] + points[2] + points[3]) * 0.25;
100 for face in &mut faces {
101 let a = points[face[0]];
102 let b = points[face[1]];
103 let c = points[face[2]];
104 let n = (b - a).cross(c - a);
105 let mid = (a + b + c) / 3.0;
106 if n.dot(mid - center) < 0.0 {
107 face.swap(1, 2);
108 }
109 }
110
111 for pi in 4..n {
113 let p = points[pi];
114 let mut visible = Vec::new();
115
116 for (fi, face) in faces.iter().enumerate() {
117 let a = points[face[0]];
118 let b = points[face[1]];
119 let c = points[face[2]];
120 let normal = (b - a).cross(c - a);
121 if normal.dot(p - a) > 1e-8 {
122 visible.push(fi);
123 }
124 }
125
126 if visible.is_empty() { continue; }
127
128 let mut horizon: Vec<(usize, usize)> = Vec::new();
130 for &fi in &visible {
131 let face = faces[fi];
132 let edges = [(face[0], face[1]), (face[1], face[2]), (face[2], face[0])];
133 for &(a, b) in &edges {
134 let is_shared = visible.iter().any(|&ofi| {
135 if ofi == fi { return false; }
136 let of = faces[ofi];
137 let oedges = [(of[0], of[1]), (of[1], of[2]), (of[2], of[0])];
138 oedges.iter().any(|&(oa, ob)| (oa == b && ob == a))
139 });
140 if !is_shared {
141 horizon.push((a, b));
142 }
143 }
144 }
145
146 visible.sort_unstable_by(|a, b| b.cmp(a));
148 for fi in visible {
149 faces.swap_remove(fi);
150 }
151
152 for (a, b) in horizon {
154 faces.push([a, b, pi]);
155 }
156 }
157
158 let mut hull_verts: Vec<usize> = faces.iter()
159 .flat_map(|f| f.iter().copied())
160 .collect();
161 hull_verts.sort_unstable();
162 hull_verts.dedup();
163
164 Self {
165 points: points.to_vec(),
166 hull_faces: faces,
167 hull_vertices: hull_verts,
168 }
169 }
170
171 pub fn face_count(&self) -> usize { self.hull_faces.len() }
172 pub fn vertex_count(&self) -> usize { self.hull_vertices.len() }
173}
174
175fn cross2d(o: Vec2, a: Vec2, b: Vec2) -> f32 {
176 (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn hull_2d_square() {
185 let points = vec![
186 Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0),
187 Vec2::new(1.0, 1.0), Vec2::new(0.0, 1.0),
188 Vec2::new(0.5, 0.5), ];
190 let hull = ConvexHull2D::compute(&points);
191 assert_eq!(hull.hull_indices.len(), 4); }
193
194 #[test]
195 fn hull_2d_area() {
196 let points = vec![
197 Vec2::new(0.0, 0.0), Vec2::new(2.0, 0.0),
198 Vec2::new(2.0, 2.0), Vec2::new(0.0, 2.0),
199 ];
200 let hull = ConvexHull2D::compute(&points);
201 assert!((hull.area() - 4.0).abs() < 0.01);
202 }
203
204 #[test]
205 fn hull_3d_tetrahedron() {
206 let points = vec![
207 Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0),
208 Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0),
209 ];
210 let hull = ConvexHull::compute(&points);
211 assert_eq!(hull.face_count(), 4);
212 assert_eq!(hull.vertex_count(), 4);
213 }
214
215 #[test]
216 fn hull_3d_with_interior() {
217 let mut points = vec![
218 Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, -1.0, -1.0),
219 Vec3::new(-1.0, 1.0, -1.0), Vec3::new(1.0, 1.0, -1.0),
220 Vec3::new(-1.0, -1.0, 1.0), Vec3::new(1.0, -1.0, 1.0),
221 Vec3::new(-1.0, 1.0, 1.0), Vec3::new(1.0, 1.0, 1.0),
222 Vec3::ZERO, ];
224 let hull = ConvexHull::compute(&points);
225 assert_eq!(hull.vertex_count(), 8); }
227}