use std::fmt::Display;
use bevy::prelude::*;
use thiserror::Error;
use super::iterators::CuboidIterator;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Region {
pos: IVec3,
size: IVec3,
}
impl Region {
pub const CHUNK: Region = Region {
pos: IVec3::ZERO,
size: IVec3::new(16, 16, 16),
};
pub const NEIGHBORS: Region = Region {
pos: IVec3::NEG_ONE,
size: IVec3::new(3, 3, 3),
};
pub const SECTOR: Region = Region {
pos: IVec3::ZERO,
size: IVec3::new(256, 256, 256),
};
pub fn from_points(a: IVec3, b: IVec3) -> Self {
let min = a.min(b);
let max = a.max(b);
let size = max - min + 1;
Self {
pos: min,
size,
}
}
pub fn from_size(pos: IVec3, size: IVec3) -> Result<Self, RegionError> {
if size.x <= 0 || size.y <= 0 || size.z <= 0 {
return Err(RegionError::NegativeSize(size));
}
Ok(Self {
pos,
size,
})
}
pub fn intersection(a: &Region, b: &Region) -> Result<Self, RegionError> {
let min = a.min().max(b.min());
let max = a.max().min(b.max());
let size = max - min + 1;
if size.x <= 0 || size.y <= 0 || size.z <= 0 {
return Err(RegionError::NoIntersection(*a, *b));
}
Ok(Self {
pos: min,
size,
})
}
pub fn min(&self) -> IVec3 {
self.pos
}
pub fn max(&self) -> IVec3 {
self.pos + self.size - 1
}
pub fn size(&self) -> IVec3 {
self.size
}
pub fn contains(&self, point: IVec3) -> bool {
let p = point - self.pos;
p.x >= 0
&& p.y >= 0
&& p.z >= 0
&& p.x < self.size.x
&& p.y < self.size.y
&& p.z < self.size.z
}
pub fn point_to_index(&self, point: IVec3) -> Result<usize, RegionError> {
if !self.contains(point) {
return Err(RegionError::OutOfBounds(point));
}
let p = point - self.pos;
let index = p.x * self.size.y * self.size.z + p.y * self.size.z + p.z;
Ok(index as usize)
}
pub fn iter(&self) -> CuboidIterator {
CuboidIterator::from(self)
}
pub fn count(&self) -> usize {
(self.size.x * self.size.y * self.size.z) as usize
}
pub fn shift(self, amount: IVec3) -> Self {
Self {
pos: self.pos + amount,
size: self.size,
}
}
pub fn intersects(&self, other: Region) -> bool {
let min = self.min().max(other.min());
let max = self.max().min(other.max());
let size = max - min;
size.x >= 0 && size.y >= 0 && size.z >= 0
}
pub fn expand(self, point: IVec3) -> Self {
let min = self.min().min(point);
let max = self.max().max(point);
let size = max - min + 1;
Self {
pos: min,
size,
}
}
}
impl IntoIterator for Region {
type IntoIter = CuboidIterator;
type Item = IVec3;
fn into_iter(self) -> Self::IntoIter {
CuboidIterator::from(&self)
}
}
impl Display for Region {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(Pos: {}, Size: {})", self.pos, self.size)
}
}
#[derive(Error, Debug)]
pub enum RegionError {
#[error("Cannot create a region with a size <= 0. Found: {0}")]
NegativeSize(IVec3),
#[error("Regions {0} and {1} do not intersect")]
NoIntersection(Region, Region),
#[error("Point is outside of region: {0}")]
OutOfBounds(IVec3),
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn index_is_unique() {
let a = IVec3::new(-17, 2, -3);
let b = IVec3::new(-20, 4, -2);
let region = Region::from_points(a, b);
let mut indices: Vec<usize> = region
.iter()
.map(|pos| region.point_to_index(pos).unwrap())
.collect();
indices.dedup();
assert_eq!(indices.len(), region.count());
assert_eq!(indices.iter().min(), Some(0).as_ref());
assert_eq!(indices.iter().max(), Some(region.count() - 1).as_ref());
}
}