1use nalgebra::{ComplexField, Point2, Scalar};
25use num_traits::{real::Real, AsPrimitive, NumAssign};
26
27use crate::{point_clouds::downsample_point_cloud_voxel, Ordering, Vec};
28
29use super::calculate_determinant;
30
31#[cfg_attr(
45 feature = "tracing",
46 tracing::instrument("Construct Convex Hull Using Jarvis March", skip_all)
47)]
48pub fn jarvis_march<O, T>(points: &[Point2<T>], voxel_size: Option<O>) -> Option<Vec<Point2<T>>>
49where
50 O: AsPrimitive<isize> + ComplexField + Copy + Real,
51 T: AsPrimitive<O> + NumAssign + PartialOrd + Scalar,
52 usize: AsPrimitive<T>,
53{
54 if points.len() < 3 {
55 return None;
56 }
57
58 let points_downsampled;
59 let points_downsampled_slice;
60 if let Some(voxel_size) = voxel_size {
61 points_downsampled = downsample_point_cloud_voxel(points, voxel_size);
62 points_downsampled_slice = points_downsampled.as_slice();
63 } else {
64 points_downsampled_slice = points;
65 }
66
67 let mut current_vertex = points_downsampled_slice.iter().min_by(|a, b| {
69 a.coords
70 .iter()
71 .zip(b.coords.iter())
72 .fold(Ordering::Equal, |ord, (a, b)| {
73 ord.then_with(|| a.partial_cmp(b).unwrap())
74 })
75 })?;
76
77 let mut hull = Vec::with_capacity(points_downsampled_slice.len());
78 loop {
79 hull.push(*current_vertex);
80 let mut endpoint = &points_downsampled_slice[0];
81 for point in points_downsampled_slice {
82 if endpoint == current_vertex
83 || calculate_determinant(hull.last().unwrap(), endpoint, point) < O::zero()
84 {
85 endpoint = point;
86 }
87 }
88
89 current_vertex = endpoint;
90
91 if current_vertex == &hull[0] {
92 break;
93 }
94 }
95
96 if hull.len() < 3 {
97 return None;
98 }
99
100 Some(hull)
101}
102
103#[cfg(feature = "pregenerated")]
104macro_rules! impl_jarvis_march {
105 ($t:expr, $prec:expr, doc $doc:tt) => {
106 ::paste::paste! {
107
108 #[doc = "A premade variant of the Jarvis March algorithm function, made for " $doc " precision floating-point arithmetic, using " $t " as the point type."]
109 pub fn [<jarvis_march_ $t>](input: &[Point2<$t>], voxel_size: Option<$prec>) -> Option<Vec<Point2<$t>>> {
110 super::jarvis_march::<$prec, $t>(input, voxel_size)
111 }
112 }
113 };
114 ($prec:expr, doc $doc:tt) => {
115 ::paste::paste! {
116 pub(super) mod [<$doc _precision>] {
117 use nalgebra::Point2;
118 use crate::Vec;
119
120 impl_jarvis_march!(u8, $prec, doc $doc);
121 impl_jarvis_march!(u16, $prec, doc $doc);
122 impl_jarvis_march!(u32, $prec, doc $doc);
123 impl_jarvis_march!(u64, $prec, doc $doc);
124 impl_jarvis_march!(usize, $prec, doc $doc);
125
126 impl_jarvis_march!(i8, $prec, doc $doc);
127 impl_jarvis_march!(i16, $prec, doc $doc);
128 impl_jarvis_march!(i32, $prec, doc $doc);
129 impl_jarvis_march!(i64, $prec, doc $doc);
130 impl_jarvis_march!(isize, $prec, doc $doc);
131
132 impl_jarvis_march!(f32, $prec, doc $doc);
133 impl_jarvis_march!(f64, $prec, doc $doc);
134 }
135 }
136 };
137}
138
139impl_jarvis_march!(f32, doc single);
140impl_jarvis_march!(f64, doc double);
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_jarvis_march() {
148 let point_cloud = Vec::from([
149 Point2::new(-9.792094, 6.501087),
150 Point2::new(-9.297297, 1.4398155),
151 Point2::new(-3.0243487, 1.593832),
152 Point2::new(-8.812742, 4.631695),
153 Point2::new(-1.0105534, 6.7442665),
154 Point2::new(5.054867, 5.4078026),
155 Point2::new(-6.2726927, -6.1140366),
156 Point2::new(3.9031277, 1.8494358),
157 Point2::new(9.026024, 4.158765),
158 Point2::new(3.3359728, -5.591101),
159 Point2::new(-1.2357492, 6.9803257),
160 Point2::new(0.83374596, 4.5202436),
161 Point2::new(-9.786989, -2.6681538),
162 Point2::new(8.778954, -0.79308414),
163 Point2::new(-8.8654585, -0.54637814),
164 Point2::new(-8.697586, -5.999858),
165 Point2::new(6.7509384, 3.09805),
166 Point2::new(3.8945904, -2.1427813),
167 Point2::new(-6.2516804, -0.11557007),
168 Point2::new(-0.11794281, -7.1581955),
169 Point2::new(-8.771274, 7.540737),
170 Point2::new(-7.273285, -2.2619143),
171 Point2::new(-8.424244, 2.289342),
172 Point2::new(3.288208, -7.215433),
173 Point2::new(-8.128621, -9.368102),
174 Point2::new(4.178275, 5.9939947),
175 Point2::new(-0.8436794, 5.534689),
176 Point2::new(6.414747, 2.2250452),
177 Point2::new(2.5352774, 5.041601),
178 Point2::new(-1.8946352, 3.5172358),
179 Point2::new(-0.6175842, -1.1188431),
180 Point2::new(-1.4161129, 9.456829),
181 Point2::new(-5.09273, -9.197719),
182 Point2::new(1.5842638, 4.6569405),
183 Point2::new(8.992567, -1.571001),
184 Point2::new(8.999609, -8.009958),
185 Point2::new(-6.1459584, -2.6052542),
186 Point2::new(-4.50453, 9.498695),
187 Point2::new(-1.3855286, 1.1417828),
188 Point2::new(-4.4411488, -3.7954373),
189 Point2::new(0.8548746, 8.935608),
190 Point2::new(2.503477, -6.5350723),
191 Point2::new(-3.7824507, -9.775844),
192 Point2::new(7.757971, 9.683109),
193 Point2::new(6.6446037, 3.5783281),
194 Point2::new(-3.5842485, 6.8722763),
195 Point2::new(3.4604778, 8.430944),
196 Point2::new(-5.876791, -6.3074894),
197 Point2::new(-2.1599998, 7.8314323),
198 Point2::new(6.1809216, -6.801429),
199 ]);
200 let hull = jarvis_march::<f32, f32>(&point_cloud, None);
201
202 assert!(hull.is_some());
203 assert_eq!(
204 hull.unwrap(),
205 Vec::from([
206 Point2::new(-9.792094, 6.501087),
207 Point2::new(-8.771274, 7.540737),
208 Point2::new(-4.50453, 9.498695),
209 Point2::new(7.757971, 9.683109),
210 Point2::new(9.026024, 4.158765),
211 Point2::new(8.999609, -8.009958),
212 Point2::new(-3.7824507, -9.775844),
213 Point2::new(-8.128621, -9.368102),
214 Point2::new(-9.786989, -2.6681538),
215 ])
216 );
217 }
218
219 #[test]
220 fn test_not_enough_points() {
221 assert_eq!(jarvis_march::<f32, f32>(&[], None), None);
222 assert_eq!(
223 jarvis_march::<f32, f32>(&[Point2::new(1.0, 1.0)], None),
224 None
225 );
226 assert_eq!(
227 jarvis_march::<f32, f32>(&[Point2::new(0.0, 0.0), Point2::new(1.0, 1.0)], None),
228 None
229 );
230 }
231
232 #[test]
233 fn test_collinear_points() {
234 let points = Vec::from([
235 Point2::new(0.0, 0.0),
236 Point2::new(1.0, 1.0),
237 Point2::new(2.0, 2.0),
238 ]);
239 assert_eq!(jarvis_march::<f32, f32>(&points, None), None);
240 }
241
242 #[test]
243 fn test_square_points() {
244 let points = Vec::from([
245 Point2::new(0.0, 0.0),
246 Point2::new(0.0, 1.0),
247 Point2::new(1.0, 1.0),
248 Point2::new(1.0, 0.0),
249 Point2::new(0.5, 0.5),
250 ]);
251 let expected = Vec::from([
252 Point2::new(0.0, 0.0),
253 Point2::new(0.0, 1.0),
254 Point2::new(1.0, 1.0),
255 Point2::new(1.0, 0.0),
256 ]);
257 assert_eq!(jarvis_march::<f32, f32>(&points, None), Some(expected));
258 }
259
260 #[test]
261 fn test_concave_shape() {
262 let points = Vec::from([
263 Point2::new(0.0, 0.0),
264 Point2::new(2.0, 0.0),
265 Point2::new(1.0, 1.0),
266 Point2::new(2.0, 2.0),
267 Point2::new(0.0, 2.0),
268 ]);
269 let expected = Vec::from([
270 Point2::new(0.0, 0.0),
271 Point2::new(0.0, 2.0),
272 Point2::new(2.0, 2.0),
273 Point2::new(2.0, 0.0),
274 ]);
275 assert_eq!(jarvis_march::<f32, f32>(&points, None), Some(expected));
276 }
277
278 #[test]
279 fn test_duplicate_points() {
280 let points = Vec::from([
281 Point2::new(0.0, 0.0),
282 Point2::new(1.0, 1.0),
283 Point2::new(1.0, 1.0),
284 Point2::new(0.0, 1.0),
285 Point2::new(1.0, 0.0),
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!(jarvis_march::<f32, f32>(&points, None), Some(expected));
294 }
295
296 #[test]
297 fn test_negative_coordinates() {
298 let points = Vec::from([
299 Point2::new(-1.0, -1.0),
300 Point2::new(-1.0, 1.0),
301 Point2::new(1.0, 1.0),
302 Point2::new(1.0, -1.0),
303 Point2::new(0.0, 0.0),
304 ]);
305 let expected = Vec::from([
306 Point2::new(-1.0, -1.0),
307 Point2::new(-1.0, 1.0),
308 Point2::new(1.0, 1.0),
309 Point2::new(1.0, -1.0),
310 ]);
311 assert_eq!(jarvis_march::<f32, f32>(&points, None), Some(expected));
312 }
313
314 #[test]
315 fn test_complex_shape() {
316 let points = Vec::from([
317 Point2::new(0.0, 0.0),
318 Point2::new(2.0, 0.0),
319 Point2::new(1.0, 1.0),
320 Point2::new(2.0, 2.0),
321 Point2::new(0.0, 2.0),
322 Point2::new(1.0, 3.0),
323 Point2::new(3.0, 1.0),
324 ]);
325 let expected = Vec::from([
326 Point2::new(0.0, 0.0),
327 Point2::new(0.0, 2.0),
328 Point2::new(1.0, 3.0),
329 Point2::new(2.0, 2.0),
330 Point2::new(3.0, 1.0),
331 Point2::new(2.0, 0.0),
332 ]);
333 assert_eq!(jarvis_march::<f32, f32>(&points, None), Some(expected));
334 }
335}