import {RESOLUTION} from '@/constants.js' import {System} from '@/ecs/index.js'; class SpatialHash { constructor({x, y}) { this.area = {x, y}; this.chunkSize = {x: RESOLUTION.x / 2, y: RESOLUTION.y / 2}; this.chunks = Array(Math.ceil(this.area.x / this.chunkSize.x)) .fill(0) .map(() => ( Array(Math.ceil(this.area.y / this.chunkSize.y)) .fill(0) .map(() => []) )); this.data = {}; } clamp(x, y) { return [ Math.max(0, Math.min(x, this.area.x - 1)), Math.max(0, Math.min(y, this.area.y - 1)) ]; } chunkIndex(x, y) { const [cx, cy] = this.clamp(x, y); return [ Math.floor(cx / this.chunkSize.x), Math.floor(cy / this.chunkSize.y), ]; } remove(datum) { if (datum in this.data) { for (const [cx, cy] of this.data[datum]) { const chunk = this.chunks[cx][cy]; chunk.splice(chunk.indexOf(datum), 1); } } this.data[datum] = []; } update({x0, x1, y0, y1}, datum) { this.remove(datum); for (const [x, y] of [[x0, y0], [x0, y1], [x1, y0], [x1, y1]]) { const [cx, cy] = this.chunkIndex(x, y); this.data[datum].push([cx, cy]); this.chunks[cx][cy].push(datum); } } } export default class UpdateSpatialHash extends System { deindex(entities) { super.deindex(entities); for (const id of entities) { this.hash.remove(id); } } reindex(entities) { for (const id of entities) { if (1 === id) { this.hash = new SpatialHash(this.ecs.get(1).AreaSize); } } super.reindex(entities); for (const id of entities) { this.updateHash(this.ecs.get(id)); } } updateHash(entity) { if (!entity.VisibleAabb) { return; } this.hash.update(entity.VisibleAabb, entity.id); } tick() { for (const entity of this.ecs.changed(['VisibleAabb'])) { this.updateHash(entity); } } nearby(entity) { const [cx0, cy0] = this.hash.chunkIndex( entity.Position.x - RESOLUTION.x * 0.75, entity.Position.y - RESOLUTION.x * 0.75, ); const [cx1, cy1] = this.hash.chunkIndex( entity.Position.x + RESOLUTION.x * 0.75, entity.Position.y + RESOLUTION.x * 0.75, ); const nearby = new Set(); for (let cy = cy0; cy <= cy1; ++cy) { for (let cx = cx0; cx <= cx1; ++cx) { this.hash.chunks[cx][cy].forEach((id) => { nearby.add(this.ecs.get(id)); }); } } return nearby; } }