geo/algorithm/convex_hull/
qhull.rs1use super::{swap_remove_to_first, trivial_hull};
2use crate::kernels::{HasKernel, Kernel, Orientation};
3use crate::utils::partition_slice;
4use crate::{coord, Coord, GeoNum, LineString};
5
6#[inline]
12fn is_ccw<T>(p_a: Coord<T>, p_b: Coord<T>, p_c: Coord<T>) -> bool
13where
14 T: GeoNum,
15{
16 let o = <T as HasKernel>::Ker::orient2d(p_a, p_b, p_c);
17 o == Orientation::CounterClockwise
18}
19
20pub fn quick_hull<T>(mut points: &mut [Coord<T>]) -> LineString<T>
22where
23 T: GeoNum,
24{
25 if points.len() < 4 {
27 return trivial_hull(points, false);
28 }
29 let mut hull = vec![];
30
31 use crate::utils::least_and_greatest_index;
32 let (min, max) = {
33 let (min_idx, mut max_idx) = least_and_greatest_index(points);
34 let min = swap_remove_to_first(&mut points, min_idx);
35
36 if max_idx == 0 {
39 max_idx = min_idx;
40 }
41
42 max_idx = max_idx.saturating_sub(1);
46
47 let max = swap_remove_to_first(&mut points, max_idx);
48 (min, max)
49 };
50
51 {
52 let (points, _) = partition_slice(points, |p| is_ccw(*max, *min, *p));
53 hull_set(*max, *min, points, &mut hull);
54 }
55 hull.push(*max);
56 let (points, _) = partition_slice(points, |p| is_ccw(*min, *max, *p));
57 hull_set(*min, *max, points, &mut hull);
58 hull.push(*min);
59 let mut hull: LineString<_> = hull.into();
61 hull.close();
62 hull
63}
64
65fn hull_set<T>(p_a: Coord<T>, p_b: Coord<T>, mut set: &mut [Coord<T>], hull: &mut Vec<Coord<T>>)
67where
68 T: GeoNum,
69{
70 if set.is_empty() {
71 return;
72 }
73 if set.len() == 1 {
74 hull.push(set[0]);
75 return;
76 }
77
78 let p_orth = coord! {
82 x: p_a.y - p_b.y,
83 y: p_b.x - p_a.x,
84 };
85
86 let furthest_idx = set
87 .iter()
88 .map(|pt| {
89 let p_diff = coord! {
90 x: pt.x - p_a.x,
91 y: pt.y - p_a.y,
92 };
93 p_orth.x * p_diff.x + p_orth.y * p_diff.y
94 })
95 .enumerate()
96 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
97 .unwrap()
98 .0;
99
100 let furthest_point = swap_remove_to_first(&mut set, furthest_idx);
102 {
104 let (points, _) = partition_slice(set, |p| is_ccw(*furthest_point, p_b, *p));
105 hull_set(*furthest_point, p_b, points, hull);
106 }
107 hull.push(*furthest_point);
108 let (points, _) = partition_slice(set, |p| is_ccw(p_a, *furthest_point, *p));
110 hull_set(p_a, *furthest_point, points, hull);
111}
112
113#[cfg(test)]
114mod test {
115 use super::*;
116 use crate::IsConvex;
117
118 #[test]
119 fn quick_hull_test1() {
120 let mut v = vec![
121 coord! { x: 0.0, y: 0.0 },
122 coord! { x: 4.0, y: 0.0 },
123 coord! { x: 4.0, y: 1.0 },
124 coord! { x: 1.0, y: 1.0 },
125 coord! { x: 1.0, y: 4.0 },
126 coord! { x: 0.0, y: 4.0 },
127 coord! { x: 0.0, y: 0.0 },
128 ];
129 let res = quick_hull(&mut v);
130 assert!(res.is_strictly_ccw_convex());
131 }
132
133 #[test]
134 fn quick_hull_test2() {
135 let mut v = vec![
136 coord! { x: 0, y: 10 },
137 coord! { x: 1, y: 1 },
138 coord! { x: 10, y: 0 },
139 coord! { x: 1, y: -1 },
140 coord! { x: 0, y: -10 },
141 coord! { x: -1, y: -1 },
142 coord! { x: -10, y: 0 },
143 coord! { x: -1, y: 1 },
144 coord! { x: 0, y: 10 },
145 ];
146 let correct = vec![
147 coord! { x: 0, y: -10 },
148 coord! { x: 10, y: 0 },
149 coord! { x: 0, y: 10 },
150 coord! { x: -10, y: 0 },
151 coord! { x: 0, y: -10 },
152 ];
153 let res = quick_hull(&mut v);
154 assert_eq!(res.0, correct);
155 }
156
157 #[test]
158 fn quick_hull_test_ccw() {
160 let initial = vec![
161 (1.0, 0.0),
162 (2.0, 1.0),
163 (1.75, 1.1),
164 (1.0, 2.0),
165 (0.0, 1.0),
166 (1.0, 0.0),
167 ];
168 let mut v: Vec<_> = initial.iter().map(|e| coord! { x: e.0, y: e.1 }).collect();
169 let correct = vec![(1.0, 0.0), (2.0, 1.0), (1.0, 2.0), (0.0, 1.0), (1.0, 0.0)];
170 let v_correct: Vec<_> = correct.iter().map(|e| coord! { x: e.0, y: e.1 }).collect();
171 let res = quick_hull(&mut v);
172 assert_eq!(res.0, v_correct);
173 }
174
175 #[test]
176 fn quick_hull_test_ccw_maintain() {
177 let initial = vec![
179 (0., 0.),
180 (2., 0.),
181 (2.5, 1.75),
182 (2.3, 1.7),
183 (1.75, 2.5),
184 (1.3, 2.),
185 (0., 2.),
186 (0., 0.),
187 ];
188 let mut v: Vec<_> = initial.iter().map(|e| coord! { x: e.0, y: e.1 }).collect();
189 let res = quick_hull(&mut v);
190 assert!(res.is_strictly_ccw_convex());
191 }
192
193 #[test]
194 fn quick_hull_test_complex() {
195 let mut coords = geo_test_fixtures::poly1::<f64>().0;
196 let correct = geo_test_fixtures::poly1_hull::<f64>().0;
197 let res = quick_hull(&mut coords);
198 assert_eq!(res.0, correct);
199 }
200
201 #[test]
202 fn quick_hull_test_complex_2() {
203 let mut coords = geo_test_fixtures::poly2::<f64>().0;
204 let correct = geo_test_fixtures::poly2_hull::<f64>().0;
205 let res = quick_hull(&mut coords);
206 assert_eq!(res.0, correct);
207 }
208
209 #[test]
210 fn quick_hull_test_collinear() {
211 let initial = vec![
215 (-1., 0.),
216 (-1., -1.),
217 (-1., 1.),
218 (0., 0.),
219 (0., -1.),
220 (0., 1.),
221 (1., 0.),
222 (1., -1.),
223 (1., 1.),
224 ];
225 let mut v: Vec<_> = initial.iter().map(|e| coord! { x: e.0, y: e.1 }).collect();
226 let res = quick_hull(&mut v);
227 assert!(res.is_strictly_ccw_convex());
228 }
229}