const DEFAULT_CELL_SIZE: f32 = 50.0;
pub struct SpatialHash {
cells: HashMap<(i32, i32), HashSet<Entity>>,
entity_positions: HashMap<Entity, (i32, i32)>,
cell_size: f32,
}
impl Default for SpatialHash {
fn default() -> Self {
Self {
cells: HashMap::new(),
entity_positions: HashMap::new(),
cell_size: DEFAULT_CELL_SIZE,
}
}
}
impl SpatialHash {
fn hash(&self, point: Vec2) -> (i32, i32) {
(
(point.x / self.cell_size) as i32,
(point.y / self.cell_size) as i32,
)
}
pub fn insert(&mut self, entity: Entity, position: Vec2) {
let hash = self.hash(position);
self.cells.entry(hash).or_default().insert(entity);
self.entity_positions.insert(entity, hash);
}
pub fn remove(&mut self, entity: Entity) {
if let Some(hash) = self.entity_positions.remove(&entity) {
if let Some(cell) = self.cells.get_mut(&hash) {
cell.remove(&entity);
if cell.is_empty() {
self.cells.remove(&hash);
}
}
}
}
pub fn update(&mut self, entity: Entity, position: Vec2) {
self.remove(entity);
self.insert(entity, position);
}
pub fn query_in_range(&self, position: Vec2, range: f32) -> HashSet<Entity> {
self.query_in_bounds(position - Vec2::splat(range), position + Vec2::splat(range))
}
pub fn query_in_bounds(&self, min: Vec2, max: Vec2) -> HashSet<Entity> {
let mut result = HashSet::new();
let (min_x, min_y) = self.hash(min);
let (max_x, max_y) = self.hash(max);
for x in min_x..=max_x {
for y in min_y..=max_y {
if let Some(cell) = self.cells.get(&(x, y)).filter(|cell| !cell.is_empty()) {
result.extend(cell.iter());
}
}
}
result
}
}