1use std;
4use std::collections::{BTreeSet, VecDeque};
5use approx;
6
7use crate::*;
8use super::*;
9
10use geometry::mesh::VertexEdgeTriangleMesh;
11use geometry::mesh::edge_triangle::TriangleKey;
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct Hull2 <S> {
16 points : Vec <Point2 <S>>
19}
20
21#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct Hull3 <S> {
24 points : Vec <Point3 <S>>
25}
26
27impl <S> Hull2 <S> {
28 pub fn from_points (points : &[Point2 <S>]) -> Option <Self> where S : Real {
38 Self::from_points_indices (points).map (|indices|{
39 let points = indices.iter().map (|i| points[*i as usize]).collect();
40 Hull2 { points }
41 })
42 }
43
44 pub fn points (&self) -> &[Point2 <S>] {
46 &self.points
47 }
48
49 #[expect(clippy::missing_asserts_for_indexing)]
50 fn from_points_indices (points : &[Point2 <S>]) -> Option <Vec <u32>> where S : Real {
51 use std::cmp::Ordering;
54 match points.len() {
55 0 => return None,
56 1 => return Some (vec![0]),
57 2 => {
58 let v = if points[0] == points[1] {
59 vec![0]
60 } else {
61 vec![0, 1]
62 };
63 return Some (v)
64 }
65 _ => {} }
67 let mut indexed_points = points.iter().copied().enumerate()
68 .collect::<Vec <(usize, Point2 <S>)>>();
69 let start_index = indexed_points.iter().min_by (|a, b|{
71 let cmp = a.1.0.y.partial_cmp (&b.1.0.y).unwrap();
72 if cmp == Ordering::Equal {
73 a.1.0.x.partial_cmp (&b.1.0.x).unwrap()
74 } else {
75 cmp
76 }
77 }).unwrap().0;
78 let start_point = indexed_points[start_index].1;
79 indexed_points.sort_by (|a, b| {
81 if a.0 == start_index {
82 return Ordering::Less
83 }
84 if b.0 == start_index {
85 return Ordering::Greater
86 }
87 let angle_a = (a.1.0.y - start_point.0.y)
88 .atan2 (a.1.0.x - start_point.0.x);
89 let angle_b = (b.1.0.y - start_point.0.y)
90 .atan2 (b.1.0.x - start_point.0.x);
91 let angle_cmp = angle_a.partial_cmp (&angle_b).unwrap();
92 if angle_cmp == Ordering::Equal {
93 let dist_a = (a.1.0.x - start_point.0.x).powi (2) +
95 (a.1.0.y - start_point.0.y).powi (2);
96 let dist_b = (b.1.0.x - start_point.0.x).powi (2) +
97 (b.1.0.y - start_point.0.y).powi (2);
98 dist_a.partial_cmp (&dist_b).unwrap()
99 } else {
100 angle_cmp
101 }
102 });
103 let mut stack : Vec <u32> = Vec::new();
105 for (point_index, point) in indexed_points {
106 while stack.len() >= 2 {
107 let top = stack[stack.len() - 1];
108 let second = stack[stack.len() -2];
109 let p1 = points[second as usize];
110 let p2 = points[top as usize];
111 let p3 = point;
112 let det = Matrix2 {
113 cols: vector2 (p2 - p1, p3 - p1)
114 }.determinant();
115 if det <= S::zero() {
116 stack.pop();
117 } else {
118 break
119 }
120 }
121 #[expect(clippy::cast_possible_truncation)]
122 stack.push (point_index as u32)
123 }
124 Some (stack)
125 }
126}
127
128impl <S> Hull3 <S> {
129 pub fn from_points (points : &[Point3 <S>]) -> Option <Self> where
130 S : Real + approx::RelativeEq
131 {
132 match points.len() {
136 0 => return None,
137 1 => return Some (Hull3 { points: points.to_vec() }),
138 2 => {
139 let mut points = points.to_vec();
140 points.dedup();
141 return Some (Hull3 { points })
142 }
143 _ => {}
144 }
145 let get_point = |i : u32| points[i as usize];
146 let collect_points = |indices : &[u32]|
147 indices.iter().copied().map (get_point).collect();
148 let sorted = {
149 #[expect(clippy::cast_possible_truncation)]
150 let mut sorted = (0..points.len() as u32).collect::<Vec<_>>();
151 sorted.sort_by (|a, b|
152 Point3::partial_cmp (&get_point (*a), &get_point (*b)).unwrap());
153 sorted.dedup_by_key (|i| get_point(*i));
154 sorted
155 };
156 let mut hull = Vec::with_capacity (sorted.len());
157 hull.push (sorted[0]); let mut current = 1;
159 let mut dimension = 0;
160 for i in &sorted[current..] {
162 if get_point (hull[0]) != get_point (*i) {
163 dimension = 1;
164 break
165 }
166 current += 1;
167 }
168 debug_assert_eq!(hull.len(), 1);
169 if dimension == 0 {
170 let points = collect_points (hull.as_slice());
171 return Some (Hull3 { points })
172 }
173 hull.push (sorted[current]); current += 1;
176 for i in &sorted[current..] {
177 if !colinear_3d (&get_point (hull[0]), &get_point (hull[1]), &get_point (*i)) {
178 dimension = 2;
179 break
180 }
181 hull.push (sorted[current]);
182 current += 1;
183 }
184 if hull.len() > 2 {
185 hull.sort_by (|a, b|
187 Point3::partial_cmp (&get_point (*a), &get_point (*b)).unwrap());
188 hull.drain (1..hull.len()-1);
189 }
190 debug_assert_eq!(hull.len(), 2);
191 if dimension == 1 {
192 let points = collect_points (hull.as_slice());
193 return Some (Hull3 { points })
194 }
195 hull.push (sorted[current]); current += 1;
198 while current < sorted.len() {
199 if !coplanar_3d (
200 &get_point (hull[0]), &get_point (hull[1]), &get_point (hull[2]),
201 &get_point (sorted[current])
202 ) {
203 dimension = 3;
204 break
205 }
206 hull.push (sorted[current]);
207 current += 1;
208 }
209 if hull.len() > 3 {
210 let v0 = get_point (hull[0]);
212 let v1 = get_point (hull[1]);
213 let v2 = get_point (hull[2]);
214 let diff1 = v1 - v0;
215 let diff2 = v2 - v0;
216 let normal = diff1.cross (diff2);
217 let signs = normal.sigvec();
218 let c;
219 debug_assert_eq!(signs.len(), 3);
220 #[expect(clippy::collapsible_else_if)]
221 if normal.x > normal.y {
222 if normal.x > normal.z {
223 if signs[0] > S::zero() {
224 c = [1, 2];
225 } else {
226 c = [2, 1];
227 }
228 } else {
229 if signs[2] > S::zero() {
230 c = [0, 1];
231 } else {
232 c = [1, 0];
233 }
234 }
235 } else {
236 if normal.y > normal.z {
237 if signs[1] > S::zero() {
238 c = [2, 0];
239 } else {
240 c = [0, 2];
241 }
242 } else {
243 if signs[2] > S::zero() {
244 c = [0, 1];
245 } else {
246 c = [1, 0];
247 }
248 }
249 }
250 let projections = hull.iter().copied()
251 .map (|i| point2 (get_point (i).0[c[0]], get_point (i).0[c[1]]))
252 .collect::<Vec <_>>();
253 let hull2_indices = Hull2::from_points_indices (projections.as_slice()).unwrap();
254 hull = hull2_indices.iter().map (|i| hull[*i as usize]).collect::<Vec <_>>();
255 }
256 if dimension == 2 {
257 let points = collect_points (hull.as_slice());
258 return Some (Hull3 { points })
259 }
260 let plane_side = |a, b, c, d| {
262 let [s0, s1, s2, s3] = [a, b, c, d].map (get_point);
263 let diff1 = s1 - s0;
264 let diff2 = s2 - s0;
265 let diff3 = s3 - s0;
266 diff1.dot (diff2.cross (diff3)).signum_or_zero()
267 };
268 let sign = plane_side (hull[0], hull[1], hull[2], sorted[current]);
269 let mut mesh = VertexEdgeTriangleMesh::default();
270 let mut h0 = hull[0];
271 let mut h1;
272 if sign > S::zero() {
273 for i in 1..hull.len()-1 {
274 h1 = hull[i];
275 let h2 = hull[i+1];
276 let inserted = mesh.insert (h0, h2, h1);
277 debug_assert!(inserted.is_some());
278 }
279 h0 = sorted[current];
280 let mut i1 = hull.len() - 1;
281 let mut i2 = 0;
282 while i2 < hull.len() {
283 h1 = hull[i1];
284 let h2 = hull[i2];
285 let inserted = mesh.insert (h0, h1, h2);
286 debug_assert!(inserted.is_some());
287 i1 = i2;
289 i2 += 1;
290 }
291 } else {
292 for i in 1..hull.len()-1 {
293 h1 = hull[i];
294 let h2 = hull[i+1];
295 let inserted = mesh.insert (h0, h1, h2);
296 debug_assert!(inserted.is_some());
297 }
298 h0 = sorted[current];
299 let mut i1 = hull.len() - 1;
300 let mut i2 = 0;
301 while i2 < hull.len() {
302 h1 = hull[i1];
303 let h2 = hull[i2];
304 let inserted = mesh.insert (h0, h2, h1);
305 debug_assert!(inserted.is_some());
306 i1 = i2;
308 i2 += 1;
309 }
310 }
311 let mut terminator : Vec <[u32; 2]> = Vec::new();
312 current += 1;
313 while current < sorted.len() {
314 let vertex = mesh.get_vertex (h0).unwrap();
315 h1 = sorted[current];
316 let mut visible = VecDeque::<TriangleKey>::new();
317 let mut visited = BTreeSet::<TriangleKey>::new();
318 for triangle_key in vertex.adjacent_triangles().iter() {
319 let triangle = mesh.get_triangle (triangle_key).unwrap();
320 let sign = plane_side (
321 triangle.vertices()[0], triangle.vertices()[1], triangle.vertices()[2], h1);
322 if sign > S::zero() {
323 visible.push_back (*triangle_key);
324 visited.insert (*triangle_key);
325 break
326 }
327 }
328 debug_assert!(visible.len() > 0);
329 debug_assert!(terminator.is_empty());
330 while visible.len() > 0 {
331 let triangle_key = visible.pop_front().unwrap();
332 let triangle = mesh.get_triangle (&triangle_key).copied().unwrap();
333 for (i, adjacent_key) in triangle.adjacent_triangles().iter().enumerate() {
334 if let Some (key) = adjacent_key {
335 let adjacent = mesh.get_triangle (key).unwrap();
336 if plane_side (
337 adjacent.vertices()[0], adjacent.vertices()[1], adjacent.vertices()[2], h1
338 ) <= S::zero() {
339 terminator.push (
340 [triangle.vertices()[i], triangle.vertices()[(i + 1) % 3]]);
341 } else if visited.insert (*key) {
342 visible.push_back (*key);
343 }
344 }
345 }
346 visited.remove (&triangle_key);
347 let removed = mesh.remove (
348 triangle.vertices()[0], triangle.vertices()[1], triangle.vertices()[2]);
349 debug_assert!(removed);
350 }
351 #[expect(clippy::iter_with_drain)]
353 for edge in terminator.drain(..) {
354 let inserted = mesh.insert (edge[0], edge[1], h1);
355 debug_assert!(inserted.is_some());
356 }
357 h0 = h1;
358 current += 1;
359 }
360 let points = mesh.vertices().keys().copied().map (get_point).collect();
361 Some (Hull3 { points })
362 }
363
364 pub fn points (&self) -> &[Point3 <S>] {
365 &self.points
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn hull2() {
375 use crate::*;
376 let points : Vec <Point2 <f32>> = [
378 [ 1.0, 1.0],
379 [ 1.0, 1.0]
380 ].map (Point2::from).into_iter().collect();
381 let hull = Hull2::from_points (points.as_slice()).unwrap();
382 assert_eq!(hull.points(), &[[1.0, 1.0].into()]);
383 let points : Vec <Point2 <f32>> = [
385 [ 0.0, 0.0],
386 [ 1.0, 3.0],
387 [ 2.0, -3.0],
388 [-3.0, -1.0]
389 ].map (Point2::from).into_iter().collect();
390 let hull = Hull2::from_points (points.as_slice()).unwrap();
391 let points : Vec <Point2 <f32>> = [
393 [ 2.0, -3.0],
394 [ 1.0, 3.0],
395 [-3.0, -1.0]
396 ].map (Point2::from).into_iter().collect();
397 assert_eq!(hull.points(), points);
398 let points : Vec <Point2 <f32>> = [
400 [ 0.0, 2.0],
401 [-2.0, -2.0],
402 [ 0.0, -2.0],
403 [ 2.0, -2.0]
404 ].map (Point2::from).into_iter().collect();
405 let hull = Hull2::from_points (points.as_slice()).unwrap();
406 let points : Vec <Point2 <f32>> = [
408 [-2.0, -2.0],
409 [ 2.0, -2.0],
410 [ 0.0, 2.0]
411 ].map (Point2::from).into_iter().collect();
412 assert_eq!(hull.points(), points);
413 let points : Vec <Point2 <f32>> = [
415 [ 0.0, 6.0],
417 [ 1.0, 5.0],
418 [ 2.0, 4.0],
419 [ 3.0, 3.0],
420 [ 3.0, 1.0],
421 [ 3.0, -1.0],
422 [ 3.0, -3.0],
423 [ 1.0, -3.0],
424 [-1.0, -3.0],
425 [-3.0, -3.0],
426 [-3.0, -1.0],
427 [-3.0, 1.0],
428 [-3.0, 3.0],
429 [-2.0, 4.0],
430 [-1.0, 5.0],
431 [-1.0, 2.0],
433 [ 2.0, -1.0],
434 [ 0.0, 3.0],
435 [-2.0, -2.0]
436 ].map (Point2::from).into_iter().collect();
437 let hull = Hull2::from_points (points.as_slice()).unwrap();
438 let points : Vec <Point2 <f32>> = [
440 [-3.0, -3.0],
441 [ 3.0, -3.0],
442 [ 3.0, 3.0],
443 [ 0.0, 6.0],
444 [-3.0, 3.0]
445 ].map (Point2::from).into_iter().collect();
446 assert_eq!(hull.points(), points);
447 }
448
449 #[test]
450 fn hull3() {
451 let points : Vec <Point3 <f32>> = [
453 [-1.0, -4.0, -1.0],
454 [-1.0, -4.0, -1.0]
455 ].map (Point3::from).into_iter().collect();
456 let hull = Hull3::from_points (points.as_slice()).unwrap();
457 assert_eq!(hull.points(), &[
458 [-1.0, -4.0, -1.0]
459 ].map (Into::into));
460 let points : Vec <Point3 <f32>> = [
462 [-1.0, -4.0, -1.0],
463 [ 0.0, 0.0, 0.0],
464 [ 1.0, 4.0, 1.0]
465 ].map (Point3::from).into_iter().collect();
466 let hull = Hull3::from_points (points.as_slice()).unwrap();
467 assert_eq!(hull.points(), &[
468 [-1.0, -4.0, -1.0],
469 [ 1.0, 4.0, 1.0]
470 ].map (Into::into));
471 let points : Vec <Point3 <f32>> = [
473 [-1.0, -4.0, 0.0],
474 [ 2.0, 2.0, 0.0],
475 [-1.0, -1.0, 0.0],
476 [-4.0, -1.0, 0.0],
477 [ 0.0, 2.0, 0.0]
478 ].map (Point3::from).into_iter().collect();
479 let hull = Hull3::from_points (points.as_slice()).unwrap();
480 assert_eq!(hull.points(), &[
481 [-1.0, -4.0, 0.0],
482 [ 2.0, 2.0, 0.0],
483 [ 0.0, 2.0, 0.0],
484 [-4.0, -1.0, 0.0]
485 ].map (Into::into));
486 let points : Vec <Point3 <f32>> = [
488 [-1.0, -4.0, -1.0],
489 [ 2.0, 2.0, 2.0],
490 [-4.0, -1.0, 2.0],
491 [ 0.0, 2.0, -3.0]
492 ].map (Point3::from).into_iter().collect();
493 let hull = Hull3::from_points (points.as_slice()).unwrap();
494 assert_eq!(hull.points(), &[
495 [-1.0, -4.0, -1.0],
496 [ 2.0, 2.0, 2.0],
497 [-4.0, -1.0, 2.0],
498 [ 0.0, 2.0, -3.0]
499 ].map (Into::into));
500 let points : Vec <Point3 <f32>> = [
502 [-1.0, -4.0, -1.0],
503 [ 2.0, 2.0, 2.0],
504 [-4.0, -1.0, 2.0],
505 [ 0.0, 2.0, -3.0],
506 [ 0.1, 0.2, 0.3],
507 [-0.1, -0.2, -0.3],
508 [-0.1, 0.2, 0.3]
509 ].map (Point3::from).into_iter().collect();
510 let hull = Hull3::from_points (points.as_slice()).unwrap();
511 assert_eq!(hull.points(), &[
512 [-1.0, -4.0, -1.0],
513 [ 2.0, 2.0, 2.0],
514 [-4.0, -1.0, 2.0],
515 [ 0.0, 2.0, -3.0]
516 ].map (Into::into));
517 }
518}