kunpolibrary/src/quadtree/QuadTree.ts
2025-05-27 18:19:41 +08:00

372 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @Author: Gongxh
* @Date: 2024-12-21
* @Description: 树节点
*/
import { Graphics, Intersection2D, rect, Rect } from "cc";
import { Circle } from "./Circle";
import { IShape, ShapeType } from "./IShape";
import { Polygon } from "./Polygon";
// 1|0
// ---
// 2|3
const enum Quadrant {
ONE = 0,
TWO,
THREE,
FOUR,
MORE, // 多个象限
}
const circleCircle = Intersection2D.circleCircle;
const polygonCircle = Intersection2D.polygonCircle;
const polygonPolygon = Intersection2D.polygonPolygon;
/** 两个形状是否碰撞 */
function isCollide(shape1: IShape, shape2: IShape): boolean {
if (shape1.shapeType === ShapeType.CIRCLE) {
if (shape2.shapeType === ShapeType.CIRCLE) {
return circleCircle(shape1.position, (shape1 as Circle).radius * shape1.scale, shape2.position, (shape2 as Circle).radius * shape2.scale);
} else if (shape2.shapeType === ShapeType.BOX || shape2.shapeType === ShapeType.POLYGON) {
return polygonCircle((shape2 as Polygon).points, shape1.position, (shape1 as Circle).radius * shape1.scale);
}
} else if (shape1.shapeType === ShapeType.BOX || shape1.shapeType === ShapeType.POLYGON) {
if (shape2.shapeType === ShapeType.CIRCLE) {
return polygonCircle((shape1 as Polygon).points, shape2.position, (shape2 as Circle).radius * shape2.scale);
} else if (shape2.shapeType === ShapeType.BOX || shape2.shapeType === ShapeType.POLYGON) {
return polygonPolygon((shape2 as Polygon).points, (shape1 as Polygon).points);
}
}
return false;
}
export const QTConfig = {
/** 每个节点(象限)所能包含物体的最大数量 */
MAX_SHAPES: 12,
/** 四叉树的最大深度 */
MAX_LEVELS: 5,
}
export class QuadTree {
/** @internal */
private _graphics: Graphics;
/** @internal */
private _shapes_map: Map<number, IShape[]>; // 根据类型存储形状对象
/** @internal */
private _trees: QuadTree[] = []; // 存储四个子节点
/** @internal */
private _level: number; // 树的深度
/** @internal */
private _bounds: Rect; // 树的外框
/** @internal */
private _ignore_shapes: IShape[] = []; // 不在树中的形状
/**
* 创建一个四叉树
* @param rect 该节点对应的象限在屏幕上的范围
* @param level 该节点的深度根节点的默认深度为0
* @param graphics cc中用于绘制树的绘制组件
*/
constructor(rect: Rect, level: number = 0, graphics: Graphics = undefined) {
this._shapes_map = new Map();
this._trees = [];
this._level = level || 0;
this._bounds = rect;
this._graphics = graphics;
}
/**
* 插入形状
* @param shape 形状数据
* 如果当前节点存在子节点,则检查物体到底属于哪个子节点,如果能匹配到子节点,则将该物体插入到该子节点中
* 如果当前节点不存在子节点,将该物体存储在当前节点。随后,检查当前节点的存储数量,如果超过了最大存储数量,则对当前节点进行划分,划分完成后,将当前节点存储的物体重新分配到四个子节点中。
*/
public insert(shape: IShape): void {
// 如果该节点下存在子节点
if (this._trees.length > 0) {
let quadrant = this._getQuadrant(shape);
if (quadrant !== Quadrant.MORE) {
this._trees[quadrant].insert(shape);
return;
}
}
if (this._level == 0 && !this._isInner(shape, this._bounds)) {
// 插入跟节点并且形状不在根节点的框内,则把形状放入忽略列表中
this._ignore_shapes.push(shape);
} else {
// 存储在当前节点下
this._insert(shape);
// 如果当前节点存储的数量超过了 MAX_OBJECTS并且深度没超过 MAX_LEVELS则继续拆分
if (!this._trees.length && this._size() > QTConfig.MAX_SHAPES && this._level < QTConfig.MAX_LEVELS) {
this._split();
for (const shapes of this._shapes_map.values()) {
let length = shapes.length - 1;
for (let i = length; i >= 0; i--) {
let quadrant = this._getQuadrant(shapes[i]);
if (quadrant !== Quadrant.MORE) {
this._trees[quadrant].insert(shapes.splice(i, 1)[0]);
}
}
}
}
}
}
/** @internal */
private _insert(shape: IShape): void {
if (!this._shapes_map.has(shape.mask)) {
this._shapes_map.set(shape.mask, []);
}
this._shapes_map.get(shape.mask).push(shape);
}
/**
* 检索功能:
* 给出一个物体对象,该函数负责将该物体可能发生碰撞的所有物体选取出来。该函数先查找物体所属的象限,该象限下的物体都是有可能发生碰撞的,然后再递归地查找子象限...
*/
public collide(shape: IShape, tag: number = -1): IShape[] {
let result: IShape[] = [];
if (this._trees.length > 0) {
let quadrant = this._getQuadrant(shape);
if (quadrant === Quadrant.MORE) {
let len = this._trees.length - 1;
for (let i = len; i >= 0; i--) {
result.push(...this._trees[i].collide(shape, tag));
}
} else {
result.push(...this._trees[quadrant].collide(shape, tag));
}
}
for (const key of this._shapes_map.keys()) {
if (!(tag & key)) {
continue;
}
let shapes = this._shapes_map.get(key);
for (const other_shape of shapes) {
if (other_shape.isValid && shape !== other_shape && isCollide(shape, other_shape)) {
result.push(other_shape);
}
}
}
return result;
}
/**
* 动态更新
*/
public update(root?: QuadTree): void {
root = root || this;
let isRoot = (root === this);
isRoot && this.graphicsClear();
this._updateIgnoreShapes(root);
this._updateShapes(root);
// 递归刷新子象限
for (const tree of this._trees) {
tree.update(root);
}
this._removeChildTree();
this.graphicsTreeBound(root);
if (isRoot && this._graphics) {
this._graphics.stroke();
}
}
public clear(): void {
this._level = 0;
this._ignore_shapes.length = 0;
this._shapes_map.clear();
for (const tree of this._trees) {
tree.clear();
}
this._trees.length = 0;
}
/** 当前形状是否包含在象限内 @internal */
private _isInner(shape: IShape, bounds: Rect): boolean {
let rect = shape.getBoundingBox();
return (
rect.xMin * shape.scale + shape.position.x > bounds.xMin &&
rect.xMax * shape.scale + shape.position.x < bounds.xMax &&
rect.yMin * shape.scale + shape.position.y > bounds.yMin &&
rect.yMax * shape.scale + shape.position.y < bounds.yMax
);
}
/**
* 获取形状对应的象限序号,以中心为界限切割:
* @param {Shape} shape 形状
* 右上:象限一
* 左上:象限二
* 左下:象限三
* 右下:象限四
* @internal
*/
private _getQuadrant(shape: IShape): Quadrant {
let bounds = this._bounds;
let rect = shape.getBoundingBox();
let center = bounds.center;
let onTop = rect.yMin * shape.scale + shape.position.y > center.y;
let onBottom = rect.yMax * shape.scale + shape.position.y < center.y;
let onLeft = rect.xMax * shape.scale + shape.position.x < center.x;
let onRight = rect.xMin * shape.scale + shape.position.x > center.x;
if (onTop) {
if (onRight) {
return Quadrant.ONE;
} else if (onLeft) {
return Quadrant.TWO;
}
} else if (onBottom) {
if (onLeft) {
return Quadrant.THREE;
} else if (onRight) {
return Quadrant.FOUR;
}
}
return Quadrant.MORE; // 跨越多个象限
}
/**
* 划分函数
* 如果某一个象限节点内存储的物体数量超过了MAX_OBJECTS最大数量
* 则需要对这个节点进行划分
* 它的工作就是将一个象限看作一个屏幕,将其划分为四个子象限
* @internal
*/
private _split(): void {
let bounds = this._bounds;
let x = bounds.x;
let y = bounds.y;
let halfwidth = bounds.width * 0.5;
let halfheight = bounds.height * 0.5;
let nextLevel = this._level + 1;
this._trees.push(
new QuadTree(rect(bounds.center.x, bounds.center.y, halfwidth, halfheight), nextLevel, this._graphics),
new QuadTree(rect(x, bounds.center.y, halfwidth, halfheight), nextLevel, this._graphics),
new QuadTree(rect(x, y, halfwidth, halfheight), nextLevel, this._graphics),
new QuadTree(rect(bounds.center.x, y, halfwidth, halfheight), nextLevel, this._graphics)
);
}
/** 删除子树 @internal */
private _removeChildTree(): void {
if (this._trees.length > 0) {
if (this._totalSize() <= 0) {
this._trees.length = 0;
}
}
}
/** 更新忽略掉的形状 @internal */
private _updateIgnoreShapes(root: QuadTree): void {
let shapes = this._ignore_shapes;
let lastIndex = shapes.length - 1;
let index = 0;
while (index < lastIndex) {
let shape = shapes[index];
if (!shape.isValid) {
if (index !== lastIndex) {
shapes[index] = shapes[lastIndex];
}
shapes.pop();
lastIndex--;
continue;
}
if (this._isInner(shape, this._bounds)) {
if (index !== lastIndex) {
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
}
root.insert(shapes.pop());
} else {
index++;
}
}
}
/** 更新有效的形状 @internal */
private _updateShapes(root: QuadTree): void {
for (const shapes of this._shapes_map.values()) {
let lastIndex = shapes.length - 1;
let index = 0;
while (index <= lastIndex) {
let shape = shapes[index];
if (!shape.isValid) {
if (index !== lastIndex) {
shapes[index] = shapes[lastIndex];
}
shapes.pop();
lastIndex--;
continue;
}
if (!this._isInner(shape, this._bounds)) {
// 如果矩形不属于该象限,则将该矩形重新插入根节点
if (index !== lastIndex) {
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
}
root.insert(shapes.pop());
lastIndex--;
} else if (this._trees.length > 0) {
// 如果矩形属于该象限且该象限具有子象限,则将该矩形安插到子象限中
let quadrant = this._getQuadrant(shape);
if (quadrant !== Quadrant.MORE) {
if (index !== lastIndex) {
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
}
this._trees[quadrant].insert(shapes.pop());
lastIndex--;
} else {
index++;
}
} else {
index++;
}
}
}
}
/** 当前树以及子树中所有的形状数量 @internal */
private _totalSize(): number {
let size = this._size();
for (const tree of this._trees) {
size += tree._totalSize();
}
return size;
}
/** 当前树中所有的形状数量 @internal */
private _size(): number {
let size = 0;
for (const shapes of this._shapes_map.values()) {
size += shapes.length;
}
return size + this._ignore_shapes.length;
}
/** 画出当前树的边界 @internal */
private graphicsTreeBound(root: QuadTree): void {
if (!this._graphics) {
return;
}
if (this._trees.length > 0) {
this._graphics.moveTo(this._bounds.x, this._bounds.center.y);
this._graphics.lineTo(this._bounds.x + this._bounds.width, this._bounds.center.y);
this._graphics.moveTo(this._bounds.center.x, this._bounds.y);
this._graphics.lineTo(this._bounds.center.x, this._bounds.y + this._bounds.height);
}
if (this == root) {
this._graphics.moveTo(this._bounds.xMin, this._bounds.yMin);
this._graphics.lineTo(this._bounds.xMax, this._bounds.yMin);
this._graphics.lineTo(this._bounds.xMax, this._bounds.yMax);
this._graphics.lineTo(this._bounds.xMin, this._bounds.yMax);
this._graphics.lineTo(this._bounds.xMin, this._bounds.yMin);
}
}
/** 清除绘制 @internal */
private graphicsClear(): void {
this._graphics && this._graphics.clear();
}
}