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
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;
|
||
|
|
}
|
||
|
|
}
|