1use crate::{
2 direction::{Axis, Direction},
3 hit_result::BlockHitResult,
4 math::EPSILON,
5 position::{BlockPos, Vec3},
6};
7
8#[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}