azalea_core/
aabb.rs

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