Pumpkin/pumpkin-util/src/math/boundingbox.rs
2026-01-15 17:47:30 +01:00

230 lines
6.2 KiB
Rust

use crate::math::{vector2::Vector2, vector3::Axis};
use super::{position::BlockPos, vector3::Vector3};
#[derive(Clone, Copy, Debug)]
pub struct BoundingBox {
pub min: Vector3<f64>,
pub max: Vector3<f64>,
}
#[derive(Clone, Copy, Debug)]
struct BoundingPlane {
pub min: Vector2<f64>,
pub max: Vector2<f64>,
}
impl BoundingPlane {
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
}
// Projecting a 3D box into 2D
pub 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 {
pub fn new_default(size: &EntityDimensions) -> Self {
Self::new_from_pos(0., 0., 0., size)
}
pub fn new_from_pos(x: f64, y: f64, z: f64, size: &EntityDimensions) -> Self {
let f = size.width as f64 / 2.;
Self {
min: Vector3::new(x - f, y, z - f),
max: Vector3::new(x + f, y + size.height as f64, z + f),
}
}
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),
}
}
pub fn expand_all(&self, value: f64) -> Self {
self.expand(value, value, value)
}
pub fn contract_all(&self, value: f64) -> Self {
self.expand_all(-value)
}
pub fn offset(&self, other: Self) -> Self {
Self {
min: self.min.add(&other.min),
max: self.max.add(&other.max),
}
}
pub fn new(min: Vector3<f64>, max: Vector3<f64>) -> Self {
Self { min, max }
}
pub 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]),
}
}
pub fn from_block(position: &BlockPos) -> Self {
let position = position.0;
Self {
min: Vector3::new(position.x as f64, position.y as f64, position.z as f64),
max: Vector3::new(
position.x as f64 + 1.0,
position.y as f64 + 1.0,
position.z as f64 + 1.0,
),
}
}
pub fn get_side(&self, max: bool) -> Vector3<f64> {
if max { self.max } else { self.min }
}
pub fn calculate_collision_time(
&self,
other: &Self,
movement: Vector3<f64>,
axis: Axis,
max_time: f64, // Start with 1.0
) -> Option<f64> {
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)
}
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
}
pub fn min_block_pos(&self) -> BlockPos {
BlockPos::floored_v(self.min)
}
pub fn max_block_pos(&self) -> BlockPos {
BlockPos::ceiled_v(self.max)
}
pub fn shift(&self, delta: Vector3<f64>) -> Self {
Self {
min: self.min + delta,
max: self.max + delta,
}
}
pub fn stretch(&self, other: Vector3<f64>) -> 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
}
pub fn from_block_raw(position: &BlockPos) -> Self {
let position = position.0;
Self {
min: Vector3::new(position.x as f64, position.y as f64, position.z as f64),
max: Vector3::new(position.x as f64, position.y as f64, position.z as f64),
}
}
pub fn intersects(&self, other: &BoundingBox) -> 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 squared_magnitude(&self, pos: Vector3<f64>) -> 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)
}
}
#[derive(Clone, Copy, Debug)]
pub struct EntityDimensions {
pub width: f32,
pub height: f32,
pub eye_height: f32,
}
impl EntityDimensions {
pub fn new(width: f32, height: f32, eye_height: f32) -> Self {
Self {
width,
height,
eye_height,
}
}
}