azalea_core/
aabb.rs

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