egret判断两个多边形是否相交(分离轴定律)

参考原文:原文

预备知识:向量的点积: 

 

关于向量的知识这里不再赘述 

分离轴定理(Separating Axis Theorem)

概念:通过判断任意两个 凸多边形 在任意角度下的投影是否均存在重叠,来判断是否发生碰撞。若在某一角度光源下,两物体的投影存在间隙,则为不碰撞,否则为发生碰撞。

图例:

在程序中,遍历所有角度是不现实的。那如何确定 投影轴 呢?其实投影轴的数量与多边形的边数相等即可。

 

上述代码有几个需要解决的地方:

  • 如何确定多边形的各个投影轴
  • 如何将多边形投射到某条投影轴上
  • 如何检测两段投影是否发生重叠

投影轴

如下图所示,我们使用一条从 p1 指向 p2 的向量来表示多边形的某条边,我们称之为边缘向量。在分离轴定理中,还需要确定一条垂直于边缘向量的法向量,我们称之为“边缘法向量”。

投影轴平行于边缘法向量。投影轴的位置不限,因为其长度是无限的,故而多边形在该轴上的投影是一样的。该轴的方向才是关键的。

 

投影

投影的大小:通过将一个多边形上的每个顶点与原点(0,0)组成的向量,投影在某一投影轴上,然后保留该多边形在该投影轴上所有投影中的最大值和最小值,这样即可表示一个多边形在某投影轴上的投影了。

判断两多边形的投影是否重合:projection1.max > projection2.min && project2.max > projection.min

 

 碰撞工具类:

interface IPoint {
    x: number;
    y: number;
}

class CollisionUitls {

    /**
     * 返回矩形的顶点坐标位置 相对于当前父节点的坐标点。
     * @param shap 
     */
    public static getRectangleVerPoints(shap: egret.DisplayObject): IPoint[] {
        const anX = shap.anchorOffsetX;
        const anY = shap.anchorOffsetY;

        const mat2x3 = shap.matrix;
        const a = mat2x3.a;
        const b = mat2x3.b;
        const c = mat2x3.c;
        const d = mat2x3.d;
        const tx = mat2x3.tx;
        const ty = mat2x3.ty;
        const w = shap.width;
        const h = shap.height;


        const p1x = - anX
        const p1y = -anY

        const p2x = w - anX;
        const p2y = -anY;

        const p3x = w - anX;
        const p3y = (h - anY);

        const p4x = -anX;
        const p4y = (h - anY);


        // a: 缩放或者旋转图像是影响沿x轴定位的值
        // b: 旋转或者倾斜影响y轴定位的值
        // 对x坐标有影响的值为a 和 c的值
        // 对y坐标有影响的值为b 和 d的值
        const p1 = { x: tx + a * p1x + c * p1y, y: ty + b * p1x + d * p1y };
        const p2 = { x: tx + a * p2x + c * p2y, y: ty + b * p2x + d * p2y };
        const p3 = { x: tx + a * p3x + c * p3y, y: ty + b * p3x + d * p3y };
        const p4 = { x: tx + a * p4x + c * p4y, y: ty + b * p4x + d * p4y };

        return [p1, p2, p3, p4];
    }

    /**
     * 检测圆和多边形
     * @param obj1 
     * @param obj2 
     */
    public static TestCircleAndPolygon(CircleX: number, CircleY: number,r:number, PolygonVertexPoints: IPoint[]): boolean {
        for (let i: number = 0; i < PolygonVertexPoints.length; i++) {
            let startPoint = PolygonVertexPoints[i];
            let endPoint;
            let sideNorVec;

            if (i != PolygonVertexPoints.length - 1) {
                endPoint = PolygonVertexPoints[i + 1];
            } else {
                //最后一个
                endPoint = PolygonVertexPoints[0];
            }
            sideNorVec = CollisionUitls.get2PointVec(startPoint, endPoint, false);

            //这个边的法相量方向(标准化过的)
            let dotNorVec = CollisionUitls.getSideNorml(sideNorVec, false);
            const data1 = CollisionUitls.calcProj(dotNorVec, PolygonVertexPoints);
            const dot = CollisionUitls.dot([CircleX, CircleY], CollisionUitls.normalize(dotNorVec));

            const value = CollisionUitls.segDist(data1.min, data1.max, dot-r, dot+r);
            if (!value) {
                return false;
            }
        }
        return true;
    }


