Pumpkin/pumpkin-util/src/math/block_box.rs
Alexander Medvedev 2fcc9bcf4a chore: enforce some more clippy lints
pumpkin contributors will love me :cap:
2026-05-20 22:27:54 +02:00

302 lines
8.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::{
BlockDirection,
math::{
position::BlockPos,
vector3::{Axis, Vector3},
},
};
use pumpkin_codecs::codec::list::validate_fixed_size;
use pumpkin_codecs::{DataResult, FlatTryFrom, IntStream, comap_flat_map_codec_impl};
/// Represents an axis-aligned 3D block bounding box in integer coordinates.
#[derive(Clone, Copy, Debug)]
pub struct BlockBox {
/// The minimum corner of the box (inclusive).
pub min: Vector3<i32>,
/// The maximum corner of the box (inclusive).
pub max: Vector3<i32>,
}
impl BlockBox {
/// Creates a new box from min/max coordinates.
///
/// # Arguments
/// * `min_x` – Minimum X coordinate (inclusive).
/// * `min_y` – Minimum Y coordinate (inclusive).
/// * `min_z` – Minimum Z coordinate (inclusive).
/// * `max_x` – Maximum X coordinate (inclusive).
/// * `max_y` – Maximum Y coordinate (inclusive).
/// * `max_z` – Maximum Z coordinate (inclusive).
#[must_use]
pub const fn new(
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
) -> Self {
Self {
min: Vector3 {
x: min_x,
y: min_y,
z: min_z,
},
max: Vector3 {
x: max_x,
y: max_y,
z: max_z,
},
}
}
/// Creates a box along an axis with specified dimensions.
///
/// # Arguments
/// * `x`, `y`, `z` – Starting coordinates.
/// * `axis` – Axis along which the box extends.
/// * `width`, `height`, `depth` – Dimensions of the box.
#[must_use]
pub fn create_box(
x: i32,
y: i32,
z: i32,
axis: Axis,
width: i32,
height: i32,
depth: i32,
) -> Self {
if axis == Axis::Z {
Self::new(x, y, z, x + width - 1, y + height - 1, z + depth - 1)
} else {
Self::new(x, y, z, x + depth - 1, y + height - 1, z + width - 1)
}
}
/// Creates a rotated box from offsets and size.
///
/// # Arguments
/// * `x`, `y`, `z` – Base coordinates.
/// * `offset_x`, `offset_y`, `offset_z` – Offsets from base.
/// * `size_x`, `size_y`, `size_z` – Dimensions.
/// * `facing` – Facing direction.
#[expect(clippy::too_many_arguments)]
#[must_use]
pub const fn rotated(
x: i32,
y: i32,
z: i32,
offset_x: i32,
offset_y: i32,
offset_z: i32,
size_x: i32,
size_y: i32,
size_z: i32,
facing: &BlockDirection,
) -> Self {
match facing {
BlockDirection::North => Self::new(
x + offset_x,
y + offset_y,
z - size_z + 1 + offset_z,
x + size_x - 1 + offset_x,
y + size_y - 1 + offset_y,
z + offset_z,
),
BlockDirection::West => Self::new(
x - size_z + 1 + offset_z,
y + offset_y,
z + offset_x,
x + offset_z,
y + size_y - 1 + offset_y,
z + size_x - 1 + offset_x,
),
BlockDirection::East => Self::new(
x + offset_z,
y + offset_y,
z + offset_x,
x + size_z - 1 + offset_z,
y + size_y - 1 + offset_y,
z + size_x - 1 + offset_x,
),
// Default / South
_ => Self::new(
x + offset_x,
y + offset_y,
z + offset_z,
x + size_x - 1 + offset_x,
y + size_y - 1 + offset_y,
z + size_z - 1 + offset_z,
),
}
}
/// Creates a box covering a single block position.
///
/// # Arguments
/// * `pos` – Block position.
#[must_use]
pub const fn from_pos(pos: BlockPos) -> Self {
Self {
min: pos.0,
max: pos.0,
}
}
/// Expands the box in all directions.
///
/// # Arguments
/// * `x`, `y`, `z` – Amount to expand on each axis.
#[must_use]
pub const fn expand(&self, x: i32, y: i32, z: i32) -> 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),
}
}
/// Moves the box by the given offsets.
///
/// # Arguments
/// * `dx`, `dy`, `dz` – Offset amounts.
pub const fn move_pos(&mut self, dx: i32, dy: i32, dz: i32) {
self.min.x += dx;
self.min.y += dy;
self.min.z += dz;
self.max.x += dx;
self.max.y += dy;
self.max.z += dz;
}
/// Returns `true` if the box contains the given block position.
///
/// # Arguments
/// * `pos` – Block coordinates.
#[must_use]
pub const fn contains_pos(&self, pos: &Vector3<i32>) -> bool {
self.contains(pos.x, pos.y, pos.z)
}
/// Returns `true` if the box contains the given coordinates.
///
/// # Arguments
/// * `x`, `y`, `z` – Coordinates to test.
#[must_use]
pub const fn contains(&self, x: i32, y: i32, z: i32) -> bool {
x >= self.min.x
&& x <= self.max.x
&& y >= self.min.y
&& y <= self.max.y
&& z >= self.min.z
&& z <= self.max.z
}
/// Returns `true` if this box intersects another box in 3D.
///
/// # Arguments
/// * `other` – Other box to test.
#[must_use]
pub const fn intersects(&self, other: &Self) -> bool {
self.max.x >= other.min.x
&& self.min.x <= other.max.x
&& self.max.z >= other.min.z
&& self.min.z <= other.max.z
&& self.max.y >= other.min.y
&& self.min.y <= other.max.y
}
/// Returns `true` if this box intersects another box in the XZ plane.
///
/// # Arguments
/// * `other` – Other box to test.
#[must_use]
pub const fn intersects_xz(&self, other: &Self) -> bool {
self.max.x >= other.min.x
&& self.min.x <= other.max.x
&& self.max.z >= other.min.z
&& self.min.z <= other.max.z
}
/// Returns `true` if this box intersects given raw XZ coordinates.
///
/// # Arguments
/// * `min_x`, `min_z`, `max_x`, `max_z` – Raw XZ bounds.
#[must_use]
pub const fn intersects_raw_xz(&self, min_x: i32, min_z: i32, max_x: i32, max_z: i32) -> bool {
self.max.x >= min_x && self.min.x <= max_x && self.max.z >= min_z && self.min.z <= max_z
}
/// Returns the number of blocks along the Y axis.
#[must_use]
pub const fn get_block_count_y(&self) -> i32 {
self.max.y - self.min.y + 1
}
/// Expands this box to encompass another box.
///
/// # Arguments
/// * `other` – Box to encompass.
pub fn encompass(&mut self, other: &Self) {
self.min.x = self.min.x.min(other.min.x);
self.min.y = self.min.y.min(other.min.y);
self.min.z = self.min.z.min(other.min.z);
self.max.x = self.max.x.max(other.max.x);
self.max.y = self.max.y.max(other.max.y);
self.max.z = self.max.z.max(other.max.z);
}
/// Returns a box covering all boxes in an iterator.
///
/// # Arguments
/// * `boxes` – Iterator of boxes.
/// # Returns
/// * `Some(Box)` covering all boxes, or `None` if empty.
pub fn encompass_all<I>(boxes: I) -> Option<Self>
where
I: IntoIterator<Item = Self>,
{
let mut iter = boxes.into_iter();
let mut result = iter.next()?; // NOTE: Return None if empty
for b in iter {
result.encompass(&b);
}
Some(result)
}
}
impl From<&BlockBox> for IntStream {
fn from(value: &BlockBox) -> Self {
let Vector3 {
x: min_x,
y: min_y,
z: min_z,
} = value.min;
let Vector3 {
x: max_x,
y: max_y,
z: max_z,
} = value.max;
Self(vec![min_x, min_y, min_z, max_x, max_y, max_z])
}
}
impl FlatTryFrom<IntStream> for BlockBox {
fn flat_try_from(value: IntStream) -> DataResult<Self> {
validate_fixed_size(value.0, 6).flat_map(|v| {
v.try_into().map_or_else(
|_| DataResult::new_error("Expected 6 elements"),
|arr| {
let [min_x, min_y, min_z, max_x, max_y, max_z]: [i32; 6] = arr;
DataResult::new_success(Self {
min: Vector3::new(min_x, min_y, min_z),
max: Vector3::new(max_x, max_y, max_z),
})
},
)
})
}
}
comap_flat_map_codec_impl!(IntStream => BlockBox, BlockBox::flat_try_from, IntStream::from);