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