1use core::fmt::Debug;
25use nalgebra::{ComplexField, Point2};
26use num_traits::{AsPrimitive, NumAssign};
27
28use crate::{
29 point_clouds::{downsample_point_cloud_voxel, lex_sort},
30 types::IsNan,
31 ToOwned, Vec, VecDeque,
32};
33
34use super::calculate_determinant;
35
36#[cfg_attr(
37 feature = "tracing",
38 tracing::instrument("Build Hull Segment", skip_all)
39)]
40fn build_hull_segment<
41 'a,
42 O: ComplexField + Copy + PartialOrd,
43 T: AsPrimitive<O> + Debug + IsNan + NumAssign + PartialOrd,
44>(
45 mut accumulator: VecDeque<&'a Point2<T>>,
46 current_point: &'a Point2<T>,
47) -> VecDeque<&'a Point2<T>> {
48 while accumulator.len() > 1
49 && calculate_determinant(
50 accumulator[accumulator.len() - 2],
51 accumulator[accumulator.len() - 1],
52 current_point,
53 ) <= O::zero()
54 {
55 accumulator.pop_back();
56 }
57
58 accumulator.push_back(current_point);
59 accumulator
60}
61
62#[cfg_attr(
79 feature = "tracing",
80 tracing::instrument("Construct Convex Hull Using Graham Scan", skip_all)
81)]
82pub fn graham_scan<O, T>(
83 points: &[Point2<T>],
84 assume_sorted: bool,
85 voxel_size: Option<O>,
86) -> Option<Vec<Point2<T>>>
87where
88 O: AsPrimitive<isize> + ComplexField + Copy + PartialOrd,
89 T: AsPrimitive<O> + Debug + IsNan + NumAssign + PartialOrd,
90 usize: AsPrimitive<T>,
91{
92 if points.len() < 3 {
93 return None;
94 }
95
96 let points_sorted;
97 let points_sorted_slice = match (assume_sorted, voxel_size) {
98 (true, None) => points,
99 (true, Some(voxel_size)) => {
100 points_sorted = downsample_point_cloud_voxel(points, voxel_size);
101 points_sorted.as_slice()
102 }
103 (false, None) => {
104 points_sorted = lex_sort(points)?;
105 points_sorted.as_slice()
106 }
107 (false, Some(voxel_size)) => {
108 let downsampled_points = downsample_point_cloud_voxel(points, voxel_size);
109 points_sorted = lex_sort(downsampled_points.as_slice())?;
110 points_sorted.as_slice()
111 }
112 };
113
114 let upper_hull = points_sorted_slice
115 .iter()
116 .fold(VecDeque::new(), |accumulator, point| {
117 build_hull_segment(accumulator, point)
118 });
119 let upper_hull_len = upper_hull.len();
120
121 let lower_hull = points_sorted_slice
122 .iter()
123 .rev()
124 .fold(VecDeque::new(), |accumulator, point| {
125 build_hull_segment(accumulator, point)
126 });
127 let lower_hull_len = lower_hull.len();
128
129 ((upper_hull_len + lower_hull_len - 2) > 2).then(|| {
130 upper_hull
131 .into_iter()
132 .take(upper_hull_len - 1)
133 .chain(lower_hull.into_iter().take(lower_hull_len - 1))
134 .map(ToOwned::to_owned)
135 .collect::<Vec<_>>()
136 })
137}
138
139#[cfg(feature = "pregenerated")]
140macro_rules! impl_graham_scan {
141 ($t:expr, $prec:expr, doc $doc:tt) => {
142 ::paste::paste! {
143
144 #[doc = "A premade variant of the Graham Scan algorithm function, made for " $doc " precision floating-point arithmetic, using " $t " as the point type."]
145 pub fn [<graham_scan_ $t>](input: &[Point2<$t>], assume_sorted: bool, voxel_size: Option<$prec>) -> Option<Vec<Point2<$t>>> {
146 super::graham_scan::<$prec, $t>(input, assume_sorted, voxel_size)
147 }
148 }
149 };
150 ($prec:expr, doc $doc:tt) => {
151 ::paste::paste! {
152 pub(super) mod [<$doc _precision>] {
153 use nalgebra::Point2;
154 use crate::Vec;
155
156 impl_graham_scan!(u8, $prec, doc $doc);
157 impl_graham_scan!(u16, $prec, doc $doc);
158 impl_graham_scan!(u32, $prec, doc $doc);
159 impl_graham_scan!(u64, $prec, doc $doc);
160 impl_graham_scan!(usize, $prec, doc $doc);
161
162 impl_graham_scan!(i8, $prec, doc $doc);
163 impl_graham_scan!(i16, $prec, doc $doc);
164 impl_graham_scan!(i32, $prec, doc $doc);
165 impl_graham_scan!(i64, $prec, doc $doc);
166 impl_graham_scan!(isize, $prec, doc $doc);
167
168 impl_graham_scan!(f32, $prec, doc $doc);
169 impl_graham_scan!(f64, $prec, doc $doc);
170 }
171 }
172 };
173}
174
175impl_graham_scan!(f32, doc single);
176impl_graham_scan!(f64, doc double);
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_graham_scan() {
184 let point_cloud = Vec::from([
185 Point2::new(-9.792094, 6.501087),
186 Point2::new(-9.297297, 1.4398155),
187 Point2::new(-3.0243487, 1.593832),
188 Point2::new(-8.812742, 4.631695),
189 Point2::new(-1.0105534, 6.7442665),
190 Point2::new(5.054867, 5.4078026),
191 Point2::new(-6.2726927, -6.1140366),
192 Point2::new(3.9031277, 1.8494358),
193 Point2::new(9.026024, 4.158765),
194 Point2::new(3.3359728, -5.591101),
195 Point2::new(-1.2357492, 6.9803257),
196 Point2::new(0.83374596, 4.5202436),
197 Point2::new(-9.786989, -2.6681538),
198 Point2::new(8.778954, -0.79308414),
199 Point2::new(-8.8654585, -0.54637814),
200 Point2::new(-8.697586, -5.999858),
201 Point2::new(6.7509384, 3.09805),
202 Point2::new(3.8945904, -2.1427813),
203 Point2::new(-6.2516804, -0.11557007),
204 Point2::new(-0.11794281, -7.1581955),
205 Point2::new(-8.771274, 7.540737),
206 Point2::new(-7.273285, -2.2619143),
207 Point2::new(-8.424244, 2.289342),
208 Point2::new(3.288208, -7.215433),
209 Point2::new(-8.128621, -9.368102),
210 Point2::new(4.178275, 5.9939947),
211 Point2::new(-0.8436794, 5.534689),
212 Point2::new(6.414747, 2.2250452),
213 Point2::new(2.5352774, 5.041601),
214 Point2::new(-1.8946352, 3.5172358),
215 Point2::new(-0.6175842, -1.1188431),
216 Point2::new(-1.4161129, 9.456829),
217 Point2::new(-5.09273, -9.197719),
218 Point2::new(1.5842638, 4.6569405),
219 Point2::new(8.992567, -1.571001),
220 Point2::new(8.999609, -8.009958),
221 Point2::new(-6.1459584, -2.6052542),
222 Point2::new(-4.50453, 9.498695),
223 Point2::new(-1.3855286, 1.1417828),
224 Point2::new(-4.4411488, -3.7954373),
225 Point2::new(0.8548746, 8.935608),
226 Point2::new(2.503477, -6.5350723),
227 Point2::new(-3.7824507, -9.775844),
228 Point2::new(7.757971, 9.683109),
229 Point2::new(6.6446037, 3.5783281),
230 Point2::new(-3.5842485, 6.8722763),
231 Point2::new(3.4604778, 8.430944),
232 Point2::new(-5.876791, -6.3074894),
233 Point2::new(-2.1599998, 7.8314323),
234 Point2::new(6.1809216, -6.801429),
235 ]);
236 let hull = graham_scan::<f32, f32>(&point_cloud, false, None);
237
238 assert!(hull.is_some());
239 assert_eq!(
240 hull.unwrap(),
241 Vec::from([
242 Point2::new(-9.792094, 6.501087),
243 Point2::new(-8.771274, 7.540737),
244 Point2::new(-4.50453, 9.498695),
245 Point2::new(7.757971, 9.683109),
246 Point2::new(9.026024, 4.158765),
247 Point2::new(8.999609, -8.009958),
248 Point2::new(-3.7824507, -9.775844),
249 Point2::new(-8.128621, -9.368102),
250 Point2::new(-9.786989, -2.6681538),
251 ])
252 );
253 }
254
255 #[test]
256 fn test_not_enough_points() {
257 assert_eq!(graham_scan::<f32, f32>(&[], false, None), None);
258 assert_eq!(
259 graham_scan::<f32, f32>(&[Point2::new(1.0, 1.0)], false, None),
260 None
261 );
262 assert_eq!(
263 graham_scan::<f32, f32>(&[Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)], false, None),
264 None
265 );
266 }
267
268 #[test]
269 fn test_collinear_points() {
270 let points = Vec::from([
271 Point2::new(0.0, 0.0),
272 Point2::new(1.0, 1.0),
273 Point2::new(2.0, 2.0),
274 ]);
275 assert_eq!(graham_scan::<f32, f32>(&points, false, None), None);
276 }
277
278 #[test]
279 fn test_square_points() {
280 let points = Vec::from([
281 Point2::new(0.0, 0.0),
282 Point2::new(0.0, 1.0),
283 Point2::new(1.0, 1.0),
284 Point2::new(1.0, 0.0),
285 Point2::new(0.5, 0.5),
286 ]);
287 let expected = Vec::from([
288 Point2::new(0.0, 0.0),
289 Point2::new(0.0, 1.0),
290 Point2::new(1.0, 1.0),
291 Point2::new(1.0, 0.0),
292 ]);
293 assert_eq!(
294 graham_scan::<f32, f32>(&points, false, None),
295 Some(expected)
296 );
297 }
298
299 #[test]
300 fn test_concave_shape() {
301 let points = Vec::from([
302 Point2::new(0.0, 0.0),
303 Point2::new(2.0, 0.0),
304 Point2::new(1.0, 1.0),
305 Point2::new(2.0, 2.0),
306 Point2::new(0.0, 2.0),
307 ]);
308 let expected = Vec::from([
309 Point2::new(0.0, 0.0),
310 Point2::new(0.0, 2.0),
311 Point2::new(2.0, 2.0),
312 Point2::new(2.0, 0.0),
313 ]);
314 assert_eq!(
315 graham_scan::<f32, f32>(&points, false, None),
316 Some(expected)
317 );
318 }
319
320 #[test]
321 fn test_duplicate_points() {
322 let points = Vec::from([
323 Point2::new(0.0, 0.0),
324 Point2::new(1.0, 1.0),
325 Point2::new(1.0, 1.0),
326 Point2::new(0.0, 1.0),
327 Point2::new(1.0, 0.0),
328 ]);
329 let expected = Vec::from([
330 Point2::new(0.0, 0.0),
331 Point2::new(0.0, 1.0),
332 Point2::new(1.0, 1.0),
333 Point2::new(1.0, 0.0),
334 ]);
335 assert_eq!(
336 graham_scan::<f32, f32>(&points, false, None),
337 Some(expected)
338 );
339 }
340
341 #[test]
342 fn test_negative_coordinates() {
343 let points = Vec::from([
344 Point2::new(-1.0, -1.0),
345 Point2::new(-1.0, 1.0),
346 Point2::new(1.0, 1.0),
347 Point2::new(1.0, -1.0),
348 Point2::new(0.0, 0.0),
349 ]);
350 let expected = Vec::from([
351 Point2::new(-1.0, -1.0),
352 Point2::new(-1.0, 1.0),
353 Point2::new(1.0, 1.0),
354 Point2::new(1.0, -1.0),
355 ]);
356 assert_eq!(
357 graham_scan::<f32, f32>(&points, false, None),
358 Some(expected)
359 );
360 }
361
362 #[test]
363 fn test_complex_shape() {
364 let points = Vec::from([
365 Point2::new(0.0, 0.0),
366 Point2::new(2.0, 0.0),
367 Point2::new(1.0, 1.0),
368 Point2::new(2.0, 2.0),
369 Point2::new(0.0, 2.0),
370 Point2::new(1.0, 3.0),
371 Point2::new(3.0, 1.0),
372 ]);
373
374 let expected = Vec::from([
377 Point2::new(0.0, 0.0),
378 Point2::new(0.0, 2.0),
379 Point2::new(1.0, 3.0),
380 Point2::new(3.0, 1.0),
381 Point2::new(2.0, 0.0),
382 ]);
383 assert_eq!(
384 graham_scan::<f32, f32>(&points, false, None),
385 Some(expected)
386 );
387 }
388}