import { math, v2, Vec2, Vec3 } from 'cc';

export class SplitRenderHelper {
    //ab与ac的叉积
    static ab_cross_ac(a, b, c) {
        return SplitRenderHelper.cross(b.x - a.x, b.y - a.y, c.x - a.x, c.y - a.y);
    }
    static dot(x1, y1, x2, y2) {
        return x1 * x2 + y1 * y2;
    }
    static cross(x1, y1, x2, y2) {
        return x1 * y2 - x2 * y1;
    }
    static dblcmp(a: number, b: number) {
        if (Math.abs(a - b) <= 0.000001) return 0;
        if (a > b) return 1;
        else return -1;
    }
    //求a点是不是在线段上，>0不在，=0与端点重合，<0在。
    static point_on_line(a, p1, p2) {
        return SplitRenderHelper.dblcmp(SplitRenderHelper.dot(p1.x - a.x, p1.y - a.y, p2.x - a.x, p2.y - a.y), 0);
    }
    // 判断一个点是否在三角形内
    static isInTriangle(point: Vec2, triA: Vec2, triB: Vec2, triC: Vec2) {
        let AB: Vec2 = new Vec2();
        Vec2.subtract(AB, triB, triA);

        let AC: Vec2 = new Vec2();
        Vec2.subtract(AC, triC, triA);

        let BC: Vec2 = new Vec2();
        Vec2.subtract(BC, triC, triB);

        let AD: Vec2 = new Vec2();
        Vec2.subtract(AD, point, triA);

        let BD: Vec2 = new Vec2();
        Vec2.subtract(BD, point, triB);

        //@ts-ignore
        return (AB.cross(AC) >= 0 ^ AB.cross(AD) < 0) && (AB.cross(AC) >= 0 ^ AC.cross(AD) >= 0) && (BC.cross(AB) > 0 ^ BC.cross(BD) >= 0);
    }
    static isInPolygon(checkPoint: Vec2, polygonPoints: Vec2[]) {
        var counter = 0;
        var i: number;
        var xinters;
        var p1: Vec2, p2: Vec2;
        var pointCount = polygonPoints.length;
        p1 = polygonPoints[0];

        for (i = 1; i <= pointCount; i++) {
            p2 = polygonPoints[i % pointCount];
            if (
                checkPoint.x > Math.min(p1.x, p2.x) &&
                checkPoint.x <= Math.max(p1.x, p2.x)
            ) {
                if (checkPoint.y <= Math.max(p1.y, p2.y)) {
                    if (p1.x != p2.x) {
                        xinters = (checkPoint.x - p1.x) * (p2.y - p1.y) / (p2.x - p1.x) + p1.y;
                        if (p1.y == p2.y || checkPoint.y <= xinters) {
                            counter++;
                        }
                    }
                }
            }
            p1 = p2;
        }

        if (counter % 2 == 0) {
            return false;
        }

        return true;
    }
    static computeUv(points: Vec2[], width: number, height: number) {
        let uvs: Vec2[] = [];
        for (const p of points) {
            // uv原点是左上角
            let x = math.clamp(0, 1, (p.x + width / 2) / width);
            let y = math.clamp(0, 1, 1. - (p.y + height / 2) / height);
            uvs.push(v2(x, y));
        }
        return uvs;
    }
    static splitPolygon(points: Vec2[]): number[] {
        if (points.length <= 3) return [0, 1, 2];
        let pointMap: { [key: string]: number } = {};     // point与idx的映射
        for (let i = 0; i < points.length; i++) {
            let p = points[i];
            pointMap[`${p.x}-${p.y}`] = i;
        }
        const getIdx = (p: Vec2) => {
            return pointMap[`${p.x}-${p.y}`]
        }
        points = points.concat([]);
        let idxs: number[] = [];

        let index = 0;
        while (points.length > 3) {
            let p1 = points[(index) % points.length]
                , p2 = points[(index + 1) % points.length]
                , p3 = points[(index + 2) % points.length];
            let splitPoint = (index + 1) % points.length;

            let v1: Vec2 = new Vec2();
            Vec2.subtract(v1, p2, p1);
            let v2: Vec2 = new Vec2();
            Vec2.subtract(v2, p3, p2);

            if (v1.cross(v2) < 0) {      // 是一个凹角, 寻找下一个
                index = (index + 1) % points.length;
                continue;
            }
            let hasPoint = false;
            for (const p of points) {
                if (p != p1 && p != p2 && p != p3 && this.isInTriangle(p, p1, p2, p3)) {
                    hasPoint = true;
                    break;
                }
            }
            if (hasPoint) {      // 当前三角形包含其他点, 寻找下一个
                index = (index + 1) % points.length;
                continue;
            }
            // 找到了耳朵, 切掉
            idxs.push(getIdx(p1), getIdx(p2), getIdx(p3));
            points.splice(splitPoint, 1);
        }
        for (const p of points) {
            idxs.push(getIdx(p));
        }
        return idxs;
    }
    //点发出的右射线和线段的关系
    // 返回值: -1:不相交, 0:相交, 1:点在线段上
    static rayPointToLine(point: Vec2, linePA: Vec2, linePB: Vec2) {
        // 定义最小和最大的X Y轴值  
        let minX = Math.min(linePA.x, linePB.x);
        let maxX = Math.max(linePA.x, linePB.x);
        let minY = Math.min(linePA.y, linePB.y);
        let maxY = Math.max(linePA.y, linePB.y);

        // 射线与边无交点的其他情况  
        if (point.y < minY || point.y > maxY || point.x > maxX) {
            return -1;
        }

        // 剩下的情况, 计算射线与边所在的直线的交点的横坐标  
        let x0 = linePA.x + ((linePB.x - linePA.x) / (linePB.y - linePA.y)) * (point.y - linePA.y);
        if (x0 > point.x) {
            return 0;
        }
        if (x0 == point.x) {
            return 1;
        }
        return -1;
    }
    //点和多边形的关系
    //返回值: -1:在多边形外部, 0:在多边形内部, 1:在多边形边线内, 2:跟多边形某个顶点重合
    static relationPointToPolygon(point: Vec2, polygon: Vec2[]) {
        let count = 0;
        for (let i = 0; i < polygon.length; ++i) {
            if (polygon[i].equals(point)) {
                return 2;
            }

            let pa = polygon[i];
            let pb = polygon[0];
            if (i < polygon.length - 1) {
                pb = polygon[i + 1];
            }

            let re = SplitRenderHelper.rayPointToLine(point, pa, pb);
            if (re == 1) {
                return 1;
            }
            if (re == 0) {
                count++;
            }
        }
        if (count % 2 == 0) {
            return -1;
        }
        return 0;
    }
    //求两条线段的交点
    //返回值：[n,p] n:0相交，1在共有点，-1不相交  p:交点
    static lineCrossPoint(p1: Vec2, p2: Vec2, q1: Vec2, q2: Vec2): [number, Vec2] {
        let a = p1, b = p2, c = q1, d = q2;
        let s1, s2, s3, s4;
        let d1, d2, d3, d4;
        let p: Vec2 = new Vec2(0, 0);

        d1 = SplitRenderHelper.dblcmp(s1 = SplitRenderHelper.ab_cross_ac(a, b, c), 0);
        d2 = SplitRenderHelper.dblcmp(s2 = SplitRenderHelper.ab_cross_ac(a, b, d), 0);
        d3 = SplitRenderHelper.dblcmp(s3 = SplitRenderHelper.ab_cross_ac(c, d, a), 0);
        d4 = SplitRenderHelper.dblcmp(s4 = SplitRenderHelper.ab_cross_ac(c, d, b), 0);

        if ((d1 ^ d2) == -2 && (d3 ^ d4) == -2) {
            p.x = (c.x * s2 - d.x * s1) / (s2 - s1);
            p.y = (c.y * s2 - d.y * s1) / (s2 - s1);
            return [0, p];
        }

        if (d1 == 0 && SplitRenderHelper.point_on_line(c, a, b) <= 0) {
            p = c;
            return [1, p];
        }
        if (d2 == 0 && SplitRenderHelper.point_on_line(d, a, b) <= 0) {
            p = d;
            return [1, p];
        }
        if (d3 == 0 && SplitRenderHelper.point_on_line(a, c, d) <= 0) {
            p = a;
            return [1, p];
        }
        if (d4 == 0 && SplitRenderHelper.point_on_line(b, c, d) <= 0) {
            p = b;
            return [1, p];
        }
        return [-1, null];
    }
    //线段对多边形进行切割
    //返回多边形数组
    //如果没有被切割，返回空数组
    static lineCutPolygon(pa: Vec2, pb: Vec2, polygon: Vec2[]) {
        // 检查切割线的端点是否在多边形内部
        const extendPoint = (point: Vec2, direction: Vec2) => {
            const extendedPoint = new Vec2(point.x + direction.x * 1000, point.y + direction.y * 1000);
            return extendedPoint;
        };

        if (SplitRenderHelper.isInPolygon(pa, polygon)) {
            const direction = Vec2.subtract(new Vec2(), pa, pb).normalize();
            pa = extendPoint(pa, direction);
        }
        if (SplitRenderHelper.isInPolygon(pb, polygon)) {
            const direction = Vec2.subtract(new Vec2(), pb, pa).normalize();
            pb = extendPoint(pb, direction);
        }

        let ret: Array<Vec2[]> = [];
        let points: Vec2[] = [];
        let pointIndex: number[] = [];

        for (let i = 0; i < polygon.length; ++i) {
            points.push(polygon[i]);

            let a = polygon[i];
            let b = polygon[0];
            if (i < polygon.length - 1) b = polygon[i + 1];

            let c = SplitRenderHelper.lineCrossPoint(pa, pb, a, b);
            if (c[0] == 0) {
                pointIndex.push(points.length);
                points.push(c[1] as Vec2);
            }
            else if (c[0] > 0) {
                if ((c[1] as Vec2).equals(a)) {
                    pointIndex.push(points.length - 1);
                }
                else {
                    pointIndex.push(points.length);
                }
            }
        }
        if (pointIndex.length > 1) {
            let cp0 = points[pointIndex[0]];
            let cp1 = points[pointIndex[1]];

            let r = SplitRenderHelper.relationPointToPolygon(new Vec2((cp0.x + cp1.x) / 2, (cp0.y + cp1.y) / 2), polygon);
            let inPolygon: boolean = r >= 0;

            let cp0_cp1: Vec2 = new Vec2();
            let len_0_1 = Vec2.subtract(cp0_cp1, cp0, cp1).length()

            let cp0_cp: Vec2 = new Vec2();
            let len_0_ = Vec2.subtract(cp0_cp, cp0, points[pointIndex[pointIndex.length - 1]]).length()

            if (pointIndex.length > 2 && len_0_1 > len_0_) {
                cp1 = points[pointIndex[pointIndex.length - 1]];
                r = SplitRenderHelper.relationPointToPolygon(new Vec2((cp0.x + cp1.x) / 2, (cp0.y + cp1.y) / 2), polygon);
                inPolygon = r < 0;
            }

            let firstInPolygon = inPolygon;

            let index = 0;
            let startIndex = pointIndex[index];
            let oldPoints = [];
            let newPoints = [];
            let count = 0;

            oldPoints.push(points[startIndex]);
            if (inPolygon) {
                newPoints.push(points[startIndex]);
            }

            index++;
            count++;
            startIndex++;

            while (count < points.length) {
                if (startIndex == points.length) startIndex = 0;
                let p = points[startIndex];
                if (index >= 0 && startIndex == pointIndex[index]) {
                    index++;
                    if (index >= pointIndex.length) index = 0;
                    if (inPolygon) {
                        newPoints.push(p);
                        ret.push(newPoints);
                        newPoints = [];
                    }
                    else {
                        newPoints = [];
                        newPoints.push(p);
                    }
                    oldPoints.push(p);
                    inPolygon = !inPolygon;
                }
                else {
                    if (inPolygon) {
                        newPoints.push(p);
                    }
                    else {
                        oldPoints.push(p);
                    }
                }
                startIndex++;
                count++;
            }
            if (inPolygon) {
                if (!firstInPolygon && newPoints.length > 1) {
                    newPoints.push(points[pointIndex[0]]);
                    ret.push(newPoints);
                }
                else {
                    for (let i = 0; i < newPoints.length; ++i) {
                        oldPoints.push(newPoints[i]);
                    }
                }

            }

            ret.push(oldPoints);
        }
        return ret;
    }
    // 实用函数
    static isPointInsidePolygon(p: Vec2, polygon: Vec2[]) {
        let windingNumber = 0;
        for (let i = 0; i < polygon.length; i++) {
            const a = polygon[i];
            const b = polygon[(i + 1) % polygon.length];
            if (a.y <= p.y) {
                if (b.y > p.y && (b.x - a.x) * (p.y - a.y) > (p.x - a.x) * (b.y - a.y))
                    windingNumber++;
            } else {
                if (b.y <= p.y && (b.x - a.x) * (p.y - a.y) < (p.x - a.x) * (b.y - a.y))
                    windingNumber--;
            }
        }
        return windingNumber !== 0;
    }
    static calculatePolygonArea(polygon: Vec2[]) {
        let area = 0;
        for (let i = 0; i < polygon.length; i++) {
            const j = (i + 1) % polygon.length;
            area += polygon[i].x * polygon[j].y;
            area -= polygon[j].x * polygon[i].y;
        }
        return Math.abs(area) / 2;
    }
}

