84 lines
2.2 KiB
JavaScript
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;
|
|
}
|
|
|
|
}
|