1use std;
4use std::collections::{BTreeSet, VecDeque};
5use approx;
6
7use crate::*;
8use super::*;
9
10use geometry::mesh::VertexEdgeTriangleMesh;
11use geometry::mesh::edge_triangle::{self, 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 Self::from_points_with_mesh (points).map (|x| x.0)
133 }
134
135 pub fn from_points_with_mesh (points : &[Point3 <S>])
136 -> Option <(Self, VertexEdgeTriangleMesh)>
137 where S : Real + approx::RelativeEq {
138 let point_hull = |points : Vec <Point3 <S>>| {
142 debug_assert_eq!(points.len(), 1);
143 ( Hull3 { points },
144 VertexEdgeTriangleMesh::with_vertex (edge_triangle::Vertex::new (0))
145 )
146 };
147 let line_hull = |points : Vec <Point3 <S>>| {
148 debug_assert_eq!(points.len(), 2);
149 ( Hull3 { points },
150 VertexEdgeTriangleMesh::with_edge (edge_triangle::Edge::new (0, 1))
151 )
152 };
153 match points.len() {
154 0 => return None,
155 n if n < 3 => {
156 let mut points = points.to_vec();
157 points.dedup();
158 match points.len() {
159 1 => return Some (point_hull (points)),
160 2 => return Some (line_hull (points)),
161 _ => unreachable!()
162 }
163 }
164 _ => {}
165 }
166 let get_point = |i : u32| points[i as usize];
167 let collect_points = |indices : &[u32]|
168 indices.iter().copied().map (get_point).collect();
169 let sorted = {
170 #[expect(clippy::cast_possible_truncation)]
171 let mut sorted = (0..points.len() as u32).collect::<Vec<_>>();
172 sorted.sort_by (|a, b|
173 Point3::partial_cmp (&get_point (*a), &get_point (*b)).unwrap());
174 sorted.dedup_by_key (|i| get_point(*i));
175 sorted
176 };
177 let mut hull = Vec::with_capacity (sorted.len());
178 hull.push (sorted[0]); let mut current = 1;
180 let mut dimension = 0;
181 for i in &sorted[current..] {
183 if get_point (hull[0]) != get_point (*i) {
184 dimension = 1;
185 break
186 }
187 current += 1;
188 }
189 debug_assert_eq!(hull.len(), 1);
190 if dimension == 0 {
191 let points = collect_points (hull.as_slice());
192 return Some (point_hull (points))
193 }
194 hull.push (sorted[current]); current += 1;
197 for i in &sorted[current..] {
198 if !colinear_3d (&get_point (hull[0]), &get_point (hull[1]), &get_point (*i)) {
199 dimension = 2;
200 break
201 }
202 hull.push (sorted[current]);
203 current += 1;
204 }
205 if hull.len() > 2 {
206 hull.sort_by (|a, b|
208 Point3::partial_cmp (&get_point (*a), &get_point (*b)).unwrap());
209 hull.drain (1..hull.len()-1);
210 }
211 debug_assert_eq!(hull.len(), 2);
212 if dimension == 1 {
213 let points = collect_points (hull.as_slice());
214 return Some (line_hull (points))
215 }
216 hull.push (sorted[current]); current += 1;
219 while current < sorted.len() {
220 if !coplanar_3d (
221 &get_point (hull[0]), &get_point (hull[1]), &get_point (hull[2]),
222 &get_point (sorted[current])
223 ) {
224 dimension = 3;
225 break
226 }
227 hull.push (sorted[current]);
228 current += 1;
229 }
230 if hull.len() > 3 {
231 let v0 = get_point (hull[0]);
233 let v1 = get_point (hull[1]);
234 let v2 = get_point (hull[2]);
235 let diff1 = v1 - v0;
236 let diff2 = v2 - v0;
237 let mut normal = diff1.cross (diff2);
238 let signs = normal.sigvec();
239 let c;
240 debug_assert_eq!(signs.len(), 3);
241 normal = normal.map (|s| s.abs());
242 #[expect(clippy::collapsible_else_if)]
243 if normal.x > normal.y {
244 if normal.x > normal.z {
245 if signs[0] > S::zero() {
246 c = [1, 2];
247 } else {
248 c = [2, 1];
249 }
250 } else {
251 if signs[2] > S::zero() {
252 c = [0, 1];
253 } else {
254 c = [1, 0];
255 }
256 }
257 } else {
258 if normal.y > normal.z {
259 if signs[1] > S::zero() {
260 c = [2, 0];
261 } else {
262 c = [0, 2];
263 }
264 } else {
265 if signs[2] > S::zero() {
266 c = [0, 1];
267 } else {
268 c = [1, 0];
269 }
270 }
271 }
272 let projections = hull.iter().copied()
273 .map (|i| point2 (get_point (i).0[c[0]], get_point (i).0[c[1]]))
274 .collect::<Vec <_>>();
275 let hull2_indices = Hull2::from_points_indices (projections.as_slice()).unwrap();
276 hull = hull2_indices.iter().map (|i| hull[*i as usize]).collect::<Vec <_>>();
277 }
278 if dimension == 2 {
279 let points = collect_points (hull.as_slice());
280 debug_assert!(points.len() >= 3);
284 let mut mesh = VertexEdgeTriangleMesh::default();
285 #[expect(clippy::cast_possible_truncation)]
286 for i in 1u32..points.len() as u32 - 1 {
287 mesh.insert (0, i, i+1);
288 }
289 return Some ((Hull3 { points }, mesh))
290 }
291 let plane_side = |a, b, c, d| {
293 let [s0, s1, s2, s3] = [a, b, c, d].map (get_point);
294 let diff1 = s1 - s0;
295 let diff2 = s2 - s0;
296 let diff3 = s3 - s0;
297 diff1.dot (diff2.cross (diff3)).signum_or_zero()
298 };
299 let sign = plane_side (hull[0], hull[1], hull[2], sorted[current]);
300 let mut mesh = VertexEdgeTriangleMesh::default();
301 let mut h0 = hull[0];
302 let mut h1;
303 if sign > S::zero() {
304 for i in 1..hull.len()-1 {
305 h1 = hull[i];
306 let h2 = hull[i+1];
307 let inserted = mesh.insert (h0, h2, h1);
308 debug_assert!(inserted.is_some());
309 }
310 h0 = sorted[current];
311 let mut i1 = hull.len() - 1;
312 let mut i2 = 0;
313 while i2 < hull.len() {
314 h1 = hull[i1];
315 let h2 = hull[i2];
316 let inserted = mesh.insert (h0, h1, h2);
317 debug_assert!(inserted.is_some());
318 i1 = i2;
320 i2 += 1;
321 }
322 } else {
323 for i in 1..hull.len()-1 {
324 h1 = hull[i];
325 let h2 = hull[i+1];
326 let inserted = mesh.insert (h0, h1, h2);
327 debug_assert!(inserted.is_some());
328 }
329 h0 = sorted[current];
330 let mut i1 = hull.len() - 1;
331 let mut i2 = 0;
332 while i2 < hull.len() {
333 h1 = hull[i1];
334 let h2 = hull[i2];
335 let inserted = mesh.insert (h0, h2, h1);
336 debug_assert!(inserted.is_some());
337 i1 = i2;
339 i2 += 1;
340 }
341 }
342 let mut terminator : Vec <[u32; 2]> = Vec::new();
343 current += 1;
344 while current < sorted.len() {
345 let vertex = mesh.get_vertex (h0).unwrap();
346 h1 = sorted[current];
347 let mut visible = VecDeque::<TriangleKey>::new();
348 let mut visited = BTreeSet::<TriangleKey>::new();
349 for triangle_key in vertex.adjacent_triangles().iter() {
350 let triangle = mesh.get_triangle (triangle_key).unwrap();
351 let sign = plane_side (
352 triangle.vertices()[0], triangle.vertices()[1], triangle.vertices()[2], h1);
353 if sign > S::zero() {
354 visible.push_back (*triangle_key);
355 visited.insert (*triangle_key);
356 break
357 }
358 }
359 debug_assert!(visible.len() > 0);
360 debug_assert!(terminator.is_empty());
361 while visible.len() > 0 {
362 let triangle_key = visible.pop_front().unwrap();
363 let triangle = mesh.get_triangle (&triangle_key).copied().unwrap();
364 for (i, adjacent_key) in triangle.adjacent_triangles().iter().enumerate() {
365 if let Some (key) = adjacent_key {
366 let adjacent = mesh.get_triangle (key).unwrap();
367 if plane_side (
368 adjacent.vertices()[0], adjacent.vertices()[1], adjacent.vertices()[2], h1
369 ) <= S::zero() {
370 terminator.push (
371 [triangle.vertices()[i], triangle.vertices()[(i + 1) % 3]]);
372 } else if visited.insert (*key) {
373 visible.push_back (*key);
374 }
375 }
376 }
377 visited.remove (&triangle_key);
378 let removed = mesh.remove (
379 triangle.vertices()[0], triangle.vertices()[1], triangle.vertices()[2]);
380 debug_assert!(removed);
381 }
382 #[expect(clippy::iter_with_drain)]
384 for edge in terminator.drain(..) {
385 let inserted = mesh.insert (edge[0], edge[1], h1);
386 debug_assert!(inserted.is_some());
387 }
388 h0 = h1;
389 current += 1;
390 }
391 let points = mesh.vertices().keys().copied().map (get_point).collect();
392 Some ((Hull3 { points }, mesh))
393 }
394
395 pub fn points (&self) -> &[Point3 <S>] {
396 &self.points
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn hull2() {
406 use crate::*;
407 let points : Vec <Point2 <f32>> = [
409 [ 1.0, 1.0],
410 [ 1.0, 1.0]
411 ].map (Point2::from).into_iter().collect();
412 let hull = Hull2::from_points (points.as_slice()).unwrap();
413 assert_eq!(hull.points(), &[[1.0, 1.0].into()]);
414 let points : Vec <Point2 <f32>> = [
416 [ 0.0, 0.0],
417 [ 1.0, 3.0],
418 [ 2.0, -3.0],
419 [-3.0, -1.0]
420 ].map (Point2::from).into_iter().collect();
421 let hull = Hull2::from_points (points.as_slice()).unwrap();
422 let points : Vec <Point2 <f32>> = [
424 [ 2.0, -3.0],
425 [ 1.0, 3.0],
426 [-3.0, -1.0]
427 ].map (Point2::from).into_iter().collect();
428 assert_eq!(hull.points(), points);
429 let points : Vec <Point2 <f32>> = [
431 [ 0.0, 2.0],
432 [-2.0, -2.0],
433 [ 0.0, -2.0],
434 [ 2.0, -2.0]
435 ].map (Point2::from).into_iter().collect();
436 let hull = Hull2::from_points (points.as_slice()).unwrap();
437 let points : Vec <Point2 <f32>> = [
439 [-2.0, -2.0],
440 [ 2.0, -2.0],
441 [ 0.0, 2.0]
442 ].map (Point2::from).into_iter().collect();
443 assert_eq!(hull.points(), points);
444 let points : Vec <Point2 <f32>> = [
446 [ 0.0, 6.0],
448 [ 1.0, 5.0],
449 [ 2.0, 4.0],
450 [ 3.0, 3.0],
451 [ 3.0, 1.0],
452 [ 3.0, -1.0],
453 [ 3.0, -3.0],
454 [ 1.0, -3.0],
455 [-1.0, -3.0],
456 [-3.0, -3.0],
457 [-3.0, -1.0],
458 [-3.0, 1.0],
459 [-3.0, 3.0],
460 [-2.0, 4.0],
461 [-1.0, 5.0],
462 [-1.0, 2.0],
464 [ 2.0, -1.0],
465 [ 0.0, 3.0],
466 [-2.0, -2.0]
467 ].map (Point2::from).into_iter().collect();
468 let hull = Hull2::from_points (points.as_slice()).unwrap();
469 let points : Vec <Point2 <f32>> = [
471 [-3.0, -3.0],
472 [ 3.0, -3.0],
473 [ 3.0, 3.0],
474 [ 0.0, 6.0],
475 [-3.0, 3.0]
476 ].map (Point2::from).into_iter().collect();
477 assert_eq!(hull.points(), points);
478 }
479
480 #[test]
481 fn hull3() {
482 let points = [
484 [-1.0, -4.0, -1.0],
485 [-1.0, -4.0, -1.0]
486 ].map (Point3::<f32>::from);
487 let hull = Hull3::from_points (points.as_slice()).unwrap();
488 assert_eq!(hull.points(), &[
489 [-1.0, -4.0, -1.0]
490 ].map (Into::into));
491 let points = [
493 [-1.0, -4.0, -1.0],
494 [ 0.0, 0.0, 0.0],
495 [ 1.0, 4.0, 1.0]
496 ].map (Point3::<f32>::from);
497 let hull = Hull3::from_points (points.as_slice()).unwrap();
498 assert_eq!(hull.points(), &[
499 [-1.0, -4.0, -1.0],
500 [ 1.0, 4.0, 1.0]
501 ].map (Into::into));
502 let points = [
504 [-1.0, -4.0, 0.0],
505 [ 2.0, 2.0, 0.0],
506 [-1.0, -1.0, 0.0],
507 [-4.0, -1.0, 0.0],
508 [ 0.0, 2.0, 0.0]
509 ].map (Point3::<f32>::from);
510 let hull = Hull3::from_points (points.as_slice()).unwrap();
511 assert_eq!(hull.points(), &[
512 [-1.0, -4.0, 0.0],
513 [ 2.0, 2.0, 0.0],
514 [ 0.0, 2.0, 0.0],
515 [-4.0, -1.0, 0.0]
516 ].map (Into::into));
517 let points = [
519 [-1.0, -4.0, -1.0],
520 [ 2.0, 2.0, 2.0],
521 [-4.0, -1.0, 2.0],
522 [ 0.0, 2.0, -3.0]
523 ].map (Point3::<f32>::from);
524 let hull = Hull3::from_points (points.as_slice()).unwrap();
525 assert_eq!(hull.points(), &[
526 [-1.0, -4.0, -1.0],
527 [ 2.0, 2.0, 2.0],
528 [-4.0, -1.0, 2.0],
529 [ 0.0, 2.0, -3.0]
530 ].map (Into::into));
531 let points = [
532 [ 1.0, -1.0, -1.0],
533 [ 1.0, -1.0, 1.0],
534 [ 1.0, 1.0, -1.0],
535 [ 1.0, 1.0, 1.0],
536 [-1.0, -1.0, -1.0],
537 [-1.0, -1.0, 1.0],
538 [-1.0, 1.0, -1.0],
539 [-1.0, 1.0, 1.0]
540 ].map (Point3::<f32>::from);
541 let hull = Hull3::from_points (points.as_slice()).unwrap();
542 assert_eq!(hull.points(), points.as_slice());
543 let points = [
545 [-1.0, -4.0, -1.0],
546 [ 2.0, 2.0, 2.0],
547 [-4.0, -1.0, 2.0],
548 [ 0.0, 2.0, -3.0],
549 [ 0.1, 0.2, 0.3],
550 [-0.1, -0.2, -0.3],
551 [-0.1, 0.2, 0.3]
552 ].map (Point3::<f32>::from);
553 let hull = Hull3::from_points (points.as_slice()).unwrap();
554 assert_eq!(hull.points(), &[
555 [-1.0, -4.0, -1.0],
556 [ 2.0, 2.0, 2.0],
557 [-4.0, -1.0, 2.0],
558 [ 0.0, 2.0, -3.0]
559 ].map (Into::into));
560 }
561}