import GameEntity from '../domain/entities/game/GameEntity'; export class SpatialCell { readonly x: number; readonly y: number; entities: Map = new Map(); neighbors: SpatialCell[] = []; constructor(x: number, y: number) { this.x = x; this.y = y; } } export default class SpatialGrid { private readonly cellSize: number; private readonly cells: Map = new Map(); constructor(cellSize: number) { this.cellSize = cellSize; } private key(cx: number, cy: number): string { return `${cx}:${cy}`; } private getCellCoords(x: number, y: number) { return { cx: Math.floor(x / this.cellSize), cy: Math.floor(y / this.cellSize), }; } private getOrCreateCell(cx: number, cy: number): SpatialCell { const k = this.key(cx, cy); let cell = this.cells.get(k); if (!cell) { cell = new SpatialCell(cx, cy); this.cells.set(k, cell); this.linkNeighbors(cell); } return cell; } private linkNeighbors(cell: SpatialCell) { for (let dx = -1; dx <= 1; dx++) { for (let dy = -1; dy <= 1; dy++) { const nx = cell.x + dx; const ny = cell.y + dy; const neighbor = this.cells.get(this.key(nx, ny)); if (!neighbor) continue; if (!cell.neighbors.includes(neighbor)) { cell.neighbors.push(neighbor); } if (!neighbor.neighbors.includes(cell)) { neighbor.neighbors.push(cell); } } } } insert(entity: GameEntity) { const { cx, cy } = this.getCellCoords(entity.getPositionX(), entity.getPositionY()); const cell = this.getOrCreateCell(cx, cy); cell.entities.set(entity.getVirtualId(), entity); entity.setCell(cell); } remove(entity: GameEntity) { const cell = entity.getCell(); if (!cell) return; cell.entities.delete(entity.getVirtualId()); entity.setCell(null); } updatePosition(entity: GameEntity) { const oldCell = entity.getCell(); const { x, y } = entity.getTargetPosition(); const { cx, cy } = this.getCellCoords(x, y); const newCell = this.getOrCreateCell(cx, cy); if (oldCell === newCell) return; if (oldCell) { oldCell.entities.delete(entity.getVirtualId()); } newCell.entities.set(entity.getVirtualId(), entity); entity.setCell(newCell); } queryAround(entity: GameEntity, radius: number, filter: number | null = null) { const result = new Map(); const cell = entity.getCell(); if (!cell) return result; const { x, y } = entity.getTargetPosition(); for (const neighbor of cell.neighbors) { for (const other of neighbor.entities.values()) { if (filter !== null && other.getEntityType() !== filter) continue; const dx = other.getPositionX() - x; const dy = other.getPositionY() - y; if (dx * dx + dy * dy <= radius * radius) { result.set(other.getVirtualId(), other); } } } return result; } }