parry3d_f64/transformation/
convex_hull2.rs

1use alloc::vec::Vec;
2use core::marker::PhantomData;
3
4use crate::math::Real;
5use crate::transformation::convex_hull_utils::{indexed_support_point_id, support_point_id};
6use na::{self, Point2, Vector2};
7use num_traits::Zero;
8
9/// Computes the convex hull of a set of 2d points.
10///
11/// The computed convex-hull have its points given in counter-clockwise order.
12#[cfg(feature = "dim2")]
13pub fn convex_hull2(points: &[Point2<Real>]) -> Vec<Point2<Real>> {
14    convex_hull2_idx(points)
15        .into_iter()
16        .map(|id| points[id])
17        .collect()
18}
19
20/// Computes the convex hull of a set of 2d points and returns only the indices of the hull
21/// vertices.
22///
23/// The computed convex-hull have its points given in counter-clockwise order.
24pub fn convex_hull2_idx(points: &[Point2<Real>]) -> Vec<usize> {
25    let mut undecidable_points = Vec::new();
26    let mut segments = get_initial_polyline(points, &mut undecidable_points);
27
28    let mut i = 0;
29    while i != segments.len() {
30        if !segments[i].valid {
31            i += 1;
32            continue;
33        }
34
35        let pt_id = indexed_support_point_id(
36            &segments[i].normal,
37            points,
38            segments[i].visible_points.iter().copied(),
39        );
40
41        if let Some(point) = pt_id {
42            segments[i].valid = false;
43
44            attach_and_push_facets2(
45                segments[i].prev,
46                segments[i].next,
47                point,
48                points,
49                &mut segments,
50                i,
51                &mut undecidable_points,
52            );
53        }
54
55        i += 1;
56    }
57
58    let mut idx = Vec::new();
59    let mut curr_facet = 0;
60
61    while !segments[curr_facet].valid {
62        curr_facet += 1
63    }
64
65    let first_facet = curr_facet;
66
67    loop {
68        let curr = &segments[curr_facet];
69
70        if curr.valid {
71            idx.push(curr.pts[0]);
72        }
73
74        curr_facet = curr.next;
75
76        if curr_facet == first_facet {
77            break;
78        }
79    }
80
81    idx
82}
83
84fn get_initial_polyline(
85    points: &[Point2<Real>],
86    undecidable: &mut Vec<usize>,
87) -> Vec<SegmentFacet> {
88    let mut res = Vec::new();
89
90    assert!(points.len() >= 2);
91
92    let p1 = support_point_id(&Vector2::x(), points).unwrap();
93    let mut p2 = p1;
94
95    let direction = [-Vector2::x(), -Vector2::y(), Vector2::y()];
96
97    for dir in direction.iter() {
98        p2 = support_point_id(dir, points).unwrap();
99
100        let p1p2 = points[p2] - points[p1];
101
102        if !p1p2.norm_squared().is_zero() {
103            break;
104        }
105    }
106
107    assert!(
108        p1 != p2,
109        "Failed to build the 2d convex hull of this point cloud."
110    );
111
112    // Build two facets with opposite normals.
113    let mut f1 = SegmentFacet::new(p1, p2, 1, 1, points);
114    let mut f2 = SegmentFacet::new(p2, p1, 0, 0, points);
115
116    // Attribute points to each facet.
117    for i in 0..points.len() {
118        if i == p1 || i == p2 {
119            continue;
120        }
121        if f1.can_be_seen_by(i, points) {
122            f1.visible_points.push(i);
123        } else if f2.can_be_seen_by(i, points) {
124            f2.visible_points.push(i);
125        } else {
126            // The point is collinear.
127            undecidable.push(i);
128        }
129    }
130
131    res.push(f1);
132    res.push(f2);
133
134    res
135}
136
137fn attach_and_push_facets2(
138    prev_facet: usize,
139    next_facet: usize,
140    point: usize,
141    points: &[Point2<Real>],
142    segments: &mut Vec<SegmentFacet>,
143    removed_facet: usize,
144    undecidable: &mut Vec<usize>,
145) {
146    let new_facet1_id = segments.len();
147    let new_facet2_id = new_facet1_id + 1;
148    let prev_pt = segments[prev_facet].pts[1];
149    let next_pt = segments[next_facet].pts[0];
150
151    let mut new_facet1 = SegmentFacet::new(prev_pt, point, prev_facet, new_facet2_id, points);
152    let mut new_facet2 = SegmentFacet::new(point, next_pt, new_facet1_id, next_facet, points);
153
154    segments[prev_facet].next = new_facet1_id;
155    segments[next_facet].prev = new_facet2_id;
156
157    // Assign to each facets some of the points which can see it.
158    for visible_point in segments[removed_facet].visible_points.iter() {
159        if *visible_point == point {
160            continue;
161        }
162
163        if new_facet1.can_be_seen_by(*visible_point, points) {
164            new_facet1.visible_points.push(*visible_point);
165        } else if new_facet2.can_be_seen_by(*visible_point, points) {
166            new_facet2.visible_points.push(*visible_point);
167        }
168        // If none of the facet can be seen from the point, it is naturally deleted.
169    }
170
171    // Try to assign collinear points to one of the new facets
172    let mut i = 0;
173
174    while i != undecidable.len() {
175        if new_facet1.can_be_seen_by(undecidable[i], points) {
176            new_facet1.visible_points.push(undecidable[i]);
177            let _ = undecidable.swap_remove(i);
178        } else if new_facet2.can_be_seen_by(undecidable[i], points) {
179            new_facet2.visible_points.push(undecidable[i]);
180            let _ = undecidable.swap_remove(i);
181        } else {
182            i += 1;
183        }
184    }
185
186    segments.push(new_facet1);
187    segments.push(new_facet2);
188}
189
190struct SegmentFacet {
191    pub valid: bool,
192    pub normal: Vector2<Real>,
193    pub next: usize,
194    pub prev: usize,
195    pub pts: [usize; 2],
196    pub visible_points: Vec<usize>,
197    pt_type: PhantomData<Point2<Real>>,
198}
199
200impl SegmentFacet {
201    pub fn new(
202        p1: usize,
203        p2: usize,
204        prev: usize,
205        next: usize,
206        points: &[Point2<Real>],
207    ) -> SegmentFacet {
208        let p1p2 = points[p2] - points[p1];
209
210        let mut normal = Vector2::new(p1p2.y, -p1p2.x);
211        let norm = normal.normalize_mut();
212
213        SegmentFacet {
214            valid: norm != 0.0,
215            normal,
216            prev,
217            next,
218            pts: [p1, p2],
219            visible_points: Vec::new(),
220            pt_type: PhantomData,
221        }
222    }
223
224    pub fn can_be_seen_by(&self, point: usize, points: &[Point2<Real>]) -> bool {
225        let p0 = &points[self.pts[0]];
226        let pt = &points[point];
227
228        let _eps = crate::math::DEFAULT_EPSILON;
229
230        (*pt - *p0).dot(&self.normal) > _eps * na::convert::<f64, Real>(100.0f64)
231    }
232}