    /**
     * 检测两个矩形是否相交 
     * @param r1PointList 矩形顶点数组
     * @param r2PointList 
     */
    public static checkPolygon(r1PointList: IPoint[], r2PointList: IPoint[]): boolean {
        
        var fun1 = function (pointlist: IPoint[], vertexPoints1: IPoint[], vertexPoints2: IPoint[]): boolean {
            //先计算 r1的边 ---》 顺时针
            for (let i: number = 0; i < pointlist.length; i++) {
                let startPoint = pointlist[i];
                let endPoint;
                let sideNorVec;

                if (i != pointlist.length - 1) {
                    endPoint = pointlist[i + 1];
                } else {
                    //最后一个


                    endPoint = pointlist[0];
                }
                sideNorVec = CollisionUitls.get2PointVec(startPoint, endPoint, false);

                //这个边的法相量方向(标准化过的)
                let dotNorVec = CollisionUitls.getSideNorml(sideNorVec, false);
                const data1 = CollisionUitls.calcProj(dotNorVec, vertexPoints1);
                const data2 = CollisionUitls.calcProj(dotNorVec, vertexPoints2);
                const value = CollisionUitls.segDist(data1.min, data1.max, data2.min, data2.max);
                if (!value) {
                    return false;
                }

            }
            return true;
        }

        if (!fun1(r1PointList, r1PointList, r2PointList)) {
            return false;
        }

        if (!fun1(r2PointList, r1PointList, r2PointList)) {
            return false;
        }
        return true;


    }

    /**
     * 计算某个图形坐标点到当前轴上的投影最小点
     * @param axis 
     * @param objPoints 
     */
    public static calcProj(axis: number[], objPoints: IPoint[]): { min: number, max: number } {

        let min = 0;
        let max = 0;
        for (let i: number = 0; i < objPoints.length; i++) {
            const vec2 = [objPoints[i].x, objPoints[i].y];


            const dot = CollisionUitls.dot(vec2, CollisionUitls.normalize(axis));

            //const dot = CollisionUitls.dot(vec2,axis);

            if (min == 0 || max == 0) {
                min = max = dot;
            } else {
                min = (dot < min) ? dot : min;
                max = (dot > max) ? dot : max;
            }

        }

        return { min: min, max: max }
    }



    /**
     * 计算同一个轴上线段的距离s1(min1,max1),s2(min2,max2),如果距离小于0则表示两线段有相交;
     * @param min1 
     * @param max1 
     * @param min2 
     * @param max2 
     * @returns true: 相交 false: 不相交
     */
    public static segDist(min1: number, max1: number, min2: number, max2: number): boolean {
        if (min1 < min2) {
            return min2 < max1 && max2 > min1;
        }
        else {
            return min1 < max2 && max1 > min2;
        }
    }

    /**
     * 计算两点向量方向
     * @param p1 
     * @param p2 
     */
    public static get2PointVec(p1: IPoint, p2: IPoint, isNormlize = true): number[] {


        const x = p2.x - p1.x;
        const y = p2.y - p1.y;



        if (isNormlize) {
            return CollisionUitls.normalize([x, y]);
        }
        return [x, y];
    }


    /**
     * 根据当前多边形边长获取对应边长的 法线向量
     * @param vec2 
     */
    public static getSideNorml(vec2: number[], isNormlize = true): number[] {

        const x = vec2[0];
        const y = vec2[1];

        //顺时针方向的法相量 
        //normal=(-y,x) || normal=(y,-x)
        if (isNormlize) {
            CollisionUitls.normalize([-y, x])
        }
        return [-y, x];
    }

    public static normalize(vec2: number[]): number[] {
        if (vec2.length != 2) {
            console.error("只能标准化2d向量")
            return vec2;
        }
        const x = vec2[0];
        const y = vec2[1];
        const model = Math.sqrt(x * x + y * y);


        return [x / model, y / model];
    }
    /**
     * 计算两个向量的点积
     * @param vec2A 
     * @param vec2B 
     */
    public static dot(vec2A: number[], vec2B: number[]): number {
        if (vec2A.length != 2 || vec2B.length != 2) {
            console.error("只能计算2d向量的点积")
            return null;
        }

        return vec2A[0] * vec2B[0] + vec2A[1] * vec2B[1];
    }
}

源码地址:git@github.com:lck898989/egret2d_collisionDEMO.git

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值