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)]
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}