diff --git a/src/quadtree/Box.ts b/src/quadtree/Box.ts index 09d7b57..01b7fad 100644 --- a/src/quadtree/Box.ts +++ b/src/quadtree/Box.ts @@ -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); diff --git a/src/quadtree/Circle.ts b/src/quadtree/Circle.ts index 9122d3a..76d1d6a 100644 --- a/src/quadtree/Circle.ts +++ b/src/quadtree/Circle.ts @@ -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; } } \ No newline at end of file diff --git a/src/quadtree/IShape.ts b/src/quadtree/IShape.ts new file mode 100644 index 0000000..aea3a13 --- /dev/null +++ b/src/quadtree/IShape.ts @@ -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; +} \ No newline at end of file diff --git a/src/quadtree/Polygon.ts b/src/quadtree/Polygon.ts index 93c2715..7066dea 100644 --- a/src/quadtree/Polygon.ts +++ b/src/quadtree/Polygon.ts @@ -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[] { diff --git a/src/quadtree/QuadTree.ts b/src/quadtree/QuadTree.ts index 5e37539..777320c 100644 --- a/src/quadtree/QuadTree.ts +++ b/src/quadtree/QuadTree.ts @@ -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; // 根据类型存储形状对象 + private _shapes_map: Map; // 根据类型存储形状对象 /** @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++; } } } diff --git a/src/quadtree/Shape.ts b/src/quadtree/Shape.ts index 0dffc33..94d9864 100644 --- a/src/quadtree/Shape.ts +++ b/src/quadtree/Shape.ts @@ -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; } -} - +} \ No newline at end of file