UNPKG

@dill-pixel/plugin-snap-physics

Version:

Snap Physics

169 lines (147 loc) 4.83 kB
import { Graphics, Rectangle } from 'pixi.js'; import { Entity } from './Entity'; import { System } from './System'; import { SpatialHashGridFilter } from './types'; type GridKey = string; export class SpatialHashGrid { private cells: Map<GridKey, Entity[]> = new Map(); constructor(cellSize: number, insertEntities: boolean = false) { this._cellSize = cellSize; if (insertEntities) { System.all.forEach((entity) => this.insert(entity)); } } private _cellSize: number; get cellSize(): number { return this._cellSize; } set cellSize(size: number) { this._cellSize = size; this.cells.clear(); this.updateAll(); } destroy() { this.cells.clear(); } insert(entity: Entity): void { const bounds = entity.boundingRect; const startX = Math.floor(bounds.x / this._cellSize); const startY = Math.floor(bounds.y / this._cellSize); const endX = Math.floor((bounds.x + bounds.width) / this._cellSize); const endY = Math.floor((bounds.y + bounds.height) / this._cellSize); for (let x = startX; x <= endX; x++) { for (let y = startY; y <= endY; y++) { const key = this.getGridKey(x, y); if (!this.cells.has(key)) { this.cells.set(key, []); } const cellEntities = this.cells.get(key)!; if (!cellEntities.includes(entity)) { cellEntities.push(entity); } } } } remove(entity: Entity): void { const keysToRemove: GridKey[] = []; this.cells.forEach((entities, key) => { const index = entities.indexOf(entity); if (index !== -1) { entities.splice(index, 1); if (entities.length === 0) { keysToRemove.push(key); } } }); keysToRemove.forEach((key) => this.cells.delete(key)); } query<T extends Entity = Entity>( range: Rectangle, filter?: SpatialHashGridFilter, dx: number = 0, dy: number = 0, exclude?: Entity | Entity[], debug?: boolean, ): Set<T> { let uidsToExclude: number[] = []; if (exclude) { if (Array.isArray(exclude)) { uidsToExclude = exclude.map((e) => e.uid); } else { uidsToExclude = [exclude.uid]; } } void debug; const expandedRange = new Rectangle( range.x - Math.abs(dx), range.y - Math.abs(dy), range.width + 2 * Math.abs(dx), range.height + 2 * Math.abs(dy), ); const foundEntities = new Set<T>(); const startX = Math.floor(Math.min(expandedRange.x, expandedRange.x + expandedRange.width) / this._cellSize); const startY = Math.floor(Math.min(expandedRange.y, expandedRange.y + expandedRange.height) / this._cellSize); const endX = Math.floor(Math.max(expandedRange.x, expandedRange.x + expandedRange.width) / this._cellSize); const endY = Math.floor(Math.max(expandedRange.y, expandedRange.y + expandedRange.height) / this._cellSize); for (let x = startX; x <= endX; x++) { for (let y = startY; y <= endY; y++) { const key = this.getGridKey(x, y); const cellEntities = this.cells.get(key); if (cellEntities) { cellEntities.forEach((entity) => { if (!uidsToExclude.includes(entity.uid)) { if (filter === undefined || this.matchesFilter(entity, filter)) { foundEntities.add(entity as T); } } }); } } } return foundEntities; } private matchesFilter(entity: Entity, filter: SpatialHashGridFilter): boolean { switch (typeof filter) { case 'string': return ( filter === entity.type || (filter === 'solid' && entity.isSolid) || (filter === 'actor' && entity.isActor) || (filter === 'sensor' && entity.isSensor) ); case 'object': return Array.isArray(filter) && filter.includes(entity.type); case 'function': return filter(entity); default: return false; } } updateAll() { System.all.forEach((entity) => this.updateEntity(entity)); } updateEntity(entity: Entity): void { this.remove(entity); this.insert(entity); } draw(gfx: Graphics) { const rects = this._getDebugRects(); rects.forEach((rect) => { gfx.rect(rect.left, rect.top, rect.width, rect.height); gfx.stroke({ color: 0x00ff00, pixelLine: true }); }); } private _getDebugRects() { const rects: Rectangle[] = []; this.cells.forEach((_cell, key) => { const [x, y] = key.split(':').map(Number); if (_cell.length) { rects.push(new Rectangle(x * this._cellSize, y * this._cellSize, this._cellSize, this._cellSize)); } }); return rects; } private getGridKey(cellX: number, cellY: number): GridKey { return `${cellX}:${cellY}`; } }