1use crate::{Point2, Scalar, Vec2};
11
12#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct Aabb2 {
20 pub min: Point2,
22 pub max: Point2,
24}
25
26impl Aabb2 {
27 pub const fn empty() -> Self {
35 Self {
36 min: Vec2::splat(Scalar::INFINITY),
37 max: Vec2::splat(Scalar::NEG_INFINITY),
38 }
39 }
40
41 #[inline]
43 pub const fn from_point(point: Point2) -> Self {
44 Self {
45 min: point,
46 max: point,
47 }
48 }
49
50 #[inline]
52 pub fn extend(&mut self, point: Point2) {
53 self.min = self.min.min(point);
54 self.max = self.max.max(point);
55 }
56
57 #[inline]
59 pub fn union(&mut self, other: &Self) {
60 self.min = self.min.min(other.min);
61 self.max = self.max.max(other.max);
62 }
63
64 #[inline]
66 pub fn is_finite(&self) -> bool {
67 self.min.is_finite() && self.max.is_finite()
68 }
69
70 #[inline]
72 pub fn intersects(&self, other: &Self) -> bool {
73 self.min.x <= other.max.x
74 && self.max.x >= other.min.x
75 && self.min.y <= other.max.y
76 && self.max.y >= other.min.y
77 }
78
79 #[inline]
81 pub fn contains(&self, point: Point2) -> bool {
82 point.x >= self.min.x
83 && point.x <= self.max.x
84 && point.y >= self.min.y
85 && point.y <= self.max.y
86 }
87
88 #[inline]
90 pub fn is_empty(&self) -> bool {
91 self.min.x > self.max.x
92 }
93
94 pub fn diagonal(&self) -> Vec2 {
96 if self.is_empty() {
97 Vec2::ZERO
98 } else {
99 self.max - self.min
100 }
101 }
102
103 #[inline]
105 pub fn center(&self) -> Point2 {
106 if self.is_empty() {
107 Vec2::ZERO
108 } else {
109 (self.min + self.max) * 0.5
110 }
111 }
112
113 pub fn area(&self) -> Scalar {
115 let span = self.diagonal();
116 span.x * span.y
117 }
118}
119
120impl Default for Aabb2 {
121 fn default() -> Self {
122 Self::empty()
123 }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq)]
133pub struct Triangle2 {
134 pub a: Point2,
136 pub b: Point2,
138 pub c: Point2,
140}
141
142impl Triangle2 {
143 pub const fn new(a: Point2, b: Point2, c: Point2) -> Self {
145 Self { a, b, c }
146 }
147
148 pub fn signed_area2(&self) -> Scalar {
155 let ab = self.b - self.a;
156 let ac = self.c - self.a;
157 ab.perp_dot(ac)
158 }
159
160 pub fn signed_area(&self) -> Scalar {
162 self.signed_area2() * 0.5
163 }
164
165 pub fn area(&self) -> Scalar {
167 self.signed_area().abs()
168 }
169
170 pub fn centroid(&self) -> Point2 {
172 (self.a + self.b + self.c) / 3.0
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq)]
184pub struct Rectangle2 {
185 pub origin: Point2,
187 pub x: Vec2,
189 pub y: Vec2,
191}
192
193impl Rectangle2 {
194 pub const fn new(origin: Point2, x: Vec2, y: Vec2) -> Self {
196 Self { origin, x, y }
197 }
198
199 pub fn from_aabb(bounds: &Aabb2) -> Option<Self> {
205 if bounds.is_empty() {
206 return None;
207 }
208 let span = bounds.diagonal();
209 Some(Self {
210 origin: bounds.min,
211 x: Vec2::new(span.x, 0.0),
212 y: Vec2::new(0.0, span.y),
213 })
214 }
215
216 pub fn corners(&self) -> [Point2; 4] {
218 [
219 self.origin,
220 self.origin + self.x,
221 self.origin + self.x + self.y,
222 self.origin + self.y,
223 ]
224 }
225
226 pub fn signed_area(&self) -> Scalar {
228 self.x.perp_dot(self.y)
229 }
230
231 pub fn area(&self) -> Scalar {
233 self.signed_area().abs()
234 }
235
236 pub fn center(&self) -> Point2 {
238 self.origin + (self.x + self.y) * 0.5
239 }
240}
241
242#[derive(Debug, Clone, PartialEq)]
253pub struct Polygon2 {
254 pub vertices: Vec<Point2>,
256}
257
258impl Polygon2 {
259 pub const fn new(vertices: Vec<Point2>) -> Self {
261 Self { vertices }
262 }
263
264 pub fn len(&self) -> usize {
266 self.vertices.len()
267 }
268
269 pub fn is_empty(&self) -> bool {
271 self.vertices.is_empty()
272 }
273
274 pub fn signed_area2(&self) -> Scalar {
284 if self.vertices.len() < 3 {
285 return 0.0;
286 }
287 let base = self.vertices[0];
288 let mut total = 0.0;
289 for pair in self.vertices[1..].windows(2) {
290 total += (pair[0] - base).perp_dot(pair[1] - base);
291 }
292 total
293 }
294
295 pub fn signed_area(&self) -> Scalar {
297 self.signed_area2() * 0.5
298 }
299
300 pub fn area(&self) -> Scalar {
302 self.signed_area().abs()
303 }
304
305 pub fn is_counter_clockwise(&self) -> bool {
310 self.signed_area2() > 0.0
311 }
312
313 pub fn bounds(&self) -> Aabb2 {
315 let mut bounds = Aabb2::empty();
316 for vertex in &self.vertices {
317 bounds.extend(*vertex);
318 }
319 bounds
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn empty_bounds_absorb_the_first_point() {
329 let mut bounds = Aabb2::default();
330 assert!(bounds.is_empty());
331 bounds.extend(Point2::new(3.0, -1.0));
332 assert_eq!(bounds.min, bounds.max);
333 assert!(!bounds.is_empty());
334 assert_eq!(bounds.area(), 0.0);
335 }
336
337 #[test]
338 fn bounds_intersect_on_touch_and_contain_their_boundary() {
339 let mut left = Aabb2::from_point(Point2::ZERO);
340 left.extend(Point2::new(1.0, 1.0));
341 let mut right = Aabb2::from_point(Point2::new(1.0, 0.0));
342 right.extend(Point2::new(2.0, 1.0));
343 assert!(left.intersects(&right), "touching boxes overlap");
344 assert!(left.contains(Point2::new(1.0, 1.0)), "boundary is inside");
345
346 let mut away = Aabb2::from_point(Point2::new(5.0, 5.0));
347 away.extend(Point2::new(6.0, 6.0));
348 assert!(!left.intersects(&away));
349 }
350
351 #[test]
352 fn triangle_signed_area_carries_winding_but_area_does_not() {
353 let ccw = Triangle2::new(Point2::ZERO, Point2::new(4.0, 0.0), Point2::new(0.0, 2.0));
354 assert_eq!(ccw.signed_area(), 4.0);
355 assert!(ccw.signed_area2() > 0.0);
356
357 let cw = Triangle2::new(ccw.a, ccw.c, ccw.b);
358 assert_eq!(cw.signed_area(), -4.0);
359 assert_eq!(cw.area(), ccw.area());
360 }
361
362 #[test]
363 fn collinear_triangle_has_zero_area() {
364 let degenerate = Triangle2::new(Point2::ZERO, Point2::new(1.0, 1.0), Point2::new(3.0, 3.0));
365 assert_eq!(degenerate.signed_area2(), 0.0);
366 assert_eq!(degenerate.area(), 0.0);
367 }
368
369 #[test]
370 fn rectangle_from_bounds_matches_the_box_it_came_from() {
371 let mut bounds = Aabb2::from_point(Point2::new(1.0, 2.0));
372 bounds.extend(Point2::new(4.0, 6.0));
373 let rectangle = Rectangle2::from_aabb(&bounds).expect("non-empty bounds");
374 assert_eq!(rectangle.area(), bounds.area());
375 assert_eq!(rectangle.center(), bounds.center());
376 assert_eq!(rectangle.corners()[2], bounds.max);
377 }
378
379 #[test]
380 fn rectangle_from_empty_bounds_is_refused_rather_than_zero_sized() {
381 assert!(Rectangle2::from_aabb(&Aabb2::empty()).is_none());
382 }
383
384 #[test]
385 fn rotated_rectangle_keeps_its_area() {
386 let diagonal = Vec2::new(1.0, 1.0);
389 let rectangle = Rectangle2::new(Point2::ZERO, diagonal, Vec2::new(-1.0, 1.0));
390 assert_eq!(rectangle.area(), 2.0);
391 }
392
393 #[test]
394 fn polygon_winding_flips_with_vertex_order() {
395 let square = Polygon2::new(vec![
396 Point2::ZERO,
397 Point2::new(2.0, 0.0),
398 Point2::new(2.0, 2.0),
399 Point2::new(0.0, 2.0),
400 ]);
401 assert_eq!(square.area(), 4.0);
402 assert!(square.is_counter_clockwise());
403
404 let mut reversed = square.vertices.clone();
405 reversed.reverse();
406 let reversed = Polygon2::new(reversed);
407 assert_eq!(reversed.area(), square.area());
408 assert!(!reversed.is_counter_clockwise());
409 assert_eq!(reversed.signed_area(), -square.signed_area());
410 }
411
412 #[test]
413 fn polygon_with_fewer_than_three_vertices_encloses_nothing() {
414 assert_eq!(Polygon2::new(Vec::new()).signed_area2(), 0.0);
415 assert_eq!(
416 Polygon2::new(vec![Point2::ZERO, Point2::new(1.0, 0.0)]).signed_area2(),
417 0.0
418 );
419 assert!(!Polygon2::new(Vec::new()).is_counter_clockwise());
420 }
421
422 #[test]
423 fn polygon_area_is_translation_invariant_far_from_the_origin() {
424 let offset = Vec2::splat(6_000_000.0);
427 let local = Polygon2::new(vec![
428 Point2::ZERO,
429 Point2::new(1.0, 0.0),
430 Point2::new(1.0, 1.0),
431 Point2::new(0.0, 1.0),
432 ]);
433 let far = Polygon2::new(local.vertices.iter().map(|v| *v + offset).collect());
434 assert_eq!(far.area(), local.area());
435 }
436
437 #[test]
438 fn polygon_bounds_cover_every_vertex() {
439 let polygon = Polygon2::new(vec![
440 Point2::new(-1.0, 4.0),
441 Point2::new(3.0, -2.0),
442 Point2::new(0.0, 0.0),
443 ]);
444 let bounds = polygon.bounds();
445 assert_eq!(bounds.min, Point2::new(-1.0, -2.0));
446 assert_eq!(bounds.max, Point2::new(3.0, 4.0));
447 for vertex in &polygon.vertices {
448 assert!(bounds.contains(*vertex));
449 }
450 }
451}