mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-06-01 07:04:28 +00:00
优化四叉树性能
This commit is contained in:
parent
91f1dbc355
commit
1b73738c39
@ -4,6 +4,7 @@
|
||||
* @Description: 矩形
|
||||
*/
|
||||
import { v2, Vec2 } from "cc";
|
||||
import { ShapeType } from "./IShape";
|
||||
import { Polygon } from "./Polygon";
|
||||
|
||||
// 3|2
|
||||
@ -12,6 +13,11 @@ import { Polygon } from "./Polygon";
|
||||
// 矩形的四个点
|
||||
|
||||
export class Box extends Polygon {
|
||||
|
||||
public get shapeType(): ShapeType {
|
||||
return ShapeType.BOX;
|
||||
}
|
||||
|
||||
constructor(x: number, y: number, width: number, height: number, tag: number = -1) {
|
||||
let points: Vec2[] = new Array(4);
|
||||
points[0] = v2(x, y);
|
||||
|
@ -5,20 +5,26 @@
|
||||
*/
|
||||
|
||||
import { Rect } from "cc";
|
||||
import { ShapeType } from "./IShape";
|
||||
import { Shape } from "./Shape";
|
||||
|
||||
export class Circle extends Shape {
|
||||
public radius: number; // 半径
|
||||
|
||||
public get shapeType(): ShapeType {
|
||||
return ShapeType.CIRCLE;
|
||||
}
|
||||
|
||||
constructor(radius: number, tag: number = -1) {
|
||||
super(tag);
|
||||
this.radius = radius;
|
||||
this.boundingBox.x = -this.radius;
|
||||
this.boundingBox.y = -this.radius;
|
||||
this.boundingBox.width = this.radius * 2;
|
||||
this.boundingBox.height = this.radius * 2;
|
||||
this._boundingBox.x = -this.radius;
|
||||
this._boundingBox.y = -this.radius;
|
||||
this._boundingBox.width = this.radius * 2;
|
||||
this._boundingBox.height = this.radius * 2;
|
||||
}
|
||||
|
||||
public getBoundingBox(): Rect {
|
||||
return this.boundingBox;
|
||||
return this._boundingBox;
|
||||
}
|
||||
}
|
32
src/quadtree/IShape.ts
Normal file
32
src/quadtree/IShape.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @Author: Gongxh
|
||||
* @Date: 2025-05-27
|
||||
* @Description:
|
||||
*/
|
||||
|
||||
import { Rect, Vec2 } from "cc";
|
||||
|
||||
export enum ShapeType {
|
||||
CIRCLE = 1,
|
||||
BOX = 2,
|
||||
POLYGON = 3,
|
||||
}
|
||||
|
||||
export interface IShape {
|
||||
/** 形状类型 */
|
||||
get shapeType(): ShapeType;
|
||||
/** 形状掩码 @internal */
|
||||
get mask(): number;
|
||||
/** 是否有效 @internal */
|
||||
|
||||
get isValid(): boolean;
|
||||
get position(): Vec2;
|
||||
get scale(): number;
|
||||
get rotation(): number;
|
||||
/** 获取包围盒 */
|
||||
getBoundingBox(): Rect;
|
||||
setPosition(x: number, y: number): void;
|
||||
setScale(value: number): void;
|
||||
setRotation(angle: number): void;
|
||||
destroy(): void;
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Rect, v2, Vec2 } from "cc";
|
||||
import { ShapeType } from "./IShape";
|
||||
import { Shape } from "./Shape";
|
||||
|
||||
|
||||
@ -30,6 +31,11 @@ function rotate(radians: number, x: number, y: number): Vec2 {
|
||||
export class Polygon extends Shape {
|
||||
protected _points: Vec2[] = []; // 多边形
|
||||
protected _realPoints: Vec2[];
|
||||
|
||||
public get shapeType(): ShapeType {
|
||||
return ShapeType.POLYGON;
|
||||
}
|
||||
|
||||
constructor(points: Vec2[], tag: number = -1) {
|
||||
super(tag);
|
||||
this._points = points;
|
||||
@ -41,7 +47,7 @@ export class Polygon extends Shape {
|
||||
}
|
||||
|
||||
public getBoundingBox(): Rect {
|
||||
if (this.isDirty) {
|
||||
if (this._isDirty) {
|
||||
let minX = Number.MAX_VALUE;
|
||||
let maxX = Number.MIN_VALUE;
|
||||
let minY = Number.MAX_VALUE;
|
||||
@ -53,13 +59,13 @@ export class Polygon extends Shape {
|
||||
maxX = Math.max(maxX, a.x);
|
||||
maxY = Math.max(maxY, a.y);
|
||||
}
|
||||
this.boundingBox.x = minX;
|
||||
this.boundingBox.y = minY;
|
||||
this.boundingBox.width = maxX - minX;
|
||||
this.boundingBox.height = maxY - minY;
|
||||
this.isDirty = false;
|
||||
this._boundingBox.x = minX;
|
||||
this._boundingBox.y = minY;
|
||||
this._boundingBox.width = maxX - minX;
|
||||
this._boundingBox.height = maxY - minY;
|
||||
this._isDirty = false;
|
||||
}
|
||||
return this.boundingBox;
|
||||
return this._boundingBox;
|
||||
}
|
||||
|
||||
public get points(): Vec2[] {
|
||||
|
@ -5,10 +5,9 @@
|
||||
*/
|
||||
|
||||
import { Graphics, Intersection2D, rect, Rect } from "cc";
|
||||
import { Box } from "./Box";
|
||||
import { Circle } from "./Circle";
|
||||
import { IShape, ShapeType } from "./IShape";
|
||||
import { Polygon } from "./Polygon";
|
||||
import { Shape } from "./Shape";
|
||||
|
||||
// 1|0
|
||||
// ---
|
||||
@ -25,18 +24,18 @@ const circleCircle = Intersection2D.circleCircle;
|
||||
const polygonCircle = Intersection2D.polygonCircle;
|
||||
const polygonPolygon = Intersection2D.polygonPolygon;
|
||||
/** 两个形状是否碰撞 */
|
||||
function isCollide(shape1: Shape, shape2: Shape): boolean {
|
||||
if (shape1 instanceof Circle) {
|
||||
if (shape2 instanceof Circle) {
|
||||
return circleCircle(shape1.position, shape1.radius * shape1.scale, shape2.position, shape2.radius * shape2.scale);
|
||||
} else if (shape2 instanceof Box || shape2 instanceof Polygon) {
|
||||
return polygonCircle(shape2.points, shape1.position, shape1.radius * shape1.scale);
|
||||
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 instanceof Box || shape1 instanceof Polygon) {
|
||||
if (shape2 instanceof Circle) {
|
||||
return polygonCircle(shape1.points, shape2.position, shape2.radius * shape2.scale);
|
||||
} else if (shape2 instanceof Box || shape2 instanceof Polygon) {
|
||||
return polygonPolygon(shape2.points, shape1.points);
|
||||
} 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;
|
||||
@ -53,7 +52,7 @@ export class QuadTree {
|
||||
/** @internal */
|
||||
private _graphics: Graphics;
|
||||
/** @internal */
|
||||
private _shapes_map: Map<number, Shape[]>; // 根据类型存储形状对象
|
||||
private _shapes_map: Map<number, IShape[]>; // 根据类型存储形状对象
|
||||
/** @internal */
|
||||
private _trees: QuadTree[] = []; // 存储四个子节点
|
||||
/** @internal */
|
||||
@ -61,7 +60,7 @@ export class QuadTree {
|
||||
/** @internal */
|
||||
private _bounds: Rect; // 树的外框
|
||||
/** @internal */
|
||||
private _ignore_shapes: Shape[] = []; // 不在树中的形状
|
||||
private _ignore_shapes: IShape[] = []; // 不在树中的形状
|
||||
/**
|
||||
* 创建一个四叉树
|
||||
* @param rect 该节点对应的象限在屏幕上的范围
|
||||
@ -82,7 +81,7 @@ export class QuadTree {
|
||||
* 如果当前节点存在子节点,则检查物体到底属于哪个子节点,如果能匹配到子节点,则将该物体插入到该子节点中
|
||||
* 如果当前节点不存在子节点,将该物体存储在当前节点。随后,检查当前节点的存储数量,如果超过了最大存储数量,则对当前节点进行划分,划分完成后,将当前节点存储的物体重新分配到四个子节点中。
|
||||
*/
|
||||
public insert(shape: Shape): void {
|
||||
public insert(shape: IShape): void {
|
||||
// 如果该节点下存在子节点
|
||||
if (this._trees.length > 0) {
|
||||
let quadrant = this._getQuadrant(shape);
|
||||
@ -114,28 +113,28 @@ export class QuadTree {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
private _insert(shape: Shape): void {
|
||||
if (!this._shapes_map.has(shape.tag)) {
|
||||
this._shapes_map.set(shape.tag, []);
|
||||
private _insert(shape: IShape): void {
|
||||
if (!this._shapes_map.has(shape.mask)) {
|
||||
this._shapes_map.set(shape.mask, []);
|
||||
}
|
||||
this._shapes_map.get(shape.tag).push(shape);
|
||||
this._shapes_map.get(shape.mask).push(shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检索功能:
|
||||
* 给出一个物体对象,该函数负责将该物体可能发生碰撞的所有物体选取出来。该函数先查找物体所属的象限,该象限下的物体都是有可能发生碰撞的,然后再递归地查找子象限...
|
||||
*/
|
||||
public collide(shape: Shape, tag: number = -1): Shape[] {
|
||||
let result: any[] = [];
|
||||
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 = result.concat(this._trees[i].collide(shape, tag));
|
||||
result.push(...this._trees[i].collide(shape, tag));
|
||||
}
|
||||
} else {
|
||||
result = result.concat(this._trees[quadrant].collide(shape, tag));
|
||||
result.push(...this._trees[quadrant].collide(shape, tag));
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +144,7 @@ export class QuadTree {
|
||||
}
|
||||
let shapes = this._shapes_map.get(key);
|
||||
for (const other_shape of shapes) {
|
||||
if (other_shape.valid && shape !== other_shape && isCollide(shape, other_shape)) {
|
||||
if (other_shape.isValid && shape !== other_shape && isCollide(shape, other_shape)) {
|
||||
result.push(other_shape);
|
||||
}
|
||||
}
|
||||
@ -185,7 +184,7 @@ export class QuadTree {
|
||||
}
|
||||
|
||||
/** 当前形状是否包含在象限内 @internal */
|
||||
private _isInner(shape: Shape, bounds: Rect): boolean {
|
||||
private _isInner(shape: IShape, bounds: Rect): boolean {
|
||||
let rect = shape.getBoundingBox();
|
||||
return (
|
||||
rect.xMin * shape.scale + shape.position.x > bounds.xMin &&
|
||||
@ -204,7 +203,7 @@ export class QuadTree {
|
||||
* 右下:象限四
|
||||
* @internal
|
||||
*/
|
||||
private _getQuadrant(shape: Shape): Quadrant {
|
||||
private _getQuadrant(shape: IShape): Quadrant {
|
||||
let bounds = this._bounds;
|
||||
let rect = shape.getBoundingBox();
|
||||
let center = bounds.center;
|
||||
@ -262,42 +261,66 @@ export class QuadTree {
|
||||
|
||||
/** 更新忽略掉的形状 @internal */
|
||||
private _updateIgnoreShapes(root: QuadTree): void {
|
||||
let len = this._ignore_shapes.length;
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
let shape = this._ignore_shapes[i];
|
||||
if (!shape.valid) {
|
||||
this._ignore_shapes.splice(i, 1);
|
||||
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)) {
|
||||
continue;
|
||||
if (this._isInner(shape, this._bounds)) {
|
||||
if (index !== lastIndex) {
|
||||
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
|
||||
}
|
||||
root.insert(shapes.pop());
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
root.insert(this._ignore_shapes.splice(i, 1)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新有效的形状 @internal */
|
||||
private _updateShapes(root: QuadTree): void {
|
||||
for (const shapes of this._shapes_map.values()) {
|
||||
let len = shapes.length;
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
let shape = shapes[i];
|
||||
if (!shape.valid) {
|
||||
shapes.splice(i, 1);
|
||||
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)) {
|
||||
// 如果矩形不属于该象限,则将该矩形重新插入根节点
|
||||
root.insert(shapes.splice(i, 1)[0]);
|
||||
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) {
|
||||
this._trees[quadrant].insert(shapes.splice(i, 1)[0]);
|
||||
if (index !== lastIndex) {
|
||||
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
|
||||
}
|
||||
this._trees[quadrant].insert(shapes.pop());
|
||||
lastIndex--;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,23 +5,20 @@
|
||||
*/
|
||||
|
||||
import { Rect, Vec2 } from "cc";
|
||||
import { IShape, ShapeType } from "./IShape";
|
||||
|
||||
export abstract class Shape {
|
||||
export abstract class Shape implements IShape {
|
||||
/**
|
||||
* 形状的标记 用来过滤不需要检测的形状
|
||||
* 通过 & 来匹配形状是否需要被检测
|
||||
* -1 表示和所有物体碰撞
|
||||
* 形状的掩码 用来过滤不需要检测的形状 通过&来匹配形状是否需要被检测 -1表示和所有物体碰撞
|
||||
*/
|
||||
public tag: number = -1;
|
||||
|
||||
/** 缩放 */
|
||||
public scale: number; // 缩放
|
||||
private _mask: number = -1;
|
||||
protected _scale: number;
|
||||
|
||||
/** 脏标记 用来重置包围盒 @internal */
|
||||
protected isDirty: boolean;
|
||||
protected _isDirty: boolean;
|
||||
|
||||
/** 包围盒 @internal */
|
||||
protected boundingBox: Rect;
|
||||
protected _boundingBox: Rect;
|
||||
|
||||
/** 位置 @internal */
|
||||
protected _position: Vec2;
|
||||
@ -32,12 +29,20 @@ export abstract class Shape {
|
||||
/** 是否有效 下次更新时删除 @internal */
|
||||
private _valid: boolean = true;
|
||||
|
||||
constructor(tag: number) {
|
||||
this.tag = tag;
|
||||
this.scale = 1.0;
|
||||
public abstract get shapeType(): ShapeType;
|
||||
|
||||
public get mask(): number { return this._mask; }
|
||||
public get position(): Vec2 { return this._position; }
|
||||
public get scale(): number { return this._scale; }
|
||||
public get rotation(): number { return this._rotation; }
|
||||
public get isValid(): boolean { return this._valid; }
|
||||
|
||||
constructor(mask: number) {
|
||||
this._mask = mask;
|
||||
this._scale = 1.0;
|
||||
this._rotation = 0;
|
||||
this.isDirty = true;
|
||||
this.boundingBox = new Rect();
|
||||
this._isDirty = true;
|
||||
this._boundingBox = new Rect();
|
||||
this._position = new Vec2();
|
||||
}
|
||||
|
||||
@ -46,30 +51,25 @@ export abstract class Shape {
|
||||
this._position.y = y;
|
||||
}
|
||||
|
||||
get position(): Vec2 {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
set rotation(angle: number) {
|
||||
public setRotation(angle: number) {
|
||||
if (this._rotation !== angle) {
|
||||
this._rotation = angle;
|
||||
this.isDirty = true;
|
||||
this._isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
get rotation(): number {
|
||||
return this._rotation;
|
||||
}
|
||||
|
||||
public get valid(): boolean {
|
||||
return this._valid;
|
||||
public setScale(value: number) {
|
||||
if (this._scale !== value) {
|
||||
this._scale = value;
|
||||
this._isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** 包围盒 子类重写 */
|
||||
public abstract getBoundingBox(): Rect;
|
||||
|
||||
|
||||
public destroy(): void {
|
||||
this._valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user