miscellaneous_grids 2.1.0

A few expandable tile grids.
Documentation
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,
        }
    }
}