2020-06-11 00:03:26 +08:00
|
|
|
|
class SpatialHash {
|
2020-06-15 10:42:06 +08:00
|
|
|
|
public gridBounds: Rectangle = new Rectangle();
|
|
|
|
|
|
|
2020-06-12 08:47:13 +08:00
|
|
|
|
private _raycastParser: RaycastResultParser;
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/** 散列中每个单元格的大小 */
|
2020-06-12 08:47:13 +08:00
|
|
|
|
private _cellSize: number;
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/** 1除以单元格大小。缓存结果,因为它被大量使用。 */
|
2020-06-12 08:47:13 +08:00
|
|
|
|
private _inverseCellSize: number;
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/** 缓存的循环用于重叠检查 */
|
|
|
|
|
|
private _overlapTestCircle: Circle = new Circle(0);
|
|
|
|
|
|
/** 用于返回冲突信息的共享HashSet */
|
2020-06-12 08:47:13 +08:00
|
|
|
|
private _tempHashSet: Collider[] = [];
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/** 保存所有数据的字典 */
|
2020-06-12 08:47:13 +08:00
|
|
|
|
private _cellDict: NumberDictionary = new NumberDictionary();
|
|
|
|
|
|
|
2020-06-16 16:35:17 +08:00
|
|
|
|
constructor(cellSize: number = 100) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
this._cellSize = cellSize;
|
|
|
|
|
|
this._inverseCellSize = 1 / this._cellSize;
|
|
|
|
|
|
this._raycastParser = new RaycastResultParser();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 从SpatialHash中删除对象
|
|
|
|
|
|
* @param collider
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public remove(collider: Collider) {
|
2020-06-15 10:42:06 +08:00
|
|
|
|
let bounds = collider.registeredPhysicsBounds;
|
|
|
|
|
|
let p1 = this.cellCoords(bounds.x, bounds.y);
|
|
|
|
|
|
let p2 = this.cellCoords(bounds.right, bounds.bottom);
|
|
|
|
|
|
|
2020-06-16 16:35:17 +08:00
|
|
|
|
for (let x = p1.x; x <= p2.x; x++) {
|
|
|
|
|
|
for (let y = p1.y; y <= p2.y; y++) {
|
2020-07-08 18:12:17 +08:00
|
|
|
|
// 单元格应该始终存在,因为这个碰撞器应该在所有查询的单元格中
|
2020-06-15 10:42:06 +08:00
|
|
|
|
let cell = this.cellAtPosition(x, y);
|
|
|
|
|
|
if (!cell)
|
|
|
|
|
|
console.error(`removing Collider [${collider}] from a cell that it is not present in`);
|
|
|
|
|
|
else
|
|
|
|
|
|
cell.remove(collider);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 将对象添加到SpatialHash
|
|
|
|
|
|
* @param collider
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public register(collider: Collider) {
|
2020-06-15 10:42:06 +08:00
|
|
|
|
let bounds = collider.bounds;
|
|
|
|
|
|
collider.registeredPhysicsBounds = bounds;
|
|
|
|
|
|
let p1 = this.cellCoords(bounds.x, bounds.y);
|
|
|
|
|
|
let p2 = this.cellCoords(bounds.right, bounds.bottom);
|
2020-06-16 11:59:40 +08:00
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
// 更新边界以跟踪网格大小
|
2020-07-09 14:16:10 +08:00
|
|
|
|
if (!this.gridBounds.contains(p1.x, p1.y)) {
|
2020-06-16 11:59:40 +08:00
|
|
|
|
this.gridBounds = RectangleExt.union(this.gridBounds, p1);
|
|
|
|
|
|
}
|
2020-06-16 16:35:17 +08:00
|
|
|
|
|
2020-07-09 14:16:10 +08:00
|
|
|
|
if (!this.gridBounds.contains(p2.x, p2.y)) {
|
2020-06-16 11:59:40 +08:00
|
|
|
|
this.gridBounds = RectangleExt.union(this.gridBounds, p2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-16 16:35:17 +08:00
|
|
|
|
for (let x = p1.x; x <= p2.x; x++) {
|
|
|
|
|
|
for (let y = p1.y; y <= p2.y; y++) {
|
2020-07-08 18:12:17 +08:00
|
|
|
|
// 如果没有单元格,我们需要创建它
|
2020-06-16 11:59:40 +08:00
|
|
|
|
let c = this.cellAtPosition(x, y, true);
|
2020-07-22 20:07:14 +08:00
|
|
|
|
if (c.indexOf(collider) == -1)
|
|
|
|
|
|
c.push(collider);
|
2020-06-16 11:59:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-06-15 10:42:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-21 10:27:15 +08:00
|
|
|
|
public clear(){
|
|
|
|
|
|
this._cellDict.clear();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取位于指定圆内的所有碰撞器
|
|
|
|
|
|
* @param circleCenter
|
|
|
|
|
|
* @param radius
|
|
|
|
|
|
* @param results
|
|
|
|
|
|
* @param layerMask
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public overlapCircle(circleCenter: Vector2, radius: number, results: Collider[], layerMask) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
let bounds = new Rectangle(circleCenter.x - radius, circleCenter.y - radius, radius * 2, radius * 2);
|
|
|
|
|
|
|
|
|
|
|
|
this._overlapTestCircle.radius = radius;
|
2020-07-22 20:07:14 +08:00
|
|
|
|
// this._overlapTestCircle.position = circleCenter;
|
2020-06-17 20:40:56 +08:00
|
|
|
|
|
2020-06-11 00:03:26 +08:00
|
|
|
|
let resultCounter = 0;
|
2020-07-07 18:54:19 +08:00
|
|
|
|
let aabbBroadphaseResult = this.aabbBroadphase(bounds, null, layerMask);
|
|
|
|
|
|
bounds = aabbBroadphaseResult.bounds;
|
|
|
|
|
|
let potentials = aabbBroadphaseResult.tempHashSet;
|
2020-06-17 20:40:56 +08:00
|
|
|
|
for (let i = 0; i < potentials.length; i++) {
|
2020-06-16 16:35:17 +08:00
|
|
|
|
let collider = potentials[i];
|
2020-06-17 20:40:56 +08:00
|
|
|
|
if (collider instanceof BoxCollider) {
|
2020-06-16 16:35:17 +08:00
|
|
|
|
results[resultCounter] = collider;
|
2020-06-17 20:40:56 +08:00
|
|
|
|
resultCounter++;
|
2020-07-09 15:11:30 +08:00
|
|
|
|
} else if (collider instanceof CircleCollider) {
|
|
|
|
|
|
if (collider.shape.overlaps(this._overlapTestCircle)){
|
|
|
|
|
|
results[resultCounter] = collider;
|
|
|
|
|
|
resultCounter ++;
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if(collider instanceof PolygonCollider) {
|
|
|
|
|
|
if (collider.shape.overlaps(this._overlapTestCircle)){
|
|
|
|
|
|
results[resultCounter] = collider;
|
|
|
|
|
|
resultCounter ++;
|
|
|
|
|
|
}
|
2020-06-17 20:40:56 +08:00
|
|
|
|
} else {
|
2020-06-16 16:35:17 +08:00
|
|
|
|
throw new Error("overlapCircle against this collider type is not implemented!");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-09 15:11:30 +08:00
|
|
|
|
// 如果我们所有的结果数据有了则返回
|
2020-06-12 08:47:13 +08:00
|
|
|
|
if (resultCounter == results.length)
|
|
|
|
|
|
return resultCounter;
|
2020-06-16 16:35:17 +08:00
|
|
|
|
}
|
2020-06-11 00:03:26 +08:00
|
|
|
|
|
|
|
|
|
|
return resultCounter;
|
|
|
|
|
|
}
|
2020-06-12 08:47:13 +08:00
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 返回边框与单元格相交的所有对象
|
|
|
|
|
|
* @param bounds
|
|
|
|
|
|
* @param excludeCollider
|
|
|
|
|
|
* @param layerMask
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public aabbBroadphase(bounds: Rectangle, excludeCollider: Collider, layerMask: number) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
this._tempHashSet.length = 0;
|
|
|
|
|
|
|
|
|
|
|
|
let p1 = this.cellCoords(bounds.x, bounds.y);
|
|
|
|
|
|
let p2 = this.cellCoords(bounds.right, bounds.bottom);
|
|
|
|
|
|
|
2020-06-16 16:35:17 +08:00
|
|
|
|
for (let x = p1.x; x <= p2.x; x++) {
|
|
|
|
|
|
for (let y = p1.y; y <= p2.y; y++) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
let cell = this.cellAtPosition(x, y);
|
|
|
|
|
|
if (!cell)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
// 当cell不为空。循环并取回所有碰撞器
|
2020-06-16 16:35:17 +08:00
|
|
|
|
for (let i = 0; i < cell.length; i++) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
let collider = cell[i];
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
// 如果它是自身或者如果它不匹配我们的层掩码 跳过这个碰撞器
|
2020-06-12 08:47:13 +08:00
|
|
|
|
if (collider == excludeCollider || !Flags.isFlagSet(layerMask, collider.physicsLayer))
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2020-06-18 09:06:59 +08:00
|
|
|
|
if (bounds.intersects(collider.bounds)){
|
|
|
|
|
|
if (this._tempHashSet.indexOf(collider) == -1)
|
|
|
|
|
|
this._tempHashSet.push(collider);
|
|
|
|
|
|
}
|
2020-06-12 08:47:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-07 18:54:19 +08:00
|
|
|
|
return {tempHashSet: this._tempHashSet, bounds: bounds};
|
2020-06-12 08:47:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取世界空间x,y值的单元格。
|
|
|
|
|
|
* 如果单元格为空且createCellIfEmpty为true,则会创建一个新的单元格
|
|
|
|
|
|
* @param x
|
|
|
|
|
|
* @param y
|
|
|
|
|
|
* @param createCellIfEmpty
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
private cellAtPosition(x: number, y: number, createCellIfEmpty: boolean = false) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
let cell: Collider[] = this._cellDict.tryGetValue(x, y);
|
2020-06-16 16:35:17 +08:00
|
|
|
|
if (!cell) {
|
|
|
|
|
|
if (createCellIfEmpty) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
cell = [];
|
|
|
|
|
|
this._cellDict.add(x, y, cell);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return cell;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取单元格的x,y值作为世界空间的x,y值
|
|
|
|
|
|
* @param x
|
|
|
|
|
|
* @param y
|
|
|
|
|
|
*/
|
2020-07-08 15:15:15 +08:00
|
|
|
|
private cellCoords(x: number, y: number): Vector2 {
|
|
|
|
|
|
return new Vector2(Math.floor(x * this._inverseCellSize), Math.floor(y * this._inverseCellSize));
|
2020-06-12 08:47:13 +08:00
|
|
|
|
}
|
2020-07-09 16:16:04 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* debug绘制空间散列的内容
|
|
|
|
|
|
* @param secondsToDisplay
|
|
|
|
|
|
* @param textScale
|
|
|
|
|
|
*/
|
|
|
|
|
|
public debugDraw(secondsToDisplay: number, textScale: number = 1){
|
|
|
|
|
|
for (let x = this.gridBounds.x; x <= this.gridBounds.right; x ++){
|
|
|
|
|
|
for (let y = this.gridBounds.y; y <= this.gridBounds.bottom; y ++){
|
|
|
|
|
|
let cell = this.cellAtPosition(x, y);
|
|
|
|
|
|
if (cell && cell.length > 0)
|
|
|
|
|
|
this.debugDrawCellDetails(x, y, cell.length, secondsToDisplay, textScale);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private debugDrawCellDetails(x: number, y: number, cellCount: number, secondsToDisplay = 0.5, textScale = 1){
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2020-06-12 08:47:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class RaycastResultParser {
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 包装一个Unit32,列表碰撞器字典
|
|
|
|
|
|
* 它的主要目的是将int、int x、y坐标散列到单个Uint32键中,使用O(1)查找。
|
|
|
|
|
|
*/
|
2020-06-12 08:47:13 +08:00
|
|
|
|
class NumberDictionary {
|
2020-07-20 16:38:49 +08:00
|
|
|
|
private _store: Map<string, Collider[]> = new Map<string, Collider[]>();
|
2020-06-12 08:47:13 +08:00
|
|
|
|
|
2020-06-16 20:22:22 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 根据x和y值计算并返回散列键
|
|
|
|
|
|
* @param x
|
|
|
|
|
|
* @param y
|
|
|
|
|
|
*/
|
2020-07-20 16:38:49 +08:00
|
|
|
|
private getKey(x: number, y: number): string {
|
|
|
|
|
|
return Long.fromNumber(x).shiftLeft(32).or(Long.fromNumber(y, false)).toString();
|
2020-06-12 08:47:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public add(x: number, y: number, list: Collider[]) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
this._store.set(this.getKey(x, y), list);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 使用蛮力方法从字典存储列表中移除碰撞器
|
|
|
|
|
|
* @param obj
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public remove(obj: Collider) {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
this._store.forEach(list => {
|
|
|
|
|
|
if (list.contains(obj))
|
|
|
|
|
|
list.remove(obj);
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public tryGetValue(x: number, y: number): Collider[] {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
return this._store.get(this.getKey(x, y));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-08 18:12:17 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 清除字典数据
|
|
|
|
|
|
*/
|
2020-06-16 16:35:17 +08:00
|
|
|
|
public clear() {
|
2020-06-12 08:47:13 +08:00
|
|
|
|
this._store.clear();
|
|
|
|
|
|
}
|
2020-06-11 00:03:26 +08:00
|
|
|
|
}
|