const { ccclass, property } = cc._decorator;

@ccclass
export default class Test extends cc.Component {

    private hitShapes: Array<HitShape>;

    private debug: cc.Graphics;
    private debug2: cc.Graphics;
    private debug3: cc.Graphics;

    protected start(): void {
        this.hitShapes = [];
        for (const child of this.node.getChildByName("circles").children) {
            this.hitShapes.push(new HitShape(child, "椭圆形"));
        }
        for (const child of this.node.getChildByName("rects").children) {
            this.hitShapes.push(new HitShape(child, "长方形"));
        }
        this.debug = this.node.getChildByName("debug").getComponent(cc.Graphics);
        this.debug2 = this.node.getChildByName("debug2").getComponent(cc.Graphics);
        this.debug3 = this.node.getChildByName("debug3").getComponent(cc.Graphics);

        let oldP: cc.Vec2;
        for (const hitShape of this.hitShapes) {
            hitShape.node.on(cc.Node.EventType.TOUCH_START, (evt: cc.Event.EventTouch) => {
                oldP = evt.getLocation();
            });
            hitShape.node.on(cc.Node.EventType.TOUCH_MOVE, (evt: cc.Event.EventTouch) => {
                const p = evt.getLocation();
                const hitShape: HitShape = evt.target["hitShape"];
                hitShape.node.x += p.x - oldP.x;
                hitShape.node.y += p.y - oldP.y;
                oldP = p;
            });
        }
    }

    protected update(dt: number): void {
        this.debug.clear();
        this.debug2.clear();
        this.debug3.clear();

        for (const hitShape of this.hitShapes) {
            hitShape.node.color = cc.Color.GREEN;
            hitShape.node.angle += 0.1 * Math.random();
            hitShape.update();
            this.debug2.moveTo(hitShape.aabb[3].absX, hitShape.aabb[3].absY);
            for (const p of hitShape.aabb) {
                this.debug2.lineTo(p.absX, p.absY);
            }
            this.debug2.rect(hitShape.absLBRT[0], hitShape.absLBRT[1], hitShape.absLBRT[2] - hitShape.absLBRT[0], hitShape.absLBRT[3] - hitShape.absLBRT[1]);
            for (const p of hitShape.ps) {
                this.debug.circle(p.absX, p.absY, 2);
            }
        }

        let i: number = -1;
        for (const hitShape1 of this.hitShapes) {
            i++;
            for (let j: number = i + 1; j < this.hitShapes.length; j++) {
                const hitShape2 = this.hitShapes[j];
                if (aabb(hitShape1.absLBRT, hitShape2.absLBRT)) {
                    this.debug3.rect(hitShape1.absLBRT[0], hitShape1.absLBRT[1], hitShape1.absLBRT[2] - hitShape1.absLBRT[0], hitShape1.absLBRT[3] - hitShape1.absLBRT[1]);
                    this.debug3.rect(hitShape2.absLBRT[0], hitShape2.absLBRT[1], hitShape2.absLBRT[2] - hitShape2.absLBRT[0], hitShape2.absLBRT[3] - hitShape2.absLBRT[1]);
                    const p = hitShape1.pIn(hitShape2.ps) || hitShape2.pIn(hitShape1.ps);
                    if (p) {
                        this.debug3.circle(p.absX, p.absY, 8);
                        hitShape1.node.color = cc.Color.RED;
                        hitShape2.node.color = cc.Color.RED;
                    }
                }
            }
        }

        this.debug.stroke();
        this.debug2.stroke();
        this.debug3.stroke();
    }

}

function aabb(lbrt1: Array<number>, lbrt2: Array<number>): boolean {
    if (
        lbrt1[0] > lbrt2[2] ||
        lbrt2[0] > lbrt1[2] ||
        lbrt1[1] > lbrt2[3] ||
        lbrt2[1] > lbrt1[3]
    ) {
        return false;
    }
    return true;
}

type HitShapeType = "椭圆形" | "长方形";
interface P { x: number, y: number, absX?: number, absY?: number };
class HitShape {

    public node: cc.Node;
    private type: HitShapeType;
    private w2: number;
    private h2: number;
    public aabb: Array<P>;
    public absLBRT: Array<number>;
    public ps: Array<P>;

