parry2d/transformation/
convex_hull2.rs

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