use crate::math::{vector2::Vector2, vector3::Axis}; use super::{position::BlockPos, vector3::Vector3}; /// Represents an axis-aligned bounding box in 3D space. #[derive(Clone, Copy, Debug)] pub struct BoundingBox { /// The minimum corner of the box. pub min: Vector3, /// The maximum corner of the box. pub max: Vector3, } /// Represents a 2D bounding plane used for collision checks. #[derive(Clone, Copy, Debug)] struct BoundingPlane { /// The minimum corner of the plane. pub min: Vector2, /// The maximum corner of the plane. pub max: Vector2, } impl BoundingPlane { /// Checks whether this plane intersects another plane. /// /// # Arguments /// * `other` – The other bounding plane to check against. pub fn intersects(&self, other: &Self) -> 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 } /// Projects a 3D bounding box onto a 2D plane by excluding one axis. /// /// # Arguments /// * `bounding_box` – The 3D bounding box to project. /// * `excluded` – The axis to exclude from the projection. pub const fn from_box(bounding_box: &BoundingBox, excluded: Axis) -> Self { let [axis1, axis2] = Axis::excluding(excluded); Self { min: Vector2::new( bounding_box.get_side(false).get_axis(axis1), bounding_box.get_side(false).get_axis(axis2), ), max: Vector2::new( bounding_box.get_side(true).get_axis(axis1), bounding_box.get_side(true).get_axis(axis2), ), } } } impl BoundingBox { /// Creates a default bounding box at the origin using entity dimensions. /// /// # Arguments /// * `size` – Dimensions of the entity. #[must_use] pub fn new_default(size: &EntityDimensions) -> Self { Self::new_from_pos(0., 0., 0., size) } /// Creates a bounding box from a position and entity dimension. /// /// # Arguments /// * `x` – X coordinate of the position. /// * `y` – Y coordinate of the position. /// * `z` – Z coordinate of the position. /// * `size` – Dimensions of the entity. #[must_use] pub fn new_from_pos(x: f64, y: f64, z: f64, size: &EntityDimensions) -> Self { let f = f64::from(size.width) / 2.; Self { min: Vector3::new(x - f, y, z - f), max: Vector3::new(x + f, y + f64::from(size.height), z + f), } } /// Expands this box by given amounts along each axis. /// /// # Arguments /// * `x` – Amount to expand along the X axis. /// * `y` – Amount to expand along the Y axis. /// * `z` – Amount to expand along the Z axis. #[must_use] pub fn expand(&self, x: f64, y: f64, z: f64) -> Self { Self { min: Vector3::new(self.min.x - x, self.min.y - y, self.min.z - z), max: Vector3::new(self.max.x + x, self.max.y + y, self.max.z + z), } } /// Expands this bounding box towards a specific direction. /// /// If a provided value is negative, it extends the minimum boundary along that axis. /// If a provided value is positive, it extends the maximum boundary along that axis. /// /// # Arguments /// * `x` – Amount to expand towards on the X axis. /// * `y` – Amount to expand towards on the Y axis. /// * `z` – Amount to expand towards on the Z axis. #[must_use] pub fn expand_towards(&self, x: f64, y: f64, z: f64) -> Self { 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 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; } Self { min: Vector3::new(min_x, min_y, min_z), max: Vector3::new(max_x, max_y, max_z), } } /// Expands this box uniformly along all axes. /// /// # Arguments /// * `value` – Amount to expand along all axes. #[must_use] pub fn expand_all(&self, value: f64) -> Self { self.expand(value, value, value) } /// Contracts this box uniformly along all axes. /// /// # Arguments /// * `value` – Amount to contract along all axes. #[must_use] pub fn contract_all(&self, value: f64) -> Self { self.expand_all(-value) } /// Returns a new bounding box shifted to a specific block position. /// /// # Arguments /// * `pos` – Block position to move the box to. #[must_use] pub fn at_pos(&self, pos: BlockPos) -> Self { let vec3 = Vector3 { x: f64::from(pos.0.x), y: f64::from(pos.0.y), z: f64::from(pos.0.z), }; Self { min: self.min + vec3, max: self.max + vec3, } } /// Returns a new bounding box offset by another bounding box. /// /// # Arguments /// * `other` – The bounding box to add as an offset. #[must_use] pub fn offset(&self, other: Self) -> Self { Self { min: self.min.add(&other.min), max: self.max.add(&other.max), } } /// Creates a bounding box from explicit min and max coordinates. /// /// # Arguments /// * `min` – Minimum corner of the box. /// * `max` – Maximum corner of the box. #[must_use] pub const fn new(min: Vector3, max: Vector3) -> Self { Self { min, max } } /// Creates a bounding box from arrays of min and max coordinates. /// /// # Arguments /// * `min` – Minimum corner as an array [x, y, z]. /// * `max` – Maximum corner as an array [x, y, z]. #[must_use] pub const fn new_array(min: [f64; 3], max: [f64; 3]) -> Self { Self { min: Vector3::new(min[0], min[1], min[2]), max: Vector3::new(max[0], max[1], max[2]), } } /// Returns a bounding box representing a full block from (0,0,0) to (1,1,1). #[must_use] pub const fn full_block() -> Self { Self { min: Vector3::new(0f64, 0f64, 0f64), max: Vector3::new(1f64, 1f64, 1f64), } } /// Creates a bounding box from a block position covering a full block. /// /// # Arguments /// * `position` – Block position to base the bounding box on. #[must_use] pub fn from_block(position: &BlockPos) -> Self { let position = position.0; Self { min: Vector3::new( f64::from(position.x), f64::from(position.y), f64::from(position.z), ), max: Vector3::new( f64::from(position.x) + 1.0, f64::from(position.y) + 1.0, f64::from(position.z) + 1.0, ), } } /// Returns the min or max side of the bounding box. /// /// # Arguments /// * `max` – Whether to return the max side (true) or min side (false). #[must_use] pub const fn get_side(&self, max: bool) -> Vector3 { if max { self.max } else { self.min } } /// Calculates the collision time with another bounding box along a movement vector. /// /// # Arguments /// * `other` – The bounding box to test collision against. /// * `movement` – Movement vector of this box. /// * `axis` – Axis along which to calculate collision. /// * `max_time` – Maximum allowed collision time. /// /// # Returns /// Some(f64) if a collision occurs within `max_time`, None otherwise. #[must_use] pub fn calculate_collision_time( &self, other: &Self, movement: Vector3, axis: Axis, max_time: f64, // NOTE: Start with 1.0 ) -> Option { let movement_on_axis = movement.get_axis(axis); if movement_on_axis == 0.0 { return None; } let move_positive = movement_on_axis.is_sign_positive(); let self_plane_const = self.get_side(move_positive).get_axis(axis); let other_plane_const = other.get_side(!move_positive).get_axis(axis); let collision_time = (other_plane_const - self_plane_const) / movement_on_axis; if collision_time < 0.0 || collision_time >= max_time { return None; } let self_moved = self.shift(movement * collision_time); let self_plane_moved = BoundingPlane::from_box(&self_moved, axis); let other_plane = BoundingPlane::from_box(other, axis); if !self_plane_moved.intersects(&other_plane) { return None; } Some(collision_time) } /// Returns the average side length of the bounding box. #[must_use] pub fn get_average_side_length(&self) -> f64 { let width = self.max.x - self.min.x; let height = self.max.y - self.min.y; let depth = self.max.z - self.min.z; (width + height + depth) / 3.0 } /// Returns the minimum block position covered by this bounding box. #[must_use] pub const fn min_block_pos(&self) -> BlockPos { BlockPos::floored_v(self.min) } /// Returns the maximum block position covered by this bounding box. #[must_use] pub const fn max_block_pos(&self) -> BlockPos { // Use a tiny epsilon and floor the max coordinates so that a box whose // max is exactly on a block boundary does not include the adjacent // block. This mirrors vanilla behaviour where max block is inclusive // only when the entity actually overlaps that block. let eps = 1e-9f64; BlockPos::floored_v(Vector3::new( self.max.x - eps, self.max.y - eps, self.max.z - eps, )) } /// Returns a new bounding box shifted by a delta vector. /// /// # Arguments /// * `delta` – Vector to shift the bounding box by. #[must_use] pub fn shift(&self, delta: Vector3) -> Self { Self { min: self.min + delta, max: self.max + delta, } } /// Stretches this bounding box along each axis by a given vector. /// /// # Arguments /// * `other` – Vector specifying how much to stretch along each axis. #[must_use] pub const fn stretch(&self, other: Vector3) -> Self { let mut new = *self; if other.x < 0.0 { new.min.x += other.x; } else if other.x > 0.0 { new.max.x += other.x; } if other.y < 0.0 { new.min.y += other.y; } else if other.y > 0.0 { new.max.y += other.y; } if other.z < 0.0 { new.min.z += other.z; } else if other.z > 0.0 { new.max.z += other.z; } new } /// Creates a bounding box from a block position with zero volume. /// /// # Arguments /// * `position` – Block position to base the bounding box on. #[must_use] pub fn from_block_raw(position: &BlockPos) -> Self { let position = position.0; Self { min: Vector3::new( f64::from(position.x), f64::from(position.y), f64::from(position.z), ), max: Vector3::new( f64::from(position.x), f64::from(position.y), f64::from(position.z), ), } } /// Checks if this bounding box intersects another bounding box. /// /// # Arguments /// * `other` – The other bounding box to check against. #[must_use] pub fn intersects(&self, other: &Self) -> 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 } /// Computes the squared magnitude from a point to the nearest point on this bounding box. /// /// # Arguments /// * `pos` – The point to measure from. #[must_use] pub fn squared_magnitude(&self, pos: Vector3) -> f64 { let d = f64::max(f64::max(self.min.x - pos.x, pos.x - self.max.x), 0.0); let e = f64::max(f64::max(self.min.y - pos.y, pos.y - self.max.y), 0.0); let f = f64::max(f64::max(self.min.z - pos.z, pos.z - self.max.z), 0.0); super::squared_magnitude(d, e, f) } } /// Represents the dimensions of an entity. #[derive(Clone, Copy, Debug)] pub struct EntityDimensions { /// Width of the entity. pub width: f32, /// Height of the entity. pub height: f32, /// Eye height relative to the bottom of the entity. pub eye_height: f32, } impl EntityDimensions { /// Creates a new entity dimensions object. /// /// # Arguments /// * `width` – Width of the entity. /// * `height` – Height of the entity. /// * `eye_height` – Eye height of the entity. #[must_use] pub const fn new(width: f32, height: f32, eye_height: f32) -> Self { Self { width, height, eye_height, } } }