1#![allow(clippy::needless_range_loop)]
17
18use crate::constants::linear_slop;
19use crate::math_functions::{
20 aabb_center, cross, distance_squared, max, min, min_int, normalize, sub, Aabb, Vec2, VEC2_ZERO,
21};
22
23pub const MAX_POLYGON_VERTICES: usize = 8;
26
27#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct Hull {
30 pub points: [Vec2; MAX_POLYGON_VERTICES],
32 pub count: i32,
34}
35
36impl Default for Hull {
37 fn default() -> Self {
38 Hull {
39 points: [VEC2_ZERO; MAX_POLYGON_VERTICES],
40 count: 0,
41 }
42 }
43}
44
45fn recurse_hull(p1: Vec2, p2: Vec2, ps: &[Vec2]) -> Hull {
47 let mut hull = Hull::default();
48
49 let count = ps.len();
50 if count == 0 {
51 return hull;
52 }
53
54 let e = normalize(sub(p2, p1));
56
57 let mut right_points = [VEC2_ZERO; MAX_POLYGON_VERTICES];
59 let mut right_count = 0;
60
61 let mut best_index = 0;
62 let mut best_distance = cross(sub(ps[best_index], p1), e);
63 if best_distance > 0.0 {
64 right_points[right_count] = ps[best_index];
65 right_count += 1;
66 }
67
68 for i in 1..count {
69 let distance = cross(sub(ps[i], p1), e);
70 if distance > best_distance {
71 best_index = i;
72 best_distance = distance;
73 }
74
75 if distance > 0.0 {
76 right_points[right_count] = ps[i];
77 right_count += 1;
78 }
79 }
80
81 if best_distance < 2.0 * linear_slop() {
82 return hull;
83 }
84
85 let best_point = ps[best_index];
86
87 let hull1 = recurse_hull(p1, best_point, &right_points[..right_count]);
89
90 let hull2 = recurse_hull(best_point, p2, &right_points[..right_count]);
92
93 for i in 0..hull1.count as usize {
95 hull.points[hull.count as usize] = hull1.points[i];
96 hull.count += 1;
97 }
98
99 hull.points[hull.count as usize] = best_point;
100 hull.count += 1;
101
102 for i in 0..hull2.count as usize {
103 hull.points[hull.count as usize] = hull2.points[i];
104 hull.count += 1;
105 }
106
107 debug_assert!(hull.count < MAX_POLYGON_VERTICES as i32);
108
109 hull
110}
111
112pub fn compute_hull(points: &[Vec2]) -> Hull {
121 let mut hull = Hull::default();
122
123 let mut count = points.len() as i32;
124
125 if count < 3 || count > MAX_POLYGON_VERTICES as i32 {
126 return hull;
128 }
129
130 count = min_int(count, MAX_POLYGON_VERTICES as i32);
131
132 let mut aabb = Aabb {
133 lower_bound: Vec2 {
134 x: f32::MAX,
135 y: f32::MAX,
136 },
137 upper_bound: Vec2 {
138 x: -f32::MAX,
139 y: -f32::MAX,
140 },
141 };
142
143 let mut ps = [VEC2_ZERO; MAX_POLYGON_VERTICES];
146 let mut n = 0;
147 let slop = linear_slop();
148 let tol_sqr = 16.0 * slop * slop;
149 for i in 0..count as usize {
150 aabb.lower_bound = min(aabb.lower_bound, points[i]);
151 aabb.upper_bound = max(aabb.upper_bound, points[i]);
152
153 let vi = points[i];
154
155 let mut unique = true;
156 for j in 0..i {
157 let vj = points[j];
158
159 let dist_sqr = distance_squared(vi, vj);
160 if dist_sqr < tol_sqr {
161 unique = false;
162 break;
163 }
164 }
165
166 if unique {
167 ps[n] = vi;
168 n += 1;
169 }
170 }
171
172 if n < 3 {
173 return hull;
175 }
176
177 let c = aabb_center(aabb);
179 let mut f1 = 0;
180 let mut dsq1 = distance_squared(c, ps[f1]);
181 for i in 1..n {
182 let dsq = distance_squared(c, ps[i]);
183 if dsq > dsq1 {
184 f1 = i;
185 dsq1 = dsq;
186 }
187 }
188
189 let p1 = ps[f1];
191 ps[f1] = ps[n - 1];
192 n -= 1;
193
194 let mut f2 = 0;
195 let mut dsq2 = distance_squared(p1, ps[f2]);
196 for i in 1..n {
197 let dsq = distance_squared(p1, ps[i]);
198 if dsq > dsq2 {
199 f2 = i;
200 dsq2 = dsq;
201 }
202 }
203
204 let p2 = ps[f2];
206 ps[f2] = ps[n - 1];
207 n -= 1;
208
209 let mut right_points = [VEC2_ZERO; MAX_POLYGON_VERTICES - 2];
211 let mut right_count = 0;
212
213 let mut left_points = [VEC2_ZERO; MAX_POLYGON_VERTICES - 2];
214 let mut left_count = 0;
215
216 let e = normalize(sub(p2, p1));
217
218 for i in 0..n {
219 let d = cross(sub(ps[i], p1), e);
220
221 if d >= 2.0 * slop {
223 right_points[right_count] = ps[i];
224 right_count += 1;
225 } else if d <= -2.0 * slop {
226 left_points[left_count] = ps[i];
227 left_count += 1;
228 }
229 }
230
231 let hull1 = recurse_hull(p1, p2, &right_points[..right_count]);
233 let hull2 = recurse_hull(p2, p1, &left_points[..left_count]);
234
235 if hull1.count == 0 && hull2.count == 0 {
236 return hull;
238 }
239
240 hull.points[hull.count as usize] = p1;
242 hull.count += 1;
243
244 for i in 0..hull1.count as usize {
245 hull.points[hull.count as usize] = hull1.points[i];
246 hull.count += 1;
247 }
248
249 hull.points[hull.count as usize] = p2;
250 hull.count += 1;
251
252 for i in 0..hull2.count as usize {
253 hull.points[hull.count as usize] = hull2.points[i];
254 hull.count += 1;
255 }
256
257 debug_assert!(hull.count <= MAX_POLYGON_VERTICES as i32);
258
259 let mut searching = true;
261 while searching && hull.count > 2 {
262 searching = false;
263
264 for i in 0..hull.count as usize {
265 let i1 = i;
266 let i2 = (i + 1) % hull.count as usize;
267 let i3 = (i + 2) % hull.count as usize;
268
269 let s1 = hull.points[i1];
270 let s2 = hull.points[i2];
271 let s3 = hull.points[i3];
272
273 let r = normalize(sub(s3, s1));
275
276 let distance = cross(sub(s2, s1), r);
277 if distance <= 2.0 * slop {
278 for j in i2..(hull.count as usize - 1) {
280 hull.points[j] = hull.points[j + 1];
281 }
282 hull.count -= 1;
283
284 searching = true;
286
287 break;
288 }
289 }
290 }
291
292 if hull.count < 3 {
293 hull.count = 0;
295 }
296
297 hull
298}
299
300pub fn validate_hull(hull: &Hull) -> bool {
303 if hull.count < 3 || (MAX_POLYGON_VERTICES as i32) < hull.count {
304 return false;
305 }
306
307 let count = hull.count as usize;
308
309 for i in 0..count {
311 let i1 = i;
313 let i2 = if i < count - 1 { i1 + 1 } else { 0 };
314 let p = hull.points[i1];
315 let e = normalize(sub(hull.points[i2], p));
316
317 for (j, &point) in hull.points[..count].iter().enumerate() {
318 if j == i1 || j == i2 {
320 continue;
321 }
322
323 let distance = cross(sub(point, p), e);
324 if distance >= 0.0 {
325 return false;
326 }
327 }
328 }
329
330 let slop = linear_slop();
332 for i in 0..count {
333 let i1 = i;
334 let i2 = (i + 1) % count;
335 let i3 = (i + 2) % count;
336
337 let p1 = hull.points[i1];
338 let p2 = hull.points[i2];
339 let p3 = hull.points[i3];
340
341 let e = normalize(sub(p3, p1));
342
343 let distance = cross(sub(p2, p1), e);
344 if distance <= slop {
345 return false;
347 }
348 }
349
350 true
351}