silphius/app/util/spatial-hash.js
2024-07-03 16:13:14 -05:00

84 lines
2.2 KiB
JavaScript

import {clamp, intersects} from '@/util/math.js';
export default class SpatialHash {
constructor({x, y}) {
this.area = {x, y};
this.chunkSize = {x: 64, y: 64};
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(() => new Map())
));
this.data = new Map();
}
chunkIndex(x, y) {
return {
x: clamp(Math.floor(x / this.chunkSize.x), 0, this.chunks.length - 1),
y: clamp(Math.floor(y / this.chunkSize.y), 0, this.chunks[0].length - 1),
};
}
remove(datum) {
if (!this.data.has(datum)) {
return;
}
for (const {x, y} of this.data.get(datum).chunks) {
this.chunks[x][y].delete(datum);
}
this.data.delete(datum);
}
update({x0, x1, y0, y1}, datum) {
this.remove(datum);
const [sx0, sx1] = x0 < x1 ? [x0, x1] : [x1, x0];
const [sy0, sy1] = y0 < y1 ? [y0, y1] : [y1, y0];
const {x: cx0, y: cy0} = this.chunkIndex(sx0, sy0);
const {x: cx1, y: cy1} = this.chunkIndex(sx1, sy1);
const chunks = [];
for (let iy = cy0; iy <= cy1; ++iy) {
for (let ix = cx0; ix <= cx1; ++ix) {
const chunk = this.chunks[ix][iy];
if (!chunk.has(datum)) {
chunk.set(datum, true);
}
chunks.push({x: ix, y: iy});
}
}
this.data.set(
datum,
{
bounds: {x0: sx0, x1: sx1, y0: sy0, y1: sy1},
chunks,
},
);
}
within(query) {
const {x0, x1, y0, y1} = query;
const [sx0, sx1] = x0 < x1 ? [x0, x1] : [x1, x0];
const [sy0, sy1] = y0 < y1 ? [y0, y1] : [y1, y0];
const {x: cx0, y: cy0} = this.chunkIndex(sx0, sy0);
const {x: cx1, y: cy1} = this.chunkIndex(sx1, sy1);
const candidates = new Set();
const within = new Set();
for (let cy = cy0; cy <= cy1; ++cy) {
for (let cx = cx0; cx <= cx1; ++cx) {
for (const [datum] of this.chunks[cx][cy]) {
candidates.add(datum);
}
}
}
for (const datum of candidates) {
if (intersects(this.data.get(datum).bounds, query)) {
within.add(datum);
}
}
return within;
}
}