use nalgebra::{Point, SVector};
use crate::{TileIndex, TileIndexOffset};
#[derive(Clone, Copy, Debug)]
pub struct TileRect<const D: usize> {
pub origin: TileIndex<D>,
pub size: SVector<usize, D>,
}
impl<const D: usize> Default for TileRect<D> {
fn default() -> Self {
Self {
origin: [0; D].into(),
size: [0; D].into(),
}
}
}
impl<const D: usize> TileRect<D> {
#[must_use]
pub fn translate(&self, translation: TileIndexOffset<D>) -> Self {
Self {
origin: self.origin + translation,
size: self.size,
}
}
pub fn linear_index_to_tile_index(&self, linear_index: usize) -> Option<TileIndex<D>> {
if linear_index < self.area() {
let mut acc = 1;
let offset = TileIndexOffset::from_fn(|i, _| {
let divisor = acc;
acc *= self.size[i];
((linear_index / divisor) % self.size[i]) as isize
});
Some(self.origin + offset)
} else {
None
}
}
pub fn min_corner(&self) -> TileIndex<D> {
self.origin
}
pub fn max_corner(&self) -> TileIndex<D> {
TileIndexOffset::<D>::from_fn(|i, _| {
self.origin[i].checked_add_unsigned(self.size[i]).unwrap()
})
.into()
}
pub fn min(&self, axis: usize) -> isize {
self.origin[axis]
}
pub fn max(&self, axis: usize) -> isize {
self.origin[axis].wrapping_add_unsigned(self.size[axis]) - 1
}
pub fn min_x(&self) -> isize {
self.min(0)
}
pub fn max_x(&self) -> isize {
self.max(0)
}
pub fn min_y(&self) -> isize {
self.min(1)
}
pub fn max_y(&self) -> isize {
self.max(1)
}
pub fn min_z(&self) -> isize {
self.min(2)
}
pub fn max_z(&self) -> isize {
self.max(2)
}
pub fn min_w(&self) -> isize {
self.min(3)
}
pub fn max_w(&self) -> isize {
self.max(3)
}
pub fn intersects(&self, rhs: &TileRect<D>) -> bool {
(0..D).all(|i| self.min(i) <= rhs.max(i) && rhs.min(i) <= self.max(i))
}
pub fn contains_point(&self, point: TileIndex<D>) -> bool {
(0..D).all(|i| self.min(i) <= point[i] && self.max(i) >= point[i])
}
pub fn contains(&self, rhs: &TileRect<D>) -> bool {
(0..D).all(|i| self.min(i) <= rhs.min(i) && self.max(i) >= rhs.max(i))
}
pub fn intersection(&self, rhs: &TileRect<D>) -> Option<TileRect<D>> {
self.intersects(rhs).then(|| {
let min =
TileIndex::<D>::from(TileIndexOffset::from_fn(|i, _| self.min(i).max(rhs.min(i))));
let max =
TileIndex::<D>::from(TileIndexOffset::from_fn(|i, _| self.max(i).min(rhs.max(i))));
let size = SVector::from_fn(|i, _| max[i].abs_diff(min[i]) + 1);
Self {
origin: min,
size: size,
}
})
}
pub fn area(&self) -> usize {
self.size.product()
}
pub fn is_empty(&self) -> bool {
self.size.iter().all(|&x| x == 0)
}
pub fn expand_to_include_index(
&mut self,
index: TileIndex<D>,
minimum_nonzero_expansion: SVector<usize, D>,
) -> bool {
self.expand_to_include_bounds(
TileRect {
origin: index,
size: SVector::from_fn(|_, _| 1),
},
minimum_nonzero_expansion,
)
}
pub fn expand_to_include_bounds(
&mut self,
bounds: TileRect<D>,
minimum_nonzero_expansion: SVector<usize, D>,
) -> bool {
let mut expanded = false;
let min_expansion = minimum_nonzero_expansion;
if self.is_empty() {
expanded = true;
*self = bounds;
}
let origin = TileIndex::<D>::from(TileIndexOffset::from_fn(|i, _| {
if bounds.origin[i] < self.origin[i] {
expanded = true;
bounds.origin[i].min(self.origin[i].saturating_sub(min_expansion[i] as isize))
} else {
self.origin[i]
}
}));
let end_corner = self.max_corner();
let bounds_end_corner = bounds.max_corner();
let end_corner = TileIndex::<D>::from(TileIndexOffset::<D>::from_fn(|i, _| {
if bounds_end_corner[i] > end_corner[i] {
expanded = true;
bounds_end_corner[i].max(end_corner[i].saturating_add(min_expansion[i] as isize))
} else {
end_corner[i]
}
}));
self.origin = origin;
self.size = SVector::from_fn(|i, _| origin[i].abs_diff(end_corner[i]));
expanded
}
}
pub struct Indexes<const D: usize> {
bounds: TileRect<D>,
linear_index: usize,
}
impl<const D: usize> Iterator for Indexes<D> {
type Item = TileIndex<D>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(index) = self.bounds.linear_index_to_tile_index(self.linear_index) {
self.linear_index += 1;
Some(index)
} else {
None
}
}
}
impl<const D: usize> TileRect<D> {
pub fn indexes(&self) -> Indexes<D> {
Indexes {
bounds: *self,
linear_index: 0,
}
}
}
pub struct SlicedIndexes<const D: usize, const D2: usize> {
inner: Indexes<D2>,
slice: Point<Option<isize>, D>,
}
impl<const D: usize, const D2: usize> Iterator for SlicedIndexes<D, D2> {
type Item = TileIndex<D>;
fn next(&mut self) -> Option<Self::Item> {
let sliced = self.inner.next()?;
let mut j = 0;
Some(
TileIndexOffset::<D>::from_fn(|i, _| {
self.slice[i].unwrap_or_else(|| {
let index = j;
j += 1;
sliced[index]
})
})
.into(),
)
}
}
impl<const D: usize> TileRect<D> {
pub fn slice_indexes<const D2: usize>(
&self,
slice: Point<Option<isize>, D>,
) -> SlicedIndexes<D, D2> {
let mut j = 0;
let sliced_origin = TileIndexOffset::<D2>::from_fn(|_, _| {
while slice[j].is_some() {
j += 1;
}
self.origin[j]
})
.into();
let mut j = 0;
let sliced_size = SVector::<usize, D2>::from_fn(|_, _| {
while slice[j].is_some() {
j += 1;
}
self.size[j]
});
let sliced = TileRect {
origin: sliced_origin,
size: sliced_size,
};
SlicedIndexes {
inner: sliced.indexes(),
slice,
}
}
}