    public constructor(node: cc.Node, type: HitShapeType) {
        this.node = node;
        this.node.angle = Math.random() * 360;
        node["hitShape"] = this;
        this.type = type;
        this.w2 = node.width / 2;
        this.h2 = node.height / 2;
        this.aabb = [{ x: -this.w2, y: -this.h2 }, { x: this.w2, y: -this.h2 }, { x: this.w2, y: this.h2 }, { x: -this.w2, y: this.h2 }];
        this.absLBRT = [0, 0, 0, 0];

        //按指定密度采样
        this.ps = [{ x: 0, y: 0 }];//优先1取中心点
        const 最小采样距离2_高精度 = 10 * 10;
        const 最小采样距离2_低精度 = 50 * 50;
        this.addPs(1, 最小采样距离2_高精度);
        this._addPs(0, 1, 最小采样距离2_低精度);
        //最后二分法取两个相邻点的中点
        switch (this.type) {
            case "椭圆形":
                this.arc_addMidPs(1, 最小采样距离2_高精度);
                this._arc_addMidPs(0, 1, 最小采样距离2_低精度);
                break;
            case "长方形":
                this.line_addMidPs(1, 最小采样距离2_高精度);
                this._line_addMidPs(0, 1, 最小采样距离2_低精度);
                break;
        }
    }

    private addPs(k: number, 最小采样距离2: number): void {
        const w2 = this.w2 * k;
        const h2 = this.h2 * k;

        //优先2取十字线上的端点
        this.addP(-w2, 0, 最小采样距离2);
        this.addP(w2, 0, 最小采样距离2);
        this.addP(0, -h2, 最小采样距离2);
        this.addP(0, h2, 最小采样距离2);

        switch (this.type) {
            case "椭圆形":
                //优先3取X线上的端点
                for (let i: number = 0; i < 4; i++) {
                    const rad = Math.PI * (0.25 + i / 2);
                    this.addP(w2 * Math.cos(rad), h2 * Math.sin(rad), 最小采样距离2);
                }
                break;
            case "长方形":
                //优先3取X线上的端点
                this.addP(-w2, -h2, 最小采样距离2);
                this.addP(-w2, h2, 最小采样距离2);
                this.addP(w2, -h2, 最小采样距离2);
                this.addP(w2, h2, 最小采样距离2);
                break;
        }
    }
    private _addPs(kStart: number, kEnd: number, 最小采样距离2: number): void {
        let d: number = this.w2;
        if (d < this.h2) d = this.h2;
        d *= kEnd - kStart;
        if (d * d < 最小采样距离2) return;
        const k = (kStart + kEnd) / 2;
        this.addPs(k, 最小采样距离2);
        this._addPs(kStart, k, 最小采样距离2);
        this._addPs(k, kEnd, 最小采样距离2);
    }
    private addP(x: number, y: number, 最小采样距离2: number): void {
        for (const p of this.ps) {
            const dx = x - p.x;
            const dy = y - p.y;
            if (dx * dx + dy * dy < 最小采样距离2) {
                return;
            }
        }
        this.ps.push({ x: x, y: y });
    }
    private line_addMidPs(k: number, 最小采样距离2: number): void {
        const w2 = this.w2 * k;
        const h2 = this.h2 * k;

        this._line_addMidP(w2, 0, w2, h2, 最小采样距离2);
        this._line_addMidP(-w2, 0, -w2, -h2, 最小采样距离2);
        this._line_addMidP(w2, h2, 0, h2, 最小采样距离2);
        this._line_addMidP(-w2, -h2, 0, -h2, 最小采样距离2);
        this._line_addMidP(0, h2, -w2, h2, 最小采样距离2);
        this._line_addMidP(0, -h2, w2, -h2, 最小采样距离2);
        this._line_addMidP(-w2, h2, -w2, 0, 最小采样距离2);
        this._line_addMidP(w2, -h2, w2, 0, 最小采样距离2);
    }
    private _line_addMidPs(kStart: number, kEnd: number, 最小采样距离2: number): void {
        let d: number = this.w2;
        if (d < this.h2) d = this.h2;
        d *= kEnd - kStart;
        if (d * d < 最小采样距离2) return;
        const k = (kStart + kEnd) / 2;
        this.line_addMidPs(k, 最小采样距离2);
        this._line_addMidPs(kStart, k, 最小采样距离2);
        this._line_addMidPs(k, kEnd, 最小采样距离2);
    }
    private _line_addMidP(x1: number, y1: number, x2: number, y2: number, 最小采样距离2: number): void {
        const dx = x1 - x2;
        const dy = y1 - y2;
        if (dx * dx + dy * dy < 最小采样距离2) return;
        const midX = (x1 + x2) / 2;
        const midY = (y1 + y2) / 2;
        this.addP(midX, midY, 最小采样距离2);
        this._line_addMidP(x1, y1, midX, midY, 最小采样距离2);
        this._line_addMidP(midX, midY, x2, y2, 最小采样距离2);
    }
    private arc_addMidPs(k: number, 最小采样距离2: number): void {
        const w2 = this.w2 * k;
        const h2 = this.h2 * k;
        for (let i: number = 0; i < 8; i++) {
            this._arc_addMidP(w2, h2, Math.PI * i / 4, Math.PI * (i + 1) / 4, 最小采样距离2);
        }
    }
    private _arc_addMidPs(kStart: number, kEnd: number, 最小采样距离2: number): void {
        let d: number = this.w2;
        if (d < this.h2) d = this.h2;
        d *= kEnd - kStart;
        if (d * d < 最小采样距离2) return;
        const k = (kStart + kEnd) / 2;
        this.arc_addMidPs(k, 最小采样距离2);
        this._arc_addMidPs(kStart, k, 最小采样距离2);
        this._arc_addMidPs(k, kEnd, 最小采样距离2);
    }
    private _arc_addMidP(w2: number, h2: number, rad1: number, rad2: number, 最小采样距离2: number): void {
        const x1 = w2 * Math.cos(rad1);
        const y1 = h2 * Math.sin(rad1);
        const x2 = w2 * Math.cos(rad2);
        const y2 = h2 * Math.sin(rad2);
        const dx = x1 - x2;
        const dy = y1 - y2;
        if (dx * dx + dy * dy < 最小采样距离2) return;
        const rad = (rad1 + rad2) / 2;
        this.addP(w2 * Math.cos(rad), h2 * Math.sin(rad), 最小采样距离2);
        this._arc_addMidP(w2, h2, rad1, rad, 最小采样距离2);
        this._arc_addMidP(w2, h2, rad, rad2, 最小采样距离2);
    }

