math_utils/geometry/primitive/
hull.rs

1//! Convex hulls
2
3use 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/// 2D convex hull
14#[derive(Clone, Debug, PartialEq)]
15pub struct Hull2 <S> {
16  /// Set of unique points sorted in counter-clockwise order for efficient
17  /// minimum bounding box algorithm
18  points : Vec <Point2 <S>>
19}
20
21/// 3D convex hull
22#[derive(Clone, Debug, PartialEq)]
23pub struct Hull3 <S> {
24  points : Vec <Point3 <S>>
25}
26
27impl <S> Hull2 <S> {
28  /// Create a new 2D convex hull from a bag of points.
29  ///
30  /// Uses Graham scan algorithm.
31  ///
32  /// Input must contain at least 1 point:
33  /// ```
34  /// # use math_utils::geometry::Hull2;
35  /// assert!(Hull2::<f32>::from_points (&[]).is_none());
36  /// ```
37  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  /// Points sorted in counter-clockwise order
45  pub fn points (&self) -> &[Point2 <S>] {
46    &self.points
47  }
48
49  fn from_points_indices (points : &[Point2 <S>]) -> Option <Vec <u32>> where S : Real {
50    // code adapted from scirs2-spatial:
51    // <https://github.com/cool-japan/scirs/blob/a176b462aca55e73bd4b25eea83aad10a9f4a2b0/scirs2-spatial/src/convex_hull.rs>
52    use std::cmp::Ordering;
53    match points.len() {
54      0 => return None,
55      1 => return Some (vec![0]),
56      2 => {
57        let v = if points[0] == points[1] {
58          vec![0]
59        } else {
60          vec![0, 1]
61        };
62        return Some (v)
63      }
64      _ => {} // continue
65    }
66    let mut indexed_points = points.iter().cloned().enumerate()
67      .collect::<Vec <(usize, Point2 <S>)>>();
68    // find bottom-most (lowest y-coordinate), then left-most x
69    let start_index = indexed_points.iter().min_by (|a, b|{
70      let cmp = a.1.0.y.partial_cmp (&b.1.0.y).unwrap();
71      if cmp == Ordering::Equal {
72        a.1.0.x.partial_cmp (&b.1.0.x).unwrap()
73      } else {
74        cmp
75      }
76    }).unwrap().0;
77    let start_point = indexed_points[start_index].1;
78    // sort points by polar angle with respect to start point
79    indexed_points.sort_by (|a, b| {
80      if a.0 == start_index {
81        return Ordering::Less
82      }
83      if b.0 == start_index {
84        return Ordering::Greater
85      }
86      let angle_a = (a.1.0.y - start_point.0.y)
87        .atan2 (a.1.0.x - start_point.0.x);
88      let angle_b = (b.1.0.y - start_point.0.y)
89        .atan2 (b.1.0.x - start_point.0.x);
90      let angle_cmp = angle_a.partial_cmp (&angle_b).unwrap();
91      if angle_cmp == Ordering::Equal {
92        // if angles are equal sort by distance
93        let dist_a = (a.1.0.x - start_point.0.x).powi (2) +
94          (a.1.0.y - start_point.0.y).powi (2);
95        let dist_b = (b.1.0.x - start_point.0.x).powi (2) +
96          (b.1.0.y - start_point.0.y).powi (2);
97        dist_a.partial_cmp (&dist_b).unwrap()
98      } else {
99        angle_cmp
100      }
101    });
102    // graham scan
103    let mut stack : Vec <u32> = Vec::new();
104    for (point_index, point) in indexed_points {
105      while stack.len() >= 2 {
106        let top = stack[stack.len() - 1];
107        let second = stack[stack.len() -2];
108        let p1 = points[second as usize];
109        let p2 = points[top as usize];
110        let p3 = point;
111        let det = Matrix2 {
112          cols: vector2 (p2 - p1, p3 - p1)
113        }.determinant();
114        if det <= S::zero() {
115          stack.pop();
116        } else {
117          break
118        }
119      }
120      stack.push (point_index as u32)
121    }
122    Some (stack)
123  }
124}
125
126impl <S> Hull3 <S> {
127  pub fn from_points (points : &[Point3 <S>]) -> Option <Self> where
128    S : Real + approx::RelativeEq
129  {
130    // code adapted from GeometricTools:
131    // <https://github.com/davideberly/GeometricTools/blob/f78dd0b65bc3a0a4723586de6991dd2c339b08ad/GTE/Mathematics/ConvexHull3.h>
132    // TODO: multi-threaded
133    match points.len() {
134      0 => return None,
135      1 => return Some (Hull3 { points: points.to_vec() }),
136      2 => {
137        let mut points = points.to_vec();
138        points.dedup();
139        return Some (Hull3 { points })
140      }
141      _ => {}
142    }
143    let get_point = |i : u32| points[i as usize];
144    let collect_points = |indices : &[u32]|
145      indices.iter().cloned().map (get_point).collect();
146    let sorted = {
147      let mut sorted = (0..points.len() as u32).collect::<Vec<_>>();
148      sorted.sort_by (|a, b|
149        Point3::partial_cmp (&get_point (*a), &get_point (*b)).unwrap());
150      sorted.dedup_by_key (|i| get_point(*i));
151      sorted
152    };
153    let mut hull = Vec::with_capacity (sorted.len());
154    hull.push (sorted[0]);  // hull[0]
155    let mut current = 1;
156    let mut dimension = 0;
157    // point hull
158    for i in &sorted[current..] {
159      if get_point (hull[0]) != get_point (*i) {
160        dimension = 1;
161        break
162      }
163      current += 1;
164    }
165    debug_assert_eq!(hull.len(), 1);
166    if dimension == 0 {
167      let points = collect_points (hull.as_slice());
168      return Some (Hull3 { points })
169    }
170    // linear hull
171    hull.push (sorted[current]);  // hull[1]
172    current += 1;
173    for i in &sorted[current..] {
174      if !colinear_3d (&get_point (hull[0]), &get_point (hull[1]), &get_point (*i)) {
175        dimension = 2;
176        break
177      }
178      hull.push (sorted[current]);
179      current += 1;
180    }
181    if hull.len() > 2 {
182      // keep endpoints
183      hull.sort_by (|a, b|
184        Point3::partial_cmp (&get_point (*a), &get_point (*b)).unwrap());
185      hull.drain (1..hull.len()-1);
186    }
187    debug_assert_eq!(hull.len(), 2);
188    if dimension == 1 {
189      let points = collect_points (hull.as_slice());
190      return Some (Hull3 { points })
191    }
192    // planar hull
193    hull.push (sorted[current]);  // hull[2]
194    current += 1;
195    while current < sorted.len() {
196      if !coplanar_3d (
197        &get_point (hull[0]), &get_point (hull[1]), &get_point (hull[2]),
198        &get_point (sorted[current])
199      ) {
200        dimension = 3;
201        break
202      }
203      hull.push (sorted[current]);
204      current += 1;
205    }
206    if hull.len() > 3 {
207      // compute planar convex hull
208      let v0     = get_point (hull[0]);
209      let v1     = get_point (hull[1]);
210      let v2     = get_point (hull[2]);
211      let diff1  = v1 - v0;
212      let diff2  = v2 - v0;
213      let normal = diff1.cross (diff2);
214      let signs  = normal.sigvec();
215      let c;
216      #[allow(clippy::collapsible_else_if)]
217      if normal.x > normal.y {
218        if normal.x > normal.z {
219          if signs[0] > S::zero() {
220            c = [1, 2];
221          } else {
222            c = [2, 1];
223          }
224        } else {
225          if signs[2] > S::zero() {
226            c = [0, 1];
227          } else {
228            c = [1, 0];
229          }
230        }
231      } else {
232        if normal.y > normal.z {
233          if signs[1] > S::zero() {
234            c = [2, 0];
235          } else {
236            c = [0, 2];
237          }
238        } else {
239          if signs[2] > S::zero() {
240            c = [0, 1];
241          } else {
242            c = [1, 0];
243          }
244        }
245      }
246      let projections = hull.iter().cloned()
247        .map (|i| point2 (get_point (i).0[c[0]], get_point (i).0[c[1]]))
248        .collect::<Vec <_>>();
249      let hull2_indices = Hull2::from_points_indices (projections.as_slice()).unwrap();
250      hull = hull2_indices.iter().map (|i| hull[*i as usize]).collect::<Vec <_>>();
251    }
252    if dimension == 2 {
253      let points = collect_points (hull.as_slice());
254      return Some (Hull3 { points })
255    }
256    // 3-dimensional hull
257    let plane_side = |a, b, c, d| {
258      let [s0, s1, s2, s3] = [a, b, c, d].map (get_point);
259      let diff1 = s1 - s0;
260      let diff2 = s2 - s0;
261      let diff3 = s3 - s0;
262      diff1.dot (diff2.cross (diff3)).signum_or_zero()
263    };
264    let sign = plane_side (hull[0], hull[1], hull[2], sorted[current]);
265    let mut h0;
266    let mut h1;
267    let mut mesh = VertexEdgeTriangleMesh::default();
268    if sign > S::zero() {
269      h0 = hull[0];
270      for i in 1..hull.len()-1 {
271        h1 = hull[i];
272        let h2 = hull[i+1];
273        let _inserted = mesh.insert (h0, h2, h1);
274        debug_assert!(_inserted.is_some());
275      }
276      h0 = sorted[current];
277      let mut i1 = hull.len() - 1;
278      let mut i2 = 0;
279      while i2 < hull.len() {
280        h1 = hull[i1];
281        let h2 = hull[i2];
282        let _inserted = mesh.insert (h0, h1, h2);
283        debug_assert!(_inserted.is_some());
284        // iter
285        i1 = i2;
286        i2 += 1;
287      }
288    } else {
289      h0 = hull[0];
290      for i in 1..hull.len()-1 {
291        h1 = hull[i];
292        let h2 = hull[i+1];
293        let _inserted = mesh.insert (h0, h1, h2);
294        debug_assert!(_inserted.is_some());
295      }
296      h0 = sorted[current];
297      let mut i1 = hull.len() - 1;
298      let mut i2 = 0;
299      while i2 < hull.len() {
300        h1 = hull[i1];
301        let h2 = hull[i2];
302        let _inserted = mesh.insert (h0, h2, h1);
303        debug_assert!(_inserted.is_some());
304        // iter
305        i1 = i2;
306        i2 += 1;
307      }
308    }
309    let mut terminator : Vec <[u32; 2]> = Vec::new();
310    current += 1;
311    while current < sorted.len() {
312      let vertex = mesh.get_vertex (h0).unwrap();
313      h1 = sorted[current];
314      let mut visible = VecDeque::<TriangleKey>::new();
315      let mut visited = BTreeSet::<TriangleKey>::new();
316      for triangle_key in vertex.adjacent_triangles().iter() {
317        let triangle = mesh.get_triangle (triangle_key).unwrap();
318        let sign = plane_side (
319          triangle.vertices()[0], triangle.vertices()[1], triangle.vertices()[2], h1);
320        if sign > S::zero() {
321          visible.push_back (*triangle_key);
322          visited.insert (*triangle_key);
323          break
324        }
325      }
326      debug_assert!(visible.len() > 0);
327      debug_assert!(terminator.is_empty());
328      while visible.len() > 0 {
329        let triangle_key = visible.pop_front().unwrap();
330        let triangle = mesh.get_triangle (&triangle_key).cloned().unwrap();
331        for (i, adjacent_key) in triangle.adjacent_triangles().iter().enumerate() {
332          if let Some (key) = adjacent_key {
333            let adjacent = mesh.get_triangle (key).unwrap();
334            if plane_side (
335              adjacent.vertices()[0], adjacent.vertices()[1], adjacent.vertices()[2], h1
336            ) <= S::zero() {
337              terminator.push (
338                [triangle.vertices()[i], triangle.vertices()[(i + 1) % 3]]);
339            } else if visited.insert (*key) {
340              visible.push_back (*key);
341            }
342          }
343        }
344        visited.remove (&triangle_key);
345        let _removed = mesh.remove (
346          triangle.vertices()[0], triangle.vertices()[1], triangle.vertices()[2]);
347        debug_assert!(_removed);
348      }
349      for edge in terminator.drain (..) {
350        let _inserted = mesh.insert (edge[0], edge[1], h1);
351        debug_assert!(_inserted.is_some());
352      }
353      h0 = h1;
354      current += 1;
355    }
356    let points = mesh.vertices().keys().cloned().map (get_point).collect();
357    Some (Hull3 { points })
358  }
359
360  pub fn points (&self) -> &[Point3 <S>] {
361    &self.points
362  }
363}
364
365#[cfg(test)]
366mod tests {
367  use super::*;
368
369  #[test]
370  fn hull2() {
371    use crate::*;
372    // dedup unique points
373    let points : Vec <Point2 <f32>> = [
374      [ 1.0,  1.0],
375      [ 1.0,  1.0]
376    ].map (Point2::from).into_iter().collect();
377    let hull = Hull2::from_points (points.as_slice()).unwrap();
378    assert_eq!(hull.points(), &[[1.0, 1.0].into()]);
379    // interior point at origin is excluded
380    let points : Vec <Point2 <f32>> = [
381      [ 0.0,  0.0],
382      [ 1.0,  3.0],
383      [ 2.0, -3.0],
384      [-3.0, -1.0]
385    ].map (Point2::from).into_iter().collect();
386    let hull = Hull2::from_points (points.as_slice()).unwrap();
387    // points are in counter-clockwise order
388    let points : Vec <Point2 <f32>> = [
389      [ 2.0, -3.0],
390      [ 1.0,  3.0],
391      [-3.0, -1.0]
392    ].map (Point2::from).into_iter().collect();
393    assert_eq!(hull.points(), points);
394    // colinear point on edge is excluded
395    let points : Vec <Point2 <f32>> = [
396      [ 0.0,  2.0],
397      [-2.0, -2.0],
398      [ 0.0, -2.0],
399      [ 2.0, -2.0]
400    ].map (Point2::from).into_iter().collect();
401    let hull = Hull2::from_points (points.as_slice()).unwrap();
402    // points are in counter-clockwise order
403    let points : Vec <Point2 <f32>> = [
404      [-2.0, -2.0],
405      [ 2.0, -2.0],
406      [ 0.0,  2.0]
407    ].map (Point2::from).into_iter().collect();
408    assert_eq!(hull.points(), points);
409    // multiple edge and interior points are excluded
410    let points : Vec <Point2 <f32>> = [
411      // perimeter points
412      [ 0.0,  6.0],
413      [ 1.0,  5.0],
414      [ 2.0,  4.0],
415      [ 3.0,  3.0],
416      [ 3.0,  1.0],
417      [ 3.0, -1.0],
418      [ 3.0, -3.0],
419      [ 1.0, -3.0],
420      [-1.0, -3.0],
421      [-3.0, -3.0],
422      [-3.0, -1.0],
423      [-3.0,  1.0],
424      [-3.0,  3.0],
425      [-2.0,  4.0],
426      [-1.0,  5.0],
427      // interior points
428      [-1.0,  2.0],
429      [ 2.0, -1.0],
430      [ 0.0,  3.0],
431      [-2.0, -2.0]
432    ].map (Point2::from).into_iter().collect();
433    let hull = Hull2::from_points (points.as_slice()).unwrap();
434    // points are in counter-clockwise order
435    let points : Vec <Point2 <f32>> = [
436      [-3.0, -3.0],
437      [ 3.0, -3.0],
438      [ 3.0,  3.0],
439      [ 0.0,  6.0],
440      [-3.0,  3.0]
441    ].map (Point2::from).into_iter().collect();
442    assert_eq!(hull.points(), points);
443  }
444
445  #[test]
446  fn hull3() {
447    // dedup 0 dimensional
448    let points : Vec <Point3 <f32>> = [
449      [-1.0, -4.0, -1.0],
450      [-1.0, -4.0, -1.0]
451    ].map (Point3::from).into_iter().collect();
452    let hull = Hull3::from_points (points.as_slice()).unwrap();
453    assert_eq!(hull.points(), &[
454      [-1.0, -4.0, -1.0]
455    ].map (Into::into));
456    // 1 dimensional endpoints
457    let points : Vec <Point3 <f32>> = [
458      [-1.0, -4.0, -1.0],
459      [ 0.0,  0.0,  0.0],
460      [ 1.0,  4.0,  1.0]
461    ].map (Point3::from).into_iter().collect();
462    let hull = Hull3::from_points (points.as_slice()).unwrap();
463    assert_eq!(hull.points(), &[
464      [-1.0, -4.0, -1.0],
465      [ 1.0,  4.0,  1.0]
466    ].map (Into::into));
467    // 2 dimensional
468    let points : Vec <Point3 <f32>> = [
469      [-1.0, -4.0, 0.0],
470      [ 2.0,  2.0, 0.0],
471      [-1.0, -1.0, 0.0],
472      [-4.0, -1.0, 0.0],
473      [ 0.0,  2.0, 0.0]
474    ].map (Point3::from).into_iter().collect();
475    let hull = Hull3::from_points (points.as_slice()).unwrap();
476    assert_eq!(hull.points(), &[
477      [-1.0, -4.0, 0.0],
478      [ 2.0,  2.0, 0.0],
479      [ 0.0,  2.0, 0.0],
480      [-4.0, -1.0, 0.0]
481    ].map (Into::into));
482    // already a hull
483    let points : Vec <Point3 <f32>> = [
484      [-1.0, -4.0, -1.0],
485      [ 2.0,  2.0,  2.0],
486      [-4.0, -1.0,  2.0],
487      [ 0.0,  2.0, -3.0]
488    ].map (Point3::from).into_iter().collect();
489    let hull = Hull3::from_points (points.as_slice()).unwrap();
490    assert_eq!(hull.points(), &[
491      [-1.0, -4.0, -1.0],
492      [ 2.0,  2.0,  2.0],
493      [-4.0, -1.0,  2.0],
494      [ 0.0,  2.0, -3.0]
495    ].map (Into::into));
496    // removes interior points
497    let points : Vec <Point3 <f32>> = [
498      [-1.0, -4.0, -1.0],
499      [ 2.0,  2.0,  2.0],
500      [-4.0, -1.0,  2.0],
501      [ 0.0,  2.0, -3.0],
502      [ 0.1,  0.2,  0.3],
503      [-0.1, -0.2, -0.3],
504      [-0.1,  0.2,  0.3]
505    ].map (Point3::from).into_iter().collect();
506    let hull = Hull3::from_points (points.as_slice()).unwrap();
507    assert_eq!(hull.points(), &[
508      [-1.0, -4.0, -1.0],
509      [ 2.0,  2.0,  2.0],
510      [-4.0, -1.0,  2.0],
511      [ 0.0,  2.0, -3.0]
512    ].map (Into::into));
513  }
514}