优化四叉树性能

This commit is contained in:
gongxh 2025-05-27 18:19:41 +08:00
parent 91f1dbc355
commit 1b73738c39
6 changed files with 159 additions and 86 deletions

View File

@ -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);

View File

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

View File

@ -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[] {

View File

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

View File

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