use std::marker::PhantomData;
use bevy::{
ecs::{component::Component, entity::Entity, resource::Resource},
math::{IVec2, UVec2, Vec2, Vec3, Vec3Swizzles},
};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use crate::error::GridError;
#[derive(Resource)]
pub struct Grid<Marker: Component, const N: usize = 4> {
dimensions: UVec2,
spacing: Vec2,
anchor: Vec2,
data: FxHashMap<UVec2, SmallVec<[Entity; N]>>,
marker: PhantomData<Marker>,
}
impl<Marker: Component, const N: usize> Default for Grid<Marker, N> {
fn default() -> Self {
Self {
dimensions: UVec2::ONE,
spacing: Vec2::ONE,
anchor: Vec2::ZERO,
data: FxHashMap::default(),
marker: PhantomData,
}
}
}
impl<Marker: Component, const N: usize> Grid<Marker, N> {
#[inline]
pub fn dimensions(&self) -> UVec2 {
self.dimensions
}
#[inline]
pub fn spacing(&self) -> Vec2 {
self.spacing
}
#[inline]
pub fn anchor(&self) -> Vec2 {
self.anchor
}
pub fn with_dimensions(mut self, value: impl Into<UVec2>) -> Self {
self.dimensions = value.into();
self
}
pub fn with_spacing(mut self, value: impl Into<Vec2>) -> Self {
self.spacing = value.into();
self
}
pub fn with_anchor(mut self, value: impl Into<Vec2>) -> Self {
self.anchor = value.into();
self
}
#[inline]
pub(crate) fn set_dimensions(&mut self, value: impl Into<UVec2>) -> &mut Self {
self.dimensions = value.into();
self
}
#[inline]
pub(crate) fn set_spacing(&mut self, value: impl Into<Vec2>) -> &mut Self {
self.spacing = value.into();
self
}
#[inline]
pub(crate) fn set_anchor(&mut self, value: impl Into<Vec2>) -> &mut Self {
self.anchor = value.into();
self
}
pub fn reset(&mut self) {
self.data = FxHashMap::default();
}
#[inline]
pub fn insert(&mut self, entity: Entity, cell: UVec2) -> Result<(), GridError> {
if !self.contains_cell(cell) {
return Err(GridError::OutOfBounds(cell.as_ivec2()));
}
self.data.entry(cell).or_default().push(entity);
Ok(())
}
#[inline]
pub fn insert_at_world_position(
&mut self,
entity: Entity,
translation: Vec3,
) -> Result<UVec2, GridError> {
let cell = self.world_to_grid(translation)?;
self.data.entry(cell).or_default().push(entity);
Ok(cell)
}
#[inline]
pub fn get(&self, cell: UVec2) -> impl Iterator<Item = Entity> {
self.data
.get(&cell)
.map_or([].iter().copied(), |v| v.iter().copied())
}
#[inline]
pub fn iter_neighbors(&self, cell: UVec2) -> impl Iterator<Item = Entity> + '_ {
self.get_cell_neighbors(cell)
.flat_map(move |neighbor_cell| self.get(neighbor_cell))
}
#[inline]
pub fn iter_neighbors_inclusive(&self, cell: UVec2) -> impl Iterator<Item = Entity> + '_ {
self.iter_neighbors(cell).chain(self.get(cell))
}
#[inline]
fn remove_from_grid(&mut self, entity: Entity, cell: UVec2) -> Result<(), GridError> {
if let Some(entities) = self.data.get_mut(&cell) {
if let Some(pos) = entities.iter().position(|&e| e == entity) {
entities.swap_remove(pos);
if entities.is_empty() {
self.data.remove(&cell);
}
} else {
return Err(GridError::EntityNotFound(entity));
}
} else {
return Err(GridError::CellNotFound(cell));
}
Ok(())
}
#[inline]
pub fn remove(&mut self, entity: Entity, cell: UVec2) -> Result<(), GridError> {
self.remove_from_grid(entity, cell)?;
Ok(())
}
#[inline]
pub fn update(
&mut self,
entity: Entity,
current_cell: UVec2,
new_cell: UVec2,
) -> Result<(), GridError> {
if !self.contains_cell(new_cell) {
return Err(GridError::OutOfBounds(new_cell.as_ivec2()));
}
self.remove_from_grid(entity, current_cell)?;
self.data.entry(new_cell).or_default().push(entity);
Ok(())
}
#[inline]
pub fn contains_cell(&self, cell: UVec2) -> bool {
cell.cmplt(self.dimensions).all()
}
#[inline]
pub fn world_to_grid(&self, translation: Vec3) -> Result<UVec2, GridError> {
let cell: IVec2 = ((translation.xy() - self.anchor) / self.spacing)
.floor()
.as_ivec2();
if cell.cmpge(self.dimensions.as_ivec2()).any() || cell.cmplt(IVec2::ZERO).any() {
return Err(GridError::OutOfBounds(cell));
}
Ok(cell.as_uvec2())
}
#[inline]
pub fn get_cell_neighbors(&self, cell: UVec2) -> GridCellIterator {
GridCellIterator {
cell: cell.as_ivec2(),
dimensions: self.dimensions,
pattern: 0,
}
}
}
pub struct GridCellIterator {
cell: IVec2,
dimensions: UVec2,
pattern: u8,
}
impl Iterator for GridCellIterator {
type Item = UVec2;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
while self.pattern < 9 {
if self.pattern == 4 {
self.pattern += 1;
continue; }
let x_offset = (self.pattern % 3) as i32 - 1;
let y_offset = (self.pattern / 3) as i32 - 1;
let neighbor = self.cell + IVec2::new(x_offset, y_offset);
self.pattern += 1;
if neighbor.cmpge(IVec2::ZERO).all() && neighbor.cmplt(self.dimensions.as_ivec2()).all()
{
return Some(neighbor.as_uvec2());
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy::ecs::component::Component;
#[derive(Component)]
struct TestMarker;
#[test]
fn test_grid_default() {
let grid = Grid::<TestMarker>::default();
assert_eq!(grid.dimensions(), UVec2::ONE);
assert_eq!(grid.spacing(), Vec2::ONE);
assert_eq!(grid.anchor(), Vec2::ZERO);
}
#[test]
fn test_grid_builders() {
let grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(20, 15))
.with_spacing(Vec2::splat(16.))
.with_anchor(Vec2::new(10.0, 5.0));
assert_eq!(grid.dimensions(), UVec2::new(20, 15));
assert_eq!(grid.spacing(), Vec2::splat(16.));
assert_eq!(grid.anchor(), Vec2::new(10.0, 5.0));
}
#[test]
fn test_contains_cell() {
let grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
assert!(grid.contains_cell(UVec2::new(0, 0)));
assert!(grid.contains_cell(UVec2::new(9, 9)));
assert!(grid.contains_cell(UVec2::new(5, 5)));
assert!(!grid.contains_cell(UVec2::new(10, 9)));
assert!(!grid.contains_cell(UVec2::new(9, 10)));
assert!(!grid.contains_cell(UVec2::new(10, 10)));
}
#[test]
fn test_world_to_grid() {
let grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
assert_eq!(
grid.world_to_grid(Vec3::new(0.0, 0.0, 0.0)).unwrap(),
UVec2::new(0, 0)
);
assert_eq!(
grid.world_to_grid(Vec3::new(32.0, 32.0, 0.0)).unwrap(),
UVec2::new(1, 1)
);
assert_eq!(
grid.world_to_grid(Vec3::new(31.0, 31.0, 0.0)).unwrap(),
UVec2::new(0, 0)
);
assert_eq!(
grid.world_to_grid(Vec3::new(288.0, 288.0, 0.0)).unwrap(),
UVec2::new(9, 9)
);
assert!(grid.world_to_grid(Vec3::new(-1.0, 0.0, 0.0)).is_err());
assert!(grid.world_to_grid(Vec3::new(321.0, 0.0, 0.0)).is_err());
}
#[test]
fn test_world_to_grid_with_anchor() {
let grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.))
.with_anchor(Vec2::new(16.0, 16.0));
assert_eq!(
grid.world_to_grid(Vec3::new(16.0, 16.0, 0.0)).unwrap(),
UVec2::new(0, 0)
);
assert_eq!(
grid.world_to_grid(Vec3::new(48.0, 48.0, 0.0)).unwrap(),
UVec2::new(1, 1)
);
assert!(grid.world_to_grid(Vec3::new(0.0, 16.0, 0.0)).is_err());
}
#[test]
fn test_insert_and_get() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity = Entity::from_raw(42);
assert!(grid.insert(entity, UVec2::new(5, 5)).is_ok());
let entities: Vec<Entity> = grid.get(UVec2::new(5, 5)).collect();
assert_eq!(entities, vec![entity]);
let entities: Vec<Entity> = grid.get(UVec2::new(0, 0)).collect();
assert!(entities.is_empty());
assert!(grid.insert(entity, UVec2::new(10, 10)).is_err());
}
#[test]
fn test_insert_at_world_position() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity = Entity::from_raw(42);
let cell = grid
.insert_at_world_position(entity, Vec3::new(160.0, 160.0, 0.0))
.unwrap();
assert_eq!(cell, UVec2::new(5, 5));
let entities: Vec<Entity> = grid.get(UVec2::new(5, 5)).collect();
assert_eq!(entities, vec![entity]);
assert!(
grid.insert_at_world_position(entity, Vec3::new(-1.0, 0.0, 0.0))
.is_err()
);
}
#[test]
fn test_remove() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity = Entity::from_raw(42);
grid.insert(entity, UVec2::new(5, 5)).unwrap();
assert!(grid.remove(entity, UVec2::new(5, 5)).is_ok());
let entities: Vec<Entity> = grid.get(UVec2::new(5, 5)).collect();
assert!(entities.is_empty());
assert!(grid.remove(Entity::from_raw(99), UVec2::new(5, 5)).is_err());
assert!(grid.remove(entity, UVec2::new(0, 0)).is_err());
}
#[test]
fn test_update() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity = Entity::from_raw(42);
grid.insert(entity, UVec2::new(5, 5)).unwrap();
assert!(
grid.update(entity, UVec2::new(5, 5), UVec2::new(7, 7))
.is_ok()
);
let entities: Vec<Entity> = grid.get(UVec2::new(7, 7)).collect();
assert_eq!(entities, vec![entity]);
let entities: Vec<Entity> = grid.get(UVec2::new(5, 5)).collect();
assert!(entities.is_empty());
assert!(
grid.update(entity, UVec2::new(7, 7), UVec2::new(10, 10))
.is_err()
);
assert!(
grid.update(Entity::from_raw(99), UVec2::new(7, 7), UVec2::new(8, 8))
.is_err()
);
}
#[test]
fn test_multiple_entities_same_cell() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity1 = Entity::from_raw(42);
let entity2 = Entity::from_raw(43);
let entity3 = Entity::from_raw(44);
grid.insert(entity1, UVec2::new(5, 5)).unwrap();
grid.insert(entity2, UVec2::new(5, 5)).unwrap();
grid.insert(entity3, UVec2::new(5, 5)).unwrap();
let mut entities: Vec<Entity> = grid.get(UVec2::new(5, 5)).collect();
entities.sort_by_key(|e| e.index());
assert_eq!(entities, vec![entity1, entity2, entity3]);
grid.remove(entity2, UVec2::new(5, 5)).unwrap();
let mut entities: Vec<Entity> = grid.get(UVec2::new(5, 5)).collect();
entities.sort_by_key(|e| e.index());
assert_eq!(entities, vec![entity1, entity3]);
}
#[test]
fn test_grid_cell_iterator() {
let grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(5, 5))
.with_spacing(Vec2::splat(32.));
let neighbors: Vec<UVec2> = grid.get_cell_neighbors(UVec2::new(2, 2)).collect();
assert_eq!(neighbors.len(), 8);
let expected = vec![
UVec2::new(1, 1),
UVec2::new(2, 1),
UVec2::new(3, 1),
UVec2::new(1, 2),
UVec2::new(3, 2),
UVec2::new(1, 3),
UVec2::new(2, 3),
UVec2::new(3, 3),
];
for expected_neighbor in expected {
assert!(neighbors.contains(&expected_neighbor));
}
let neighbors: Vec<UVec2> = grid.get_cell_neighbors(UVec2::new(0, 0)).collect();
assert_eq!(neighbors.len(), 3);
assert!(neighbors.contains(&UVec2::new(1, 0)));
assert!(neighbors.contains(&UVec2::new(0, 1)));
assert!(neighbors.contains(&UVec2::new(1, 1)));
let neighbors: Vec<UVec2> = grid.get_cell_neighbors(UVec2::new(4, 2)).collect();
assert_eq!(neighbors.len(), 5);
}
#[test]
fn test_iter_neighbors() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity1 = Entity::from_raw(42);
let entity2 = Entity::from_raw(43);
let entity3 = Entity::from_raw(44);
grid.insert(entity1, UVec2::new(4, 4)).unwrap();
grid.insert(entity2, UVec2::new(5, 4)).unwrap();
grid.insert(entity3, UVec2::new(7, 7)).unwrap();
let neighbors: Vec<Entity> = grid.iter_neighbors(UVec2::new(5, 5)).collect();
assert!(neighbors.contains(&entity1));
assert!(neighbors.contains(&entity2));
assert!(!neighbors.contains(&entity3)); }
#[test]
fn test_iter_neighbors_inclusive() {
let mut grid = Grid::<TestMarker>::default()
.with_dimensions(UVec2::new(10, 10))
.with_spacing(Vec2::splat(32.));
let entity1 = Entity::from_raw(42);
let entity2 = Entity::from_raw(43);
let entity3 = Entity::from_raw(44);
let center_entity = Entity::from_raw(45);
grid.insert(entity1, UVec2::new(4, 4)).unwrap();
grid.insert(entity2, UVec2::new(5, 4)).unwrap();
grid.insert(center_entity, UVec2::new(5, 5)).unwrap(); grid.insert(entity3, UVec2::new(7, 7)).unwrap();
let neighbors: Vec<Entity> = grid.iter_neighbors_inclusive(UVec2::new(5, 5)).collect();
assert!(neighbors.contains(&entity1));
assert!(neighbors.contains(&entity2));
assert!(neighbors.contains(¢er_entity)); assert!(!neighbors.contains(&entity3));
let regular_neighbors: Vec<Entity> = grid.iter_neighbors(UVec2::new(5, 5)).collect();
assert!(neighbors.len() > regular_neighbors.len());
}
}