1use std::marker::PhantomData;
4use std::ops::{Add, Div, Neg, Sub};
5
6use glam::{DVec3, IVec3};
7
8use crate::{BlockPos, axis::Axis};
9
10const fn ordered_pair(a: f64, b: f64) -> (f64, f64) {
11 if a <= b { (a, b) } else { (b, a) }
12}
13
14const fn ordered_pair_i32(a: i32, b: i32) -> (i32, i32) {
15 if a <= b { (a, b) } else { (b, a) }
16}
17
18pub trait Space {
20 const ZERO_SPAN_IS_EMPTY: bool;
22}
23
24#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct BlockLocal;
27
28impl Space for BlockLocal {
29 const ZERO_SPAN_IS_EMPTY: bool = true;
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct World;
35
36impl Space for World {
37 const ZERO_SPAN_IS_EMPTY: bool = true;
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub struct Structure;
43
44impl Space for Structure {
45 const ZERO_SPAN_IS_EMPTY: bool = false;
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub struct Aabb<T, I> {
54 min: T,
56 max: T,
58 p: PhantomData<I>,
59}
60
61pub type BlockLocalAabb = Aabb<DVec3, BlockLocal>;
63
64pub type WorldAabb = Aabb<DVec3, World>;
66
67pub type BoundingBox = Aabb<IVec3, Structure>;
69
70pub trait AabbVector: Copy + Add<Output = Self> + Sub<Output = Self> {
72 type Scalar: Copy
74 + PartialOrd
75 + Add<Output = Self::Scalar>
76 + Sub<Output = Self::Scalar>
77 + Div<Output = Self::Scalar>
78 + Neg<Output = Self::Scalar>
79 + From<u8>;
80
81 fn new(x: Self::Scalar, y: Self::Scalar, z: Self::Scalar) -> Self;
83
84 fn x(self) -> Self::Scalar;
86
87 fn y(self) -> Self::Scalar;
89
90 fn z(self) -> Self::Scalar;
92
93 #[must_use]
95 fn min(self, other: Self) -> Self;
96
97 #[must_use]
99 fn max(self, other: Self) -> Self;
100}
101
102impl AabbVector for DVec3 {
103 type Scalar = f64;
104
105 fn new(x: Self::Scalar, y: Self::Scalar, z: Self::Scalar) -> Self {
106 DVec3::new(x, y, z)
107 }
108
109 fn x(self) -> Self::Scalar {
110 self.x
111 }
112
113 fn y(self) -> Self::Scalar {
114 self.y
115 }
116
117 fn z(self) -> Self::Scalar {
118 self.z
119 }
120
121 fn min(self, other: Self) -> Self {
122 self.min(other)
123 }
124
125 fn max(self, other: Self) -> Self {
126 self.max(other)
127 }
128}
129
130impl AabbVector for IVec3 {
131 type Scalar = i32;
132
133 fn new(x: Self::Scalar, y: Self::Scalar, z: Self::Scalar) -> Self {
134 IVec3::new(x, y, z)
135 }
136
137 fn x(self) -> Self::Scalar {
138 self.x
139 }
140
141 fn y(self) -> Self::Scalar {
142 self.y
143 }
144
145 fn z(self) -> Self::Scalar {
146 self.z
147 }
148
149 fn min(self, other: Self) -> Self {
150 self.min(other)
151 }
152
153 fn max(self, other: Self) -> Self {
154 self.max(other)
155 }
156}
157
158impl<T: AabbVector, I> Aabb<T, I> {
159 #[must_use]
161 pub fn min(&self, axis: Axis) -> T::Scalar {
162 match axis {
163 Axis::X => self.min.x(),
164 Axis::Y => self.min.y(),
165 Axis::Z => self.min.z(),
166 }
167 }
168
169 #[must_use]
171 pub fn max(&self, axis: Axis) -> T::Scalar {
172 match axis {
173 Axis::X => self.max.x(),
174 Axis::Y => self.max.y(),
175 Axis::Z => self.max.z(),
176 }
177 }
178
179 #[must_use]
181 pub fn from_min_max(min: T, max: T) -> Self {
182 Self {
183 min: min.min(max),
184 max: min.max(max),
185 p: PhantomData,
186 }
187 }
188
189 #[must_use]
191 pub fn translate(self, delta: T) -> Self {
192 Self {
193 min: self.min + delta,
194 max: self.max + delta,
195 p: PhantomData,
196 }
197 }
198
199 #[must_use]
201 pub fn inflate(self, amount: T::Scalar) -> Self {
202 self.inflate_xyz(amount, amount, amount)
203 }
204
205 #[must_use]
207 pub fn inflate_xyz(self, x: T::Scalar, y: T::Scalar, z: T::Scalar) -> Self {
208 let delta = T::new(x, y, z);
209 Self::from_min_max(self.min - delta, self.max + delta)
210 }
211
212 #[must_use]
214 pub fn encapsulating(a: &Self, b: &Self) -> Self {
215 Self {
216 min: a.min.min(b.min),
217 max: a.max.max(b.max),
218 p: PhantomData,
219 }
220 }
221}
222
223impl<I> Aabb<DVec3, I> {
224 #[must_use]
226 pub const fn min_corner(&self) -> DVec3 {
227 self.min
228 }
229
230 #[must_use]
232 pub const fn max_corner(&self) -> DVec3 {
233 self.max
234 }
235
236 #[must_use]
238 pub const fn min_x(&self) -> f64 {
239 self.min.x
240 }
241
242 #[must_use]
244 pub const fn min_y(&self) -> f64 {
245 self.min.y
246 }
247
248 #[must_use]
250 pub const fn min_z(&self) -> f64 {
251 self.min.z
252 }
253
254 #[must_use]
256 pub const fn max_x(&self) -> f64 {
257 self.max.x
258 }
259
260 #[must_use]
262 pub const fn max_y(&self) -> f64 {
263 self.max.y
264 }
265
266 #[must_use]
268 pub const fn max_z(&self) -> f64 {
269 self.max.z
270 }
271
272 #[must_use]
276 pub fn distance_to_sqr(self, point: DVec3) -> f64 {
277 let dx = f64::max(f64::max(self.min.x - point.x, point.x - self.max.x), 0.0);
278 let dy = f64::max(f64::max(self.min.y - point.y, point.y - self.max.y), 0.0);
279 let dz = f64::max(f64::max(self.min.z - point.z, point.z - self.max.z), 0.0);
280 dx * dx + dy * dy + dz * dz
281 }
282
283 #[must_use]
287 pub const fn closest_point_to(self, point: DVec3) -> DVec3 {
288 DVec3::new(
289 point.x.clamp(self.min.x, self.max.x),
290 point.y.clamp(self.min.y, self.max.y),
291 point.z.clamp(self.min.z, self.max.z),
292 )
293 }
294}
295
296impl<I> Aabb<IVec3, I> {
297 #[must_use]
299 #[inline]
300 pub const fn min_corner(&self) -> IVec3 {
301 self.min
302 }
303
304 #[must_use]
306 #[inline]
307 pub const fn max_corner(&self) -> IVec3 {
308 self.max
309 }
310
311 #[must_use]
313 #[inline]
314 pub const fn min_x(&self) -> i32 {
315 self.min.x
316 }
317
318 #[must_use]
320 #[inline]
321 pub const fn min_y(&self) -> i32 {
322 self.min.y
323 }
324
325 #[must_use]
327 #[inline]
328 pub const fn min_z(&self) -> i32 {
329 self.min.z
330 }
331
332 #[must_use]
334 #[inline]
335 pub const fn max_x(&self) -> i32 {
336 self.max.x
337 }
338
339 #[must_use]
341 #[inline]
342 pub const fn max_y(&self) -> i32 {
343 self.max.y
344 }
345
346 #[must_use]
348 #[inline]
349 pub const fn max_z(&self) -> i32 {
350 self.max.z
351 }
352}
353
354impl<T: AabbVector, I> Aabb<T, I> {
355 #[must_use]
357 pub fn deflate(self, amount: T::Scalar) -> Self {
358 self.inflate(-amount)
359 }
360}
361
362impl<T: AabbVector, I: Space> Aabb<T, I> {
363 #[inline]
364 fn axis_overlaps(min1: T::Scalar, max1: T::Scalar, min2: T::Scalar, max2: T::Scalar) -> bool {
365 if I::ZERO_SPAN_IS_EMPTY {
366 min1 < max2 && max1 > min2
367 } else {
368 min1 <= max2 && max1 >= min2
369 }
370 }
371
372 #[inline]
373 fn axis_contains(min: T::Scalar, max: T::Scalar, v: T::Scalar) -> bool {
374 if I::ZERO_SPAN_IS_EMPTY {
375 v >= min && v < max
376 } else {
377 v >= min && v <= max
378 }
379 }
380
381 pub fn is_empty(&self) -> bool {
383 if I::ZERO_SPAN_IS_EMPTY {
384 self.min.x() >= self.max.x()
385 || self.min.y() >= self.max.y()
386 || self.min.z() >= self.max.z()
387 } else {
388 self.min.x() > self.max.x()
389 || self.min.y() > self.max.y()
390 || self.min.z() > self.max.z()
391 }
392 }
393
394 #[must_use]
396 pub fn intersects(self, other: Self) -> bool {
397 self.intersects_bounds(other.min, other.max)
398 }
399
400 #[must_use]
402 pub fn intersects_bounds(self, min: T, max: T) -> bool {
403 Self::axis_overlaps(self.min.x(), self.max.x(), min.x(), max.x())
404 && Self::axis_overlaps(self.min.y(), self.max.y(), min.y(), max.y())
405 && Self::axis_overlaps(self.min.z(), self.max.z(), min.z(), max.z())
406 }
407
408 #[must_use]
410 pub fn intersects_xz(
411 self,
412 min_x: T::Scalar,
413 min_z: T::Scalar,
414 max_x: T::Scalar,
415 max_z: T::Scalar,
416 ) -> bool {
417 Self::axis_overlaps(self.min.x(), self.max.x(), min_x, max_x)
418 && Self::axis_overlaps(self.min.z(), self.max.z(), min_z, max_z)
419 }
420
421 #[must_use]
423 pub fn contains(self, pos: T) -> bool {
424 self.contains_xyz(pos.x(), pos.y(), pos.z())
425 }
426
427 #[must_use]
429 pub fn contains_xyz(self, x: T::Scalar, y: T::Scalar, z: T::Scalar) -> bool {
430 Self::axis_contains(self.min.x(), self.max.x(), x)
431 && Self::axis_contains(self.min.y(), self.max.y(), y)
432 && Self::axis_contains(self.min.z(), self.max.z(), z)
433 }
434}
435
436impl<T: AabbVector, I: Space> Aabb<T, I> {
437 #[inline]
438 fn span(raw: T::Scalar) -> T::Scalar {
439 if I::ZERO_SPAN_IS_EMPTY {
440 raw
441 } else {
442 raw + T::Scalar::from(1u8)
443 }
444 }
445
446 #[must_use]
448 pub fn width(&self) -> T::Scalar {
449 Self::span(self.max.x() - self.min.x())
450 }
451
452 #[must_use]
454 pub fn height(&self) -> T::Scalar {
455 Self::span(self.max.y() - self.min.y())
456 }
457
458 #[must_use]
460 pub fn depth(&self) -> T::Scalar {
461 Self::span(self.max.z() - self.min.z())
462 }
463
464 #[must_use]
466 pub fn center(&self) -> T {
467 let two = T::Scalar::from(2u8);
468 T::new(
469 self.min.x() + Self::span(self.max.x() - self.min.x()) / two,
470 self.min.y() + Self::span(self.max.y() - self.min.y()) / two,
471 self.min.z() + Self::span(self.max.z() - self.min.z()) / two,
472 )
473 }
474}
475
476impl<I: Space> Aabb<IVec3, I> {
477 #[must_use]
479 pub fn contains_blockpos(self, pos: BlockPos) -> bool {
480 self.contains(pos.0)
481 }
482}
483
484impl<I: Space> Aabb<DVec3, I> {
485 pub const FULL_BLOCK: Self = Self::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
487
488 pub const EMPTY: Self = Self::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
490
491 #[must_use]
493 pub const fn new(
494 min_x: f64,
495 min_y: f64,
496 min_z: f64,
497 max_x: f64,
498 max_y: f64,
499 max_z: f64,
500 ) -> Self {
501 let (min_x, max_x) = ordered_pair(min_x, max_x);
502 let (min_y, max_y) = ordered_pair(min_y, max_y);
503 let (min_z, max_z) = ordered_pair(min_z, max_z);
504 Self {
505 min: DVec3::new(min_x, min_y, min_z),
506 max: DVec3::new(max_x, max_y, max_z),
507 p: PhantomData,
508 }
509 }
510
511 #[must_use]
515 pub fn of_size(center: DVec3, size_x: f64, size_y: f64, size_z: f64) -> Self {
516 let half_x = size_x / 2.0;
517 let half_y = size_y / 2.0;
518 let half_z = size_z / 2.0;
519 Self::new(
520 center.x - half_x,
521 center.y - half_y,
522 center.z - half_z,
523 center.x + half_x,
524 center.y + half_y,
525 center.z + half_z,
526 )
527 }
528
529 #[must_use]
531 pub fn size(self) -> f64 {
532 (self.width() + self.height() + self.depth()) / 3.0
533 }
534}
535
536impl Aabb<DVec3, BlockLocal> {
537 #[must_use]
539 pub fn at_block(self, pos: BlockPos) -> Aabb<DVec3, World> {
540 let offset = DVec3::new(f64::from(pos.x()), f64::from(pos.y()), f64::from(pos.z()));
541 Aabb {
542 min: self.min + offset,
543 max: self.max + offset,
544 p: PhantomData,
545 }
546 }
547}
548
549impl Aabb<DVec3, World> {
550 #[must_use]
552 pub fn entity_box(x: f64, y: f64, z: f64, half_width: f64, height: f64) -> Self {
553 Self::new(
554 x - half_width,
555 y,
556 z - half_width,
557 x + half_width,
558 y + height,
559 z + half_width,
560 )
561 }
562
563 #[must_use]
565 pub fn expand_towards(self, delta: DVec3) -> Self {
566 Self {
567 min: self.min + delta.min(DVec3::ZERO),
568 max: self.max + delta.max(DVec3::ZERO),
569 p: PhantomData,
570 }
571 }
572
573 #[must_use]
575 pub fn intersects_block(self, pos: BlockPos) -> bool {
576 let min = DVec3::new(f64::from(pos.x()), f64::from(pos.y()), f64::from(pos.z()));
577 let max = min + DVec3::ONE;
578 self.intersects_bounds(min, max)
579 }
580}
581
582impl Aabb<IVec3, Structure> {
583 #[must_use]
585 pub const fn new(pos1: IVec3, pos2: IVec3) -> Self {
586 let (min_x, max_x) = ordered_pair_i32(pos1.x, pos2.x);
587 let (min_y, max_y) = ordered_pair_i32(pos1.y, pos2.y);
588 let (min_z, max_z) = ordered_pair_i32(pos1.z, pos2.z);
589 Self {
590 min: IVec3::new(min_x, min_y, min_z),
591 max: IVec3::new(max_x, max_y, max_z),
592 p: PhantomData,
593 }
594 }
595
596 #[must_use]
598 pub const fn from_corners(a: BlockPos, b: BlockPos) -> Self {
599 Self::new(a.0, b.0)
600 }
601
602 #[must_use]
606 pub fn distance_to_sqr(self, point: DVec3) -> f64 {
607 let min_x = f64::from(self.min_x());
608 let min_y = f64::from(self.min_y());
609 let min_z = f64::from(self.min_z());
610 let max_x = f64::from(self.max_x());
611 let max_y = f64::from(self.max_y());
612 let max_z = f64::from(self.max_z());
613
614 let dx = f64::max(f64::max(min_x - point.x, point.x - max_x), 0.0);
615 let dy = f64::max(f64::max(min_y - point.y, point.y - max_y), 0.0);
616 let dz = f64::max(f64::max(min_z - point.z, point.z - max_z), 0.0);
617 dx * dx + dy * dy + dz * dz
618 }
619}
620
621#[cfg(test)]
622#[expect(
623 clippy::float_cmp,
624 reason = "geometry constructors use exact test values"
625)]
626mod tests {
627 use super::*;
628
629 #[test]
630 fn constructors_normalize_endpoints_like_vanilla() {
631 let aabb = WorldAabb::new(3.0, 4.0, 5.0, 1.0, 2.0, 0.0);
632 assert_eq!(aabb.min_x(), 1.0);
633 assert_eq!(aabb.min_y(), 2.0);
634 assert_eq!(aabb.min_z(), 0.0);
635 assert_eq!(aabb.max_x(), 3.0);
636 assert_eq!(aabb.max_y(), 4.0);
637 assert_eq!(aabb.max_z(), 5.0);
638 }
639
640 #[test]
641 fn inflate_and_deflate_normalize_inverted_bounds() {
642 let aabb = WorldAabb::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0).deflate(0.75);
643 assert_eq!(aabb.min_corner(), DVec3::splat(0.25));
644 assert_eq!(aabb.max_corner(), DVec3::splat(0.75));
645
646 let bbox = BoundingBox::new(IVec3::ZERO, IVec3::splat(5)).inflate(-4);
647 assert_eq!(bbox.min_corner(), IVec3::splat(1));
648 assert_eq!(bbox.max_corner(), IVec3::splat(4));
649 }
650
651 #[test]
652 fn of_size_builds_vanilla_centered_box() {
653 let aabb = WorldAabb::of_size(DVec3::new(10.0, 64.0, -2.0), 2.0, 4.0, 6.0);
654
655 assert_eq!(aabb.min_corner(), DVec3::new(9.0, 62.0, -5.0));
656 assert_eq!(aabb.max_corner(), DVec3::new(11.0, 66.0, 1.0));
657 }
658
659 #[test]
660 fn block_local_aabb_translates_to_world_space() {
661 let local = BlockLocalAabb::new(0.0, 0.25, 0.0, 1.0, 0.75, 1.0);
662 let world = local.at_block(BlockPos::new(10, 64, -5));
663
664 assert_eq!(world.min_x(), 10.0);
665 assert_eq!(world.min_y(), 64.25);
666 assert_eq!(world.min_z(), -5.0);
667 assert_eq!(world.max_x(), 11.0);
668 assert_eq!(world.max_y(), 64.75);
669 assert_eq!(world.max_z(), -4.0);
670 }
671
672 #[test]
673 fn contains_uses_vanilla_exclusive_max_edge() {
674 let aabb = WorldAabb::new(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
675
676 assert!(aabb.contains_xyz(0.0, 0.5, 0.5));
677 assert!(aabb.contains_xyz(0.999, 0.5, 0.5));
678 assert!(!aabb.contains_xyz(1.0, 0.5, 0.5));
679 }
680
681 #[test]
682 fn world_aabb_distance_to_sqr_uses_nearest_surface_point() {
683 let aabb = WorldAabb::new(1.0, 2.0, 3.0, 4.0, 6.0, 8.0);
684
685 assert_eq!(aabb.distance_to_sqr(DVec3::new(2.0, 3.0, 4.0)), 0.0);
686 assert_eq!(aabb.distance_to_sqr(DVec3::new(0.0, 1.0, 1.0)), 6.0);
687 assert_eq!(aabb.distance_to_sqr(DVec3::new(5.0, 7.0, 9.0)), 3.0);
688 }
689
690 #[test]
691 fn closest_point_to_clamps_to_box_bounds() {
692 let aabb = WorldAabb::new(1.0, 2.0, 3.0, 4.0, 6.0, 8.0);
693
694 assert_eq!(
695 aabb.closest_point_to(DVec3::new(0.0, 4.0, 10.0)),
696 DVec3::new(1.0, 4.0, 8.0)
697 );
698 assert_eq!(
699 aabb.closest_point_to(DVec3::new(2.0, 3.0, 4.0)),
700 DVec3::new(2.0, 3.0, 4.0)
701 );
702 }
703
704 #[test]
705 fn expand_towards_covers_start_and_end() {
706 let aabb = WorldAabb::new(1.0, 1.0, 1.0, 2.0, 2.0, 2.0);
707 let swept = aabb.expand_towards(DVec3::new(-0.5, 1.5, 0.0));
708
709 assert_eq!(swept.min_x(), 0.5);
710 assert_eq!(swept.min_y(), 1.0);
711 assert_eq!(swept.min_z(), 1.0);
712 assert_eq!(swept.max_x(), 2.0);
713 assert_eq!(swept.max_y(), 3.5);
714 assert_eq!(swept.max_z(), 2.0);
715 }
716}