use crate::{ block_hit_result::BlockHitResult, direction::{Axis, Direction}, math::EPSILON, position::{BlockPos, Vec3}, }; /// A rectangular prism with a starting and ending point. #[derive(Copy, Clone, Debug, PartialEq, Default)] pub struct AABB { pub min: Vec3, pub max: Vec3, } pub struct ClipPointOpts<'a> { pub t: &'a mut f64, pub approach_dir: Option, pub delta: &'a Vec3, pub begin: f64, pub min_x: f64, pub min_z: f64, pub max_x: f64, pub max_z: f64, pub result_dir: Direction, pub start: &'a Vec3, } impl AABB { pub fn contract(&self, x: f64, y: f64, z: f64) -> AABB { let mut min = self.min; let mut max = self.max; if x < 0.0 { min.x -= x; } else if x > 0.0 { max.x -= x; } if y < 0.0 { min.y -= y; } else if y > 0.0 { max.y -= y; } if z < 0.0 { min.z -= z; } else if z > 0.0 { max.z -= z; } AABB { min, max } } pub fn expand_towards(&self, other: &Vec3) -> AABB { let mut min_x = self.min.x; let mut min_y = self.min.y; let mut min_z = self.min.z; let mut max_x = self.max.x; let mut max_y = self.max.y; let mut max_z = self.max.z; if other.x < 0.0 { min_x += other.x; } else if other.x > 0.0 { max_x += other.x; } if other.y < 0.0 { min_y += other.y; } else if other.y > 0.0 { max_y += other.y; } if other.z < 0.0 { min_z += other.z; } else if other.z > 0.0 { max_z += other.z; } AABB { min: Vec3::new(min_x, min_y, min_z), max: Vec3::new(max_x, max_y, max_z), } } pub fn inflate(&self, x: f64, y: f64, z: f64) -> AABB { let min_x = self.min.x - x; let min_y = self.min.y - y; let min_z = self.min.z - z; let max_x = self.max.x + x; let max_y = self.max.y + y; let max_z = self.max.z + z; AABB { min: Vec3::new(min_x, min_y, min_z), max: Vec3::new(max_x, max_y, max_z), } } pub fn intersect(&self, other: &AABB) -> AABB { let min_x = self.min.x.max(other.min.x); let min_y = self.min.y.max(other.min.y); let min_z = self.min.z.max(other.min.z); let max_x = self.max.x.min(other.max.x); let max_y = self.max.y.min(other.max.y); let max_z = self.max.z.min(other.max.z); AABB { min: Vec3::new(min_x, min_y, min_z), max: Vec3::new(max_x, max_y, max_z), } } pub fn minmax(&self, other: &AABB) -> AABB { let min_x = self.min.x.min(other.min.x); let min_y = self.min.y.min(other.min.y); let min_z = self.min.z.min(other.min.z); let max_x = self.max.x.max(other.max.x); let max_y = self.max.y.max(other.max.y); let max_z = self.max.z.max(other.max.z); AABB { min: Vec3::new(min_x, min_y, min_z), max: Vec3::new(max_x, max_y, max_z), } } pub fn move_relative(&self, delta: Vec3) -> AABB { AABB { min: self.min + delta, max: self.max + delta, } } pub fn intersects_aabb(&self, other: &AABB) -> bool { self.min.x < other.max.x && self.max.x > other.min.x && self.min.y < other.max.y && self.max.y > other.min.y && self.min.z < other.max.z && self.max.z > other.min.z } pub fn intersects_vec3(&self, other: &Vec3, other2: &Vec3) -> bool { self.intersects_aabb(&AABB { min: Vec3::new( other.x.min(other2.x), other.y.min(other2.y), other.z.min(other2.z), ), max: Vec3::new( other.x.max(other2.x), other.y.max(other2.y), other.z.max(other2.z), ), }) } pub fn contains(&self, point: &Vec3) -> bool { point.x >= self.min.x && point.x < self.max.x && point.y >= self.min.y && point.y < self.max.y && point.z >= self.min.z && point.z < self.max.z } pub fn size(&self) -> f64 { let x = self.get_size(Axis::X); let y = self.get_size(Axis::Y); let z = self.get_size(Axis::Z); (x + y + z) / 3.0 } pub fn get_size(&self, axis: Axis) -> f64 { axis.choose( self.max.x - self.min.x, self.max.y - self.min.y, self.max.z - self.min.z, ) } pub fn deflate(&mut self, x: f64, y: f64, z: f64) -> AABB { self.inflate(-x, -y, -z) } pub fn deflate_all(&mut self, amount: f64) -> AABB { self.deflate(amount, amount, amount) } pub fn clip(&self, min: &Vec3, max: &Vec3) -> Option { let mut t = 1.0; let delta = max - min; let _dir = Self::get_direction_aabb(self, min, &mut t, None, &delta)?; Some(min + &(delta * t)) } pub fn clip_with_from_and_to(min: &Vec3, max: &Vec3, from: &Vec3, to: &Vec3) -> Option { let mut t = 1.0; let delta = to - from; let _dir = Self::get_direction(min, max, from, &mut t, None, &delta)?; Some(from + &(delta * t)) } pub fn clip_iterable( boxes: &Vec, from: &Vec3, to: &Vec3, pos: &BlockPos, ) -> Option { let mut t = 1.0; let mut dir = None; let delta = to - from; for aabb in boxes { dir = Self::get_direction_aabb( &aabb.move_relative(pos.to_vec3_floored()), from, &mut t, dir, &delta, ); } let dir = dir?; Some(BlockHitResult { location: from + &(delta * t), direction: dir, block_pos: *pos, inside: false, miss: false, world_border: false, }) } fn get_direction_aabb( &self, from: &Vec3, t: &mut f64, dir: Option, delta: &Vec3, ) -> Option { AABB::get_direction(&self.min, &self.max, from, t, dir, delta) } fn get_direction( min: &Vec3, max: &Vec3, from: &Vec3, t: &mut f64, mut dir: Option, delta: &Vec3, ) -> Option { if delta.x > EPSILON { dir = Self::clip_point(ClipPointOpts { t, approach_dir: dir, delta, begin: min.x, min_x: min.y, max_x: max.y, min_z: min.z, max_z: max.z, result_dir: Direction::West, start: from, }); } else if delta.x < -EPSILON { dir = Self::clip_point(ClipPointOpts { t, approach_dir: dir, delta, begin: max.x, min_x: min.y, max_x: max.y, min_z: min.z, max_z: max.z, result_dir: Direction::East, start: from, }); } if delta.y > EPSILON { dir = Self::clip_point(ClipPointOpts { t, approach_dir: dir, delta: &Vec3 { x: delta.y, y: delta.z, z: delta.x, }, begin: min.y, min_x: min.z, max_x: max.z, min_z: min.x, max_z: max.x, result_dir: Direction::Down, start: &Vec3 { x: from.y, y: from.z, z: from.x, }, }); } else if delta.y < -EPSILON { dir = Self::clip_point(ClipPointOpts { t, approach_dir: dir, delta: &Vec3 { x: delta.y, y: delta.z, z: delta.x, }, begin: max.y, min_x: min.z, max_x: max.z, min_z: min.x, max_z: max.x, result_dir: Direction::Up, start: &Vec3 { x: from.y, y: from.z, z: from.x, }, }); } if delta.z > EPSILON { dir = Self::clip_point(ClipPointOpts { t, approach_dir: dir, delta: &Vec3 { x: delta.z, y: delta.x, z: delta.y, }, begin: min.z, min_x: min.x, max_x: max.x, min_z: min.y, max_z: max.y, result_dir: Direction::North, start: &Vec3 { x: from.z, y: from.x, z: from.y, }, }); } else if delta.z < -EPSILON { dir = Self::clip_point(ClipPointOpts { t, approach_dir: dir, delta: &Vec3 { x: delta.z, y: delta.x, z: delta.y, }, begin: max.z, min_x: min.x, max_x: max.x, min_z: min.y, max_z: max.y, result_dir: Direction::South, start: &Vec3 { x: from.z, y: from.x, z: from.y, }, }); } dir } fn clip_point(opts: ClipPointOpts) -> Option { let d = (opts.begin - opts.start.x) / opts.delta.x; let e = opts.start.y + d * opts.delta.y; let f = opts.start.z + d * opts.delta.z; if 0.0 < d && d < *opts.t && opts.min_x - EPSILON < e && e < opts.max_x + EPSILON && opts.min_z - EPSILON < f && f < opts.max_z + EPSILON { *opts.t = d; Some(opts.result_dir) } else { opts.approach_dir } } pub fn has_nan(&self) -> bool { self.min.x.is_nan() || self.min.y.is_nan() || self.min.z.is_nan() || self.max.x.is_nan() || self.max.y.is_nan() || self.max.z.is_nan() } pub fn get_center(&self) -> Vec3 { Vec3::new( (self.min.x + self.max.x) / 2.0, (self.min.y + self.max.y) / 2.0, (self.min.z + self.max.z) / 2.0, ) } pub fn of_size(center: Vec3, dx: f64, dy: f64, dz: f64) -> AABB { AABB { min: Vec3::new( center.x - dx / 2.0, center.y - dy / 2.0, center.z - dz / 2.0, ), max: Vec3::new( center.x + dx / 2.0, center.y + dy / 2.0, center.z + dz / 2.0, ), } } pub fn max(&self, axis: &Axis) -> f64 { axis.choose(self.max.x, self.max.y, self.max.z) } pub fn min(&self, axis: &Axis) -> f64 { axis.choose(self.min.x, self.min.y, self.min.z) } pub fn collided_along_vector(&self, vector: Vec3, boxes: &Vec) -> bool { let center = self.get_center(); let new_center = center + vector; for aabb in boxes { let inflated = aabb.inflate( self.get_size(Axis::X) * 0.5, self.get_size(Axis::Y) * 0.5, self.get_size(Axis::Z) * 0.5, ); if inflated.contains(&new_center) || inflated.contains(¢er) { return true; } if inflated.clip(¢er, &new_center).is_some() { return true; } } false } } impl BlockPos { pub fn between_closed_aabb(aabb: &AABB) -> Vec { BlockPos::between_closed(BlockPos::from(aabb.min), BlockPos::from(aabb.max)) } pub fn between_closed(min: BlockPos, max: BlockPos) -> Vec { assert!(min.x <= max.x); assert!(min.y <= max.y); assert!(min.z <= max.z); let length_x = max.x - min.x + 1; let length_y = max.y - min.y + 1; let length_z = max.z - min.z + 1; let volume = length_x * length_y * length_z; let mut result = Vec::with_capacity(volume as usize); for index in 0..volume { let index_x = index % length_x; let remaining_after_x = index / length_x; let index_y = remaining_after_x % length_y; let index_z = remaining_after_x / length_y; result.push(BlockPos::new( min.x + index_x, min.y + index_y, min.z + index_z, )); } result } } #[cfg(test)] mod tests { use super::*; #[test] fn test_aabb_clip_iterable() { assert_ne!( AABB::clip_iterable( &vec![AABB { min: Vec3::new(0., 0., 0.), max: Vec3::new(1., 1., 1.), }], &Vec3::new(-1., -1., -1.), &Vec3::new(1., 1., 1.), &BlockPos::new(0, 0, 0), ), None ); } }