@tsparticles/engine
Version:
Easily create highly customizable particle, confetti and fireworks animations and use them as animated backgrounds for your website. Ready to use components available also for React, Vue.js (2.x and 3.x), Angular, Svelte, jQuery, Preact, Riot.js, Inferno.
125 lines (124 loc) • 3.91 kB
JavaScript
import { Circle, Rectangle } from "./Ranges.js";
export class SpatialHashGrid {
#cells = new Map();
#cellSize;
#circlePool = [];
#circlePoolIdx;
#pendingCellSize;
#rectanglePool = [];
#rectanglePoolIdx;
constructor(cellSize) {
this.#cellSize = cellSize;
this.#circlePoolIdx = 0;
this.#rectanglePoolIdx = 0;
}
get cellSize() {
return this.#cellSize;
}
set cellSize(value) {
this.#pendingCellSize = value;
}
clear() {
this.#cells.clear();
const pendingCellSize = this.#pendingCellSize;
if (pendingCellSize) {
this.#cellSize = pendingCellSize;
}
this.#pendingCellSize = undefined;
}
insert(particle) {
const { x, y } = particle.getPosition(), key = this.#cellKeyFromCoords(x, y);
if (!this.#cells.has(key)) {
this.#cells.set(key, []);
}
this.#cells.get(key)?.push(particle);
}
query(range, check, out = []) {
const bounds = this.#getRangeBounds(range);
if (!bounds) {
return out;
}
const minCellX = Math.floor(bounds.minX / this.#cellSize), maxCellX = Math.floor(bounds.maxX / this.#cellSize), minCellY = Math.floor(bounds.minY / this.#cellSize), maxCellY = Math.floor(bounds.maxY / this.#cellSize);
for (let cx = minCellX; cx <= maxCellX; cx++) {
for (let cy = minCellY; cy <= maxCellY; cy++) {
this.#queryCell(cx, cy, range, check, out);
}
}
return out;
}
queryCircle(position, radius, check, out = []) {
const circle = this.#acquireCircle(position.x, position.y, radius);
try {
return this.query(circle, check, out);
}
finally {
this.#circlePoolIdx--;
}
}
queryRectangle(position, size, check, out = []) {
const rect = this.#acquireRectangle(position.x, position.y, size.width, size.height);
try {
return this.query(rect, check, out);
}
finally {
this.#rectanglePoolIdx--;
}
}
#acquireCircle(x, y, r) {
const idx = this.#circlePoolIdx++;
let circle = this.#circlePool[idx];
if (!circle) {
circle = new Circle(x, y, r);
this.#circlePool[idx] = circle;
}
return circle.reset(x, y, r);
}
#acquireRectangle(x, y, w, h) {
const idx = this.#rectanglePoolIdx++;
let rectangle = this.#rectanglePool[idx];
if (!rectangle) {
rectangle = new Rectangle(x, y, w, h);
this.#rectanglePool[idx] = rectangle;
}
return rectangle.reset(x, y, w, h);
}
#cellKeyFromCoords(x, y) {
const cellX = Math.floor(x / this.#cellSize), cellY = Math.floor(y / this.#cellSize);
return `${cellX}_${cellY}`;
}
#getRangeBounds(range) {
if (range instanceof Circle) {
const r = range.radius, { x, y } = range.position;
return {
minX: x - r,
maxX: x + r,
minY: y - r,
maxY: y + r,
};
}
if (range instanceof Rectangle) {
const { x, y } = range.position, { width, height } = range.size;
return {
minX: x,
maxX: x + width,
minY: y,
maxY: y + height,
};
}
return null;
}
#queryCell(cx, cy, range, check, out) {
const cellParticles = this.#cells.get(`${cx}_${cy}`);
if (!cellParticles) {
return;
}
for (const p of cellParticles) {
if (check && !check(p)) {
continue;
}
if (range.contains(p.getPosition())) {
out.push(p);
}
}
}
}