azalea_core/
aabb.rs

1use crate::{
2    direction::{Axis, Direction},
3    hit_result::BlockHitResult,
4    math::EPSILON,
5    position::{BlockPos, Vec3},
6};
7
8/// An axis-aligned bounding box.
9///
10/// In other words, a rectangular prism with a starting and ending point.
11#[derive(Copy, Clone, Debug, PartialEq, Default)]
12pub struct Aabb {
13    pub min: Vec3,
14    pub max: Vec3,
15}
16
17pub struct ClipPointOpts<'a> {
18    pub t: &'a mut f64,
19    pub approach_dir: Option<Direction>,
20    pub delta: Vec3,
21    pub begin: f64,
22
23    pub min_x: f64,
24    pub min_z: f64,
25    pub max_x: f64,
26    pub max_z: f64,
27
28    pub result_dir: Direction,
29    pub start: Vec3,
30}
31
32impl Aabb {
33    pub fn contract(&self, amount: Vec3) -> Aabb {
34        let mut min = self.min;
35        let mut max = self.max;
36
37        if amount.x < 0.0 {
38            min.x -= amount.x;
39        } else if amount.x > 0.0 {
40            max.x -= amount.x;
41        }
42
43        if amount.y < 0.0 {
44            min.y -= amount.y;
45        } else if amount.y > 0.0 {
46            max.y -= amount.y;
47        }
48
49        if amount.z < 0.0 {
50            min.z -= amount.z;
51        } else if amount.z > 0.0 {
52            max.z -= amount.z;
53        }
54
55        Aabb { min, max }
56    }
57
58    pub fn expand_towards(&self, other: Vec3) -> Aabb {
59        let mut min = self.min;
60        let mut max = self.max;
61
62        if other.x < 0.0 {
63            min.x += other.x;
64        } else if other.x > 0.0 {
65            max.x += other.x;
66        }
67
68        if other.y < 0.0 {
69            min.y += other.y;
70        } else if other.y > 0.0 {
71            max.y += other.y;
72        }
73
74        if other.z < 0.0 {
75            min.z += other.z;
76        } else if other.z > 0.0 {
77            max.z += other.z;
78        }
79
80        Aabb { min, max }
81    }
82
83    pub fn inflate(&self, amount: Vec3) -> Aabb {
84        let min = self.min - amount;
85        let max = self.max + amount;
86
87        Aabb { min, max }
88    }
89    pub fn inflate_all(&self, amount: f64) -> Aabb {
90        self.inflate(Vec3::new(amount, amount, amount))
91    }
92
93    pub fn intersect(&self, other: &Aabb) -> Aabb {
94        let min = self.min.max(other.min);
95        let max = self.max.min(other.max);
96        Aabb { min, max }
97    }
98
99    pub fn minmax(&self, other: &Aabb) -> Aabb {
100        let min = self.min.min(other.min);
101        let max = self.max.max(other.max);
102        Aabb { min, max }
103    }
104
105    pub fn move_relative(&self, delta: Vec3) -> Aabb {
106        Aabb {
107            min: self.min + delta,
108            max: self.max + delta,
109        }
110    }
111
112    pub fn intersects_aabb(&self, other: &Aabb) -> bool {
113        self.min.x < other.max.x
114            && self.max.x > other.min.x
115            && self.min.y < other.max.y
116            && self.max.y > other.min.y
117            && self.min.z < other.max.z
118            && self.max.z > other.min.z
119    }
120    pub fn intersects_vec3(&self, corner1: Vec3, corner2: Vec3) -> bool {
121        let min = corner1.min(corner2);
122        let max = corner1.max(corner2);
123        self.intersects_aabb(&Aabb { min, max })
124    }
125
126    pub fn contains(&self, point: Vec3) -> bool {
127        point.x >= self.min.x
128            && point.x < self.max.x
129            && point.y >= self.min.y
130            && point.y < self.max.y
131            && point.z >= self.min.z
132            && point.z < self.max.z
133    }
134
135    pub fn size(&self) -> f64 {
136        let x = self.get_size(Axis::X);
137        let y = self.get_size(Axis::Y);
138        let z = self.get_size(Axis::Z);
139        (x + y + z) / 3.0
140    }
141
142    #[inline]
143    pub fn get_size(&self, axis: Axis) -> f64 {
144        axis.choose(
145            self.max.x - self.min.x,
146            self.max.y - self.min.y,
147            self.max.z - self.min.z,
148        )
149    }
150
151    pub fn deflate(&self, amount: Vec3) -> Aabb {
152        self.inflate(Vec3::new(-amount.x, -amount.y, -amount.z))
153    }
154    pub fn deflate_all(&self, amount: f64) -> Aabb {
155        self.deflate(Vec3::new(amount, amount, amount))
156    }
157
158    pub fn clip(&self, min: Vec3, max: Vec3) -> Option<Vec3> {
159        let mut t = 1.0;
160        let delta = max - min;
161        let _dir = Self::get_direction_aabb(self, min, &mut t, None, delta)?;
162        Some(min + (delta * t))
163    }
164
165    pub fn clip_with_from_and_to(min: Vec3, max: Vec3, from: Vec3, to: Vec3) -> Option<Vec3> {
166        let mut t = 1.0;
167        let delta = to - from;
168        let _dir = Self::get_direction(min, max, from, &mut t, None, delta)?;
169        Some(from + (delta * t))
170    }
171
172    pub fn clip_iterable(
173        boxes: &[Aabb],
174        from: Vec3,
175        to: Vec3,
176        pos: BlockPos,
177    ) -> Option<BlockHitResult> {
178        let mut t = 1.0;
179        let mut dir = None;
180        let delta = to - from;
181
182        for aabb in boxes {
183            dir = Self::get_direction_aabb(
184                &aabb.move_relative(pos.to_vec3_floored()),
185                from,
186                &mut t,
187                dir,
188                delta,
189            );
190        }
191        let dir = dir?;
192        Some(BlockHitResult {
193            location: from + (delta * t),
194            direction: dir,
195            block_pos: pos,
196            inside: false,
197            miss: false,
198            world_border: false,
199        })
200    }
201
202    fn get_direction_aabb(
203        &self,
204        from: Vec3,
205        t: &mut f64,
206        dir: Option<Direction>,
207        delta: Vec3,
208    ) -> Option<Direction> {
209        Aabb::get_direction(self.min, self.max, from, t, dir, delta)
210    }
211
212    fn get_direction(
213        min: Vec3,
214        max: Vec3,
215        from: Vec3,
216        t: &mut f64,
217        mut dir: Option<Direction>,
218        delta: Vec3,
219    ) -> Option<Direction> {
220        if delta.x > EPSILON {
221            dir = Self::clip_point(ClipPointOpts {
222                t,
223                approach_dir: dir,
224                delta,
225
226                begin: min.x,
227                min_x: min.y,
228                max_x: max.y,
229                min_z: min.z,
230                max_z: max.z,
231
232                result_dir: Direction::West,
233                start: from,
234            });
235        } else if delta.x < -EPSILON {
236            dir = Self::clip_point(ClipPointOpts {
237                t,
238                approach_dir: dir,
239                delta,
240
241                begin: max.x,
242                min_x: min.y,
243                max_x: max.y,
244                min_z: min.z,
245                max_z: max.z,
246
247                result_dir: Direction::East,
248                start: from,
249            });
250        }
251
252        if delta.y > EPSILON {
253            dir = Self::clip_point(ClipPointOpts {
254                t,
255                approach_dir: dir,
256                delta: Vec3 {
257                    x: delta.y,
258                    y: delta.z,
259                    z: delta.x,
260                },
261                begin: min.y,
262                min_x: min.z,
263                max_x: max.z,
264                min_z: min.x,
265                max_z: max.x,
266                result_dir: Direction::Down,
267                start: Vec3 {
268                    x: from.y,
269                    y: from.z,
270                    z: from.x,
271                },
272            });
273        } else if delta.y < -EPSILON {
274            dir = Self::clip_point(ClipPointOpts {
275                t,
276                approach_dir: dir,
277                delta: Vec3 {
278                    x: delta.y,
279                    y: delta.z,
280                    z: delta.x,
281                },
282                begin: max.y,
283                min_x: min.z,
284                max_x: max.z,
285                min_z: min.x,
286                max_z: max.x,
287                result_dir: Direction::Up,
288                start: Vec3 {
289                    x: from.y,
290                    y: from.z,
291                    z: from.x,
292                },
293            });
294        }
295
296        if delta.z > EPSILON {
297            dir = Self::clip_point(ClipPointOpts {
298                t,
299                approach_dir: dir,
300                delta: Vec3 {
301                    x: delta.z,
302                    y: delta.x,
303                    z: delta.y,
304                },
305                begin: min.z,
306                min_x: min.x,
307                max_x: max.x,
308                min_z: min.y,
309                max_z: max.y,
310                result_dir: Direction::North,
311                start: Vec3 {
312                    x: from.z,
313                    y: from.x,
314                    z: from.y,
315                },
316            });
317        } else if delta.z < -EPSILON {
318            dir = Self::clip_point(ClipPointOpts {
319                t,
320                approach_dir: dir,
321                delta: Vec3 {
322                    x: delta.z,
323                    y: delta.x,
324                    z: delta.y,
325                },
326                begin: max.z,
327                min_x: min.x,
328                max_x: max.x,
329                min_z: min.y,
330                max_z: max.y,
331                result_dir: Direction::South,
332                start: Vec3 {
333                    x: from.z,
334                    y: from.x,
335                    z: from.y,
336                },
337            });
338        }
339
340        dir
341    }
342
343    fn clip_point(opts: ClipPointOpts) -> Option<Direction> {
344        let d = (opts.begin - opts.start.x) / opts.delta.x;
345        let e = opts.start.y + d * opts.delta.y;
346        let f = opts.start.z + d * opts.delta.z;
347        if 0.0 < d
348            && d < *opts.t
349            && opts.min_x - EPSILON < e
350            && e < opts.max_x + EPSILON
351            && opts.min_z - EPSILON < f
352            && f < opts.max_z + EPSILON
353        {
354            *opts.t = d;
355            Some(opts.result_dir)
356        } else {
357            opts.approach_dir
358        }
359    }
360
361    pub fn has_nan(&self) -> bool {
362        self.min.x.is_nan()
363            || self.min.y.is_nan()
364            || self.min.z.is_nan()
365            || self.max.x.is_nan()
366            || self.max.y.is_nan()
367            || self.max.z.is_nan()
368    }
369
370    pub fn get_center(&self) -> Vec3 {
371        Vec3::new(
372            (self.min.x + self.max.x) / 2.0,
373            (self.min.y + self.max.y) / 2.0,
374            (self.min.z + self.max.z) / 2.0,
375        )
376    }
377
378    pub fn of_size(center: Vec3, dx: f64, dy: f64, dz: f64) -> Aabb {
379        Aabb {
380            min: Vec3::new(
381                center.x - dx / 2.0,
382                center.y - dy / 2.0,
383                center.z - dz / 2.0,
384            ),
385            max: Vec3::new(
386                center.x + dx / 2.0,
387                center.y + dy / 2.0,
388                center.z + dz / 2.0,
389            ),
390        }
391    }
392
393    pub fn max(&self, axis: &Axis) -> f64 {
394        axis.choose(self.max.x, self.max.y, self.max.z)
395    }
396    pub fn min(&self, axis: &Axis) -> f64 {
397        axis.choose(self.min.x, self.min.y, self.min.z)
398    }
399
400    pub fn collided_along_vector(&self, vector: Vec3, boxes: &[Aabb]) -> bool {
401        let center = self.get_center();
402        let new_center = center + vector;
403
404        for aabb in boxes {
405            let inflated = aabb.inflate(Vec3::new(
406                self.get_size(Axis::X) * 0.5,
407                self.get_size(Axis::Y) * 0.5,
408                self.get_size(Axis::Z) * 0.5,
409            ));
410            if inflated.contains(new_center) || inflated.contains(center) {
411                return true;
412            }
413
414            if inflated.clip(center, new_center).is_some() {
415                return true;
416            }
417        }
418
419        false
420    }
421}
422
423impl BlockPos {
424    pub fn between_closed_aabb(aabb: &Aabb) -> Vec<BlockPos> {
425        BlockPos::between_closed(BlockPos::from(aabb.min), BlockPos::from(aabb.max))
426    }
427
428    pub fn between_closed(min: BlockPos, max: BlockPos) -> Vec<BlockPos> {
429        assert!(min.x <= max.x);
430        assert!(min.y <= max.y);
431        assert!(min.z <= max.z);
432
433        let length_x = max.x - min.x + 1;
434        let length_y = max.y - min.y + 1;
435        let length_z = max.z - min.z + 1;
436        let volume = length_x * length_y * length_z;
437
438        let mut result = Vec::with_capacity(volume as usize);
439        for index in 0..volume {
440            let index_x = index % length_x;
441            let remaining_after_x = index / length_x;
442            let index_y = remaining_after_x % length_y;
443            let index_z = remaining_after_x / length_y;
444            result.push(BlockPos::new(
445                min.x + index_x,
446                min.y + index_y,
447                min.z + index_z,
448            ));
449        }
450
451        result
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_aabb_clip_iterable() {
461        assert_ne!(
462            Aabb::clip_iterable(
463                &[Aabb {
464                    min: Vec3::new(0., 0., 0.),
465                    max: Vec3::new(1., 1., 1.),
466                }],
467                Vec3::new(-1., -1., -1.),
468                Vec3::new(1., 1., 1.),
469                BlockPos::new(0, 0, 0),
470            ),
471            None
472        );
473    }
474}