    public update(): void {
        const rad = this.node.angle * Math.PI / 180;
        const c = Math.cos(rad);
        const s = Math.sin(rad);
        this.absLBRT[0] = this.absLBRT[1] = 100000000;
        this.absLBRT[2] = this.absLBRT[3] = -100000000;
        for (const p of this.aabb) {
            p.absX = this.node.x + p.x * c - p.y * s;
            p.absY = this.node.y + p.x * s + p.y * c;
            if (p.absX < this.absLBRT[0]) this.absLBRT[0] = p.absX;
            if (p.absY < this.absLBRT[1]) this.absLBRT[1] = p.absY;
            if (p.absX > this.absLBRT[2]) this.absLBRT[2] = p.absX;
            if (p.absY > this.absLBRT[3]) this.absLBRT[3] = p.absY;
        }
        for (const p of this.ps) {
            p.absX = this.node.x + p.x * c - p.y * s;
            p.absY = this.node.y + p.x * s + p.y * c;
        }
    }

    public pIn(ps: Array<P>): P {
        const _rad = -this.node.angle * Math.PI / 180;
        const c = Math.cos(_rad);
        const s = Math.sin(_rad);
        switch (this.type) {
            case "椭圆形":
                for (const p of ps) {
                    const dx = p.absX - this.node.x;
                    const dy = p.absY - this.node.y;
                    const normalizeX = (dx * c - dy * s) / this.w2;
                    const normalizeY = (dx * s + dy * c) / this.h2;
                    if (normalizeX * normalizeX + normalizeY * normalizeY < 1) {
                        return p;
                    }
                }
                break;
            case "长方形":
                for (const p of ps) {
                    const dx = p.absX - this.node.x;
                    const dy = p.absY - this.node.y;
                    const normalizeX = (dx * c - dy * s) / this.w2;
                    const normalizeY = (dx * s + dy * c) / this.h2;
                    if (normalizeX > -1 && normalizeX < 1 && normalizeY > -1 && normalizeY < 1) {
                        return p;
                    }
                }
                break;
        }
    }

}
