1use core::cmp::Ordering;
2use core::fmt;
3use core::iter::FusedIterator;
4
5use euclid::{Point3D, Size3D, Vector3D};
6
7#[cfg(not(feature = "std"))]
9#[allow(unused_imports)]
10use num_traits::float::FloatCore as _;
11
12use crate::math::{
13 Axis, Cube, Face6, FreeCoordinate, FreePoint, FreeVector, GridAab, GridCoordinate, LineVertex,
14 Wireframe,
15};
16
17#[doc = include_str!("../serde-warning.md")]
23#[derive(Copy, Clone, PartialEq)]
25pub struct Aab {
26 lower_bounds: FreePoint,
31 upper_bounds: FreePoint,
32}
33
34impl Aab {
35 pub const ZERO: Aab = Aab {
37 lower_bounds: Point3D::new(0., 0., 0.),
38 upper_bounds: Point3D::new(0., 0., 0.),
39 };
40
41 #[inline]
43 #[track_caller]
44 pub fn new(
45 lx: FreeCoordinate,
46 hx: FreeCoordinate,
47 ly: FreeCoordinate,
48 hy: FreeCoordinate,
49 lz: FreeCoordinate,
50 hz: FreeCoordinate,
51 ) -> Self {
52 Self::from_lower_upper(Point3D::new(lx, ly, lz), Point3D::new(hx, hy, hz))
53 }
54
55 #[inline]
59 #[track_caller]
60 pub fn from_lower_upper(
61 lower_bounds: impl Into<FreePoint>,
62 upper_bounds: impl Into<FreePoint>,
63 ) -> Self {
64 let lower_bounds = lower_bounds.into();
65 let upper_bounds = upper_bounds.into();
66 match Self::checked_from_lower_upper(lower_bounds, upper_bounds) {
67 Some(aab) => aab,
68 None => panic!(
69 "invalid AAB points that are misordered or NaN: \
70 lower {lower_bounds:?} upper {upper_bounds:?}"
71 ),
72 }
73 }
74
75 pub(crate) fn checked_from_lower_upper(
80 lower_bounds: FreePoint,
81 upper_bounds: FreePoint,
82 ) -> Option<Self> {
83 if lower_bounds.x <= upper_bounds.x
84 && lower_bounds.y <= upper_bounds.y
85 && lower_bounds.z <= upper_bounds.z
86 {
87 Some(Self {
88 lower_bounds,
89 upper_bounds,
90 })
91 } else {
92 None
93 }
94 }
95
96 #[inline]
98 pub const fn lower_bounds_p(&self) -> FreePoint {
99 self.lower_bounds
100 }
101
102 #[inline]
104 pub const fn upper_bounds_p(&self) -> FreePoint {
105 self.upper_bounds
106 }
107
108 #[inline]
110 pub fn lower_bounds_v(&self) -> FreeVector {
111 self.lower_bounds.to_vector()
112 }
113
114 #[inline]
116 pub fn upper_bounds_v(&self) -> FreeVector {
117 self.upper_bounds.to_vector()
118 }
119
120 #[inline]
126 pub fn face_coordinate(&self, face: Face6) -> FreeCoordinate {
127 match face {
128 Face6::NX => -self.lower_bounds.x,
129 Face6::NY => -self.lower_bounds.y,
130 Face6::NZ => -self.lower_bounds.z,
131 Face6::PX => self.upper_bounds.x,
132 Face6::PY => self.upper_bounds.y,
133 Face6::PZ => self.upper_bounds.z,
134 }
135 }
136
137 #[inline]
147 pub fn size(&self) -> Size3D<FreeCoordinate, Cube> {
148 Size3D::from(self.upper_bounds - self.lower_bounds)
149 }
150
151 #[inline]
161 pub fn center(&self) -> FreePoint {
162 (self.lower_bounds + self.upper_bounds.to_vector()) * 0.5
163 }
164
165 #[doc(hidden)]
168 #[inline]
169 pub fn corner_points(
170 self,
171 ) -> impl DoubleEndedIterator<Item = FreePoint> + ExactSizeIterator + FusedIterator {
172 let l = self.lower_bounds;
173 let u = self.upper_bounds;
174 (0..8).map(move |i| {
175 Point3D::new(
176 if i & 1 == 0 { l.x } else { u.x },
177 if i & 2 == 0 { l.y } else { u.y },
178 if i & 4 == 0 { l.z } else { u.z },
179 )
180 })
181 }
182
183 #[inline]
187 pub fn contains(&self, point: FreePoint) -> bool {
188 for axis in Axis::ALL {
191 if !(self.lower_bounds[axis] <= point[axis] && point[axis] <= self.upper_bounds[axis]) {
192 return false;
193 }
194 }
195 true
196 }
197
198 #[inline]
202 pub fn intersects(&self, other: Aab) -> bool {
203 for axis in Axis::ALL {
204 let intersection_min = self.lower_bounds[axis].max(other.lower_bounds[axis]);
205 let intersection_max = self.upper_bounds[axis].min(other.upper_bounds[axis]);
206 match intersection_min.partial_cmp(&intersection_max) {
207 Some(Ordering::Less | Ordering::Equal) => {}
208 _ => return false,
209 }
210 }
211 true
212 }
213
214 #[inline]
217 #[must_use]
218 pub fn union(self, other: Self) -> Self {
219 Self {
220 lower_bounds: self.lower_bounds.min(other.lower_bounds),
222 upper_bounds: self.upper_bounds.max(other.upper_bounds),
223 }
224 }
225
226 #[inline]
242 #[must_use]
243 pub fn union_point(self, point: FreePoint) -> Self {
244 Self {
245 lower_bounds: point.min(self.lower_bounds),
247 upper_bounds: point.max(self.upper_bounds),
248 }
249 }
250
251 #[allow(clippy::missing_inline_in_public_items)]
254 pub fn random_point(self, rng: &mut impl rand::Rng) -> FreePoint {
255 FreePoint::new(
256 rng.gen_range(self.lower_bounds.x..=self.upper_bounds.x),
257 rng.gen_range(self.lower_bounds.y..=self.upper_bounds.y),
258 rng.gen_range(self.lower_bounds.z..=self.upper_bounds.z),
259 )
260 }
261
262 #[inline]
266 #[must_use]
267 #[track_caller] pub fn translate(self, offset: FreeVector) -> Self {
269 Self::from_lower_upper(self.lower_bounds + offset, self.upper_bounds + offset)
270 }
271
272 #[must_use]
274 #[inline]
275 pub fn scale(self, scalar: FreeCoordinate) -> Self {
276 Self::from_lower_upper(self.lower_bounds * scalar, self.upper_bounds * scalar)
277 }
278
279 #[must_use]
295 #[inline]
296 pub fn expand(self, distance: FreeCoordinate) -> Self {
297 let distance_vec = Vector3D::splat(distance);
300 match Self::checked_from_lower_upper(
301 self.lower_bounds - distance_vec,
302 self.upper_bounds + distance_vec,
303 ) {
304 Some(aab) => aab,
305 None => {
306 let center = self.center();
307 Aab::from_lower_upper(center, center)
308 }
309 }
310 }
311
312 #[inline]
313 #[doc(hidden)] pub fn leading_corner(&self, direction: FreeVector) -> FreeVector {
315 let mut leading_corner = Vector3D::zero();
316 for axis in Axis::ALL {
317 if direction[axis] >= 0.0 {
318 leading_corner[axis] = self.upper_bounds[axis];
319 } else {
320 leading_corner[axis] = self.lower_bounds[axis];
321 }
322 }
323 leading_corner
324 }
325
326 #[inline]
370 pub fn round_up_to_grid(self) -> GridAab {
371 GridAab::from_lower_upper(
372 self.lower_bounds.map(|c| c.floor() as GridCoordinate),
373 self.upper_bounds.map(|c| c.ceil() as GridCoordinate),
374 )
375 }
376}
377
378impl fmt::Debug for Aab {
379 #[allow(clippy::missing_inline_in_public_items)]
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 let Aab {
382 lower_bounds: l,
383 upper_bounds: u,
384 } = *self;
385 f.debug_tuple("Aab")
386 .field(&(l.x..=u.x))
387 .field(&(l.y..=u.y))
388 .field(&(l.z..=u.z))
389 .finish()
390 }
391}
392
393impl Eq for Aab {}
396
397impl Wireframe for Aab {
398 #[inline(never)]
399 fn wireframe_points<E>(&self, output: &mut E)
400 where
401 E: Extend<LineVertex>,
402 {
403 let mut vertices = [LineVertex::from(FreePoint::origin()); 24];
404 let l = self.lower_bounds_p();
405 let u = self.upper_bounds_p();
406 for axis_0 in Axis::ALL {
407 let vbase = usize::from(axis_0) * 8;
408 let axis_1 = axis_0.increment();
409 let axis_2 = axis_0.decrement();
410 let mut p = l;
411 vertices[vbase].position = p;
413 p[axis_0] = u[axis_0];
414 vertices[vbase + 1].position = p;
415 vertices[vbase + 2].position = p;
416 p[axis_1] = u[axis_1];
417 vertices[vbase + 3].position = p;
418 vertices[vbase + 4].position = p;
419 p[axis_2] = u[axis_2];
420 vertices[vbase + 5].position = p;
421 p[axis_2] = l[axis_2];
423 vertices[vbase + 6].position = p;
424 p[axis_0] = l[axis_0];
425 vertices[vbase + 7].position = p;
426 }
427 output.extend(vertices);
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use alloc::vec::Vec;
435 use euclid::point3;
436
437 #[test]
438 fn new_wrong_order() {
439 assert_eq!(
440 Aab::checked_from_lower_upper(point3(2., 1., 1.), point3(1., 2., 2.)),
441 None
442 );
443 assert_eq!(
444 Aab::checked_from_lower_upper(point3(1., 2., 1.), point3(2., 1., 2.)),
445 None
446 );
447 assert_eq!(
448 Aab::checked_from_lower_upper(point3(1., 1., 2.), point3(2., 2., 1.)),
449 None
450 );
451 }
452
453 #[test]
454 fn new_nan() {
455 assert_eq!(
456 Aab::checked_from_lower_upper(point3(0., 0., 0.), point3(1., 1., f64::NAN)),
457 None
458 );
459 }
460
461 #[test]
462 #[should_panic = "invalid AAB points that are misordered or NaN: lower (0.0, 0.0, 0.0) upper (1.0, 1.0, NaN)"]
463 fn new_panic_message() {
464 Aab::from_lower_upper([0., 0., 0.], [1., 1., f64::NAN]);
465 }
466
467 #[test]
468 fn debug() {
471 let aab = Aab::new(1.0000001, 2.0, 3.0, 4.0, 5.0, 6.0);
472 assert_eq!(
473 format!("{aab:?}"),
474 "Aab(1.0000001..=2.0, 3.0..=4.0, 5.0..=6.0)"
475 );
476 assert_eq!(
477 format!("{aab:#?}\n"),
478 indoc::indoc! {"
479 Aab(
480 1.0000001..=2.0,
481 3.0..=4.0,
482 5.0..=6.0,
483 )
484 "}
485 );
486 }
487
488 #[test]
489 fn union_point_nan() {
490 assert_eq!(
491 Aab::ZERO.union_point(FreePoint::new(2., f64::NAN, 10.)),
492 Aab::from_lower_upper([0., 0., 0.], [2., 0., 10.]),
493 );
494 }
495
496 #[test]
497 fn expand_nan() {
498 let aab = Aab::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
499 assert_eq!(
500 aab.expand(FreeCoordinate::NAN),
501 Aab::from_lower_upper(aab.center(), aab.center()),
502 );
503 }
504
505 #[test]
506 fn expand_negative_failure() {
507 let aab = Aab::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
508 assert_eq!(
509 aab.expand(-10.0),
510 Aab::from_lower_upper(aab.center(), aab.center()),
511 );
512 }
513
514 #[test]
515 fn expand_negative_success() {
516 let aab = Aab::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
517 assert_eq!(
518 aab.expand(-0.25),
519 Aab::new(1.25, 1.75, 3.25, 3.75, 5.25, 5.75),
520 );
521 }
522
523 #[test]
524 fn expand_inf() {
525 const INF: FreeCoordinate = FreeCoordinate::INFINITY;
526 assert_eq!(
527 Aab::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0).expand(INF),
528 Aab::new(-INF, INF, -INF, INF, -INF, INF),
529 );
530 }
531
532 #[test]
533 fn wireframe_smoke_test() {
534 let aab: Aab = Cube::new(1, 2, 3).aab();
535 let mut wireframe: Vec<LineVertex> = Vec::new();
536 aab.wireframe_points(&mut wireframe);
537 for LineVertex { position, color } in wireframe {
538 assert!(color.is_none());
539 assert!(position.x == 1.0 || position.x == 2.0);
540 assert!(position.y == 2.0 || position.y == 3.0);
541 assert!(position.z == 3.0 || position.z == 4.0);
542 }
543 }
544
545 #[test]
546 fn leading_corner_consistency() {
547 let aab = Aab::new(-1.1, 2.2, -3.3, 4.4, -5.5, 6.6);
548 for direction in (-1..=1)
549 .zip(-1..=1)
550 .zip(-1..=1)
551 .map(|((x, y), z)| Vector3D::new(x, y, z).map(FreeCoordinate::from))
552 {
553 let leading_corner = aab.leading_corner(direction);
554
555 for axis in Axis::ALL {
556 assert_eq!(leading_corner[axis].signum(), direction[axis].signum());
559 }
560 }
561 }
562
563 #[test]
566 fn corner_points() {
567 assert_eq!(
568 Cube::new(10, 20, 30)
569 .aab()
570 .corner_points()
571 .collect::<Vec<_>>(),
572 vec![
573 point3(10., 20., 30.),
574 point3(11., 20., 30.),
575 point3(10., 21., 30.),
576 point3(11., 21., 30.),
577 point3(10., 20., 31.),
578 point3(11., 20., 31.),
579 point3(10., 21., 31.),
580 point3(11., 21., 31.),
581 ],
582 );
583 }
584}