You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
4.2 KiB

3 weeks ago
import type { EntityId } from '../ecs/EntityId';
import type { Cell } from './Cell';
import type { GridMap } from './GridMap';
/** 空间网格索引(按格子桶加速选敌) */
export class SpatialGrid {
/** 一维桶:index = row * cols + col,空桶为 undefined */
private readonly _buckets: (EntityId[] | undefined)[];
/** 实体当前行;未登记为 -1 */
private readonly _entityRow: number[] = [];
/** 实体当前列;未登记为 -1 */
private readonly _entityCol: number[] = [];
/** query 复用缓冲,仅在下次 query 前有效 */
private readonly _queryOut: EntityId[] = [];
/**
* @param _grid 关联的格子地图
*/
constructor(private _grid: GridMap) {
this._buckets = new Array(_grid.rows * _grid.cols);
}
/**
* @param id 实体 ID
* @param cell 当前所在格子
*/
updateEntity(id: EntityId, cell: Cell): void {
const prevRow = this._entityRow[id];
const prevCol = this._entityCol[id];
if (prevRow === cell.row && prevCol === cell.col) return;
if (prevRow != null && prevRow >= 0) {
this._removeFromBucket(id, prevRow, prevCol);
}
this._entityRow[id] = cell.row;
this._entityCol[id] = cell.col;
this._addToBucket(id, cell.row, cell.col);
}
/** 从空间索引中移除实体 */
removeEntity(id: EntityId): void {
const row = this._entityRow[id];
if (row == null || row < 0) return;
this._removeFromBucket(id, row, this._entityCol[id]);
this._entityRow[id] = -1;
this._entityCol[id] = -1;
}
/**
* @param center 圆心格子
* @param radiusCells 半径(格)
* @returns 范围内实体 ID
*/
queryRadius(center: Cell, radiusCells: number): EntityId[] {
const out = this._queryOut;
out.length = 0;
const rows = this._grid.rows;
const cols = this._grid.cols;
const r = Math.ceil(radiusCells);
const cr = center.row;
const cc = center.col;
for (let dr = -r; dr <= r; dr++) {
const row = cr + dr;
if (row < 0 || row >= rows) continue;
for (let dc = -r; dc <= r; dc++) {
const col = cc + dc;
if (col < 0 || col >= cols) continue;
const dRow = cr - row;
const dCol = cc - col;
if (Math.sqrt(dRow * dRow + dCol * dCol) > radiusCells) continue;
this._appendBucket(out, row * cols + col);
}
}
return out.slice();
}
/**
* @returns 全部已登记实体
*/
queryAll(): EntityId[] {
const out = this._queryOut;
out.length = 0;
const buckets = this._buckets;
for (let i = 0; i < buckets.length; i++) {
this._appendBucket(out, i);
}
return out.slice();
}
/**
* @param out 输出
* @param index 桶下标
*/
private _appendBucket(out: EntityId[], index: number): void {
const list = this._buckets[index];
if (!list) return;
for (let i = 0; i < list.length; i++) out.push(list[i]);
}
/**
* @param id 实体
* @param row 行
* @param col 列
*/
private _addToBucket(id: EntityId, row: number, col: number): void {
if (row < 0 || row >= this._grid.rows || col < 0 || col >= this._grid.cols) return;
const index = row * this._grid.cols + col;
let list = this._buckets[index];
if (!list) {
list = [];
this._buckets[index] = list;
}
for (let i = 0; i < list.length; i++) {
if (list[i] === id) return;
}
list.push(id);
}
/**
* @param id 实体
* @param row 行
* @param col 列
*/
private _removeFromBucket(id: EntityId, row: number, col: number): void {
const index = row * this._grid.cols + col;
const list = this._buckets[index];
if (!list) return;
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === id) list.splice(i, 1);
}
if (list.length === 0) this._buckets[index] = undefined;
}
}