线段(向量)的计算(判断线段重叠、相交,合并线段,点与线的关系)

主要内容:

  • 判断两线段是否相交
  • 计算两线段的交叉点
  • 点与直线的位置关系
  • 判断两线段重合并计算其重合部分
  • 判断合并两条线段

说明全都在注释里了,有的方法可能不是最佳,欢迎大家提出建议~~

public class Line : MonoBehaviour 
{
    Vector2 impossiblePoint = -1000 * Vector2.one;

    // ----------

    //判断两线段交叉并计算交叉点,线段AB,线段CD
    public Vector2 LineCross(Vector2 A, Vector2 B, Vector2 C, Vector2 D)
    {
        //可能情况说明:
        //01 完全交叉,线段AB的端点分别在线段CD的两侧,且线段CD的端点也分别在线段AB的两侧
        //  pointPosA * pointPosB < 0 && pointPosC * pointPosD < 0
        //  pointPosA * pointPosB < 0   点AB在线段CD两侧, > 0,点AB在线段同侧, == 0,点AB至少有一个点在线段上
        //02 线段一个端点在另一条线段上,且另一个端点不在
        //  pointPosA == 0 && pointPosB != 0 || pointPosA != 0 && pointPosB == 0
        //  pointPosC == 0 && pointPosD != 0 || pointPosC != 0 && pointPosD == 0

        //计算各端点与线段的关系
        float pointPosA, pointPosB, pointPosC, pointPosD;
        pointPosA = PointPosionOfLine(A, C, D);
        pointPosB = PointPosionOfLine(B, C, D);
        pointPosC = PointPosionOfLine(C, A, B);
        pointPosD = PointPosionOfLine(D, A, B);

        //01
        if (pointPosA * pointPosB < 0 && pointPosC * pointPosD < 0)
        {
            return GetCrosPoint(A, B, C, D);
        }
        //02
        if (IsZero(pointPosA) && !IsZero(pointPosB))
            return A;
        else if (!IsZero(pointPosA) && IsZero(pointPosB))
            return B;
        if (IsZero(pointPosC) && !IsZero(pointPosD))
            return C;
        else if (!IsZero(pointPosC) && IsZero(pointPosD))
            return D;
        //03 若不符合以上条件,则不交叉
        return impossiblePoint;
    }

    //点与直线的位置,目标点M,直线AB
    float PointPosionOfLine(Vector2 M, Vector2 A, Vector2 B)
    {
        // 返回值 > 0 在右侧, = 0 在线上, < 0 在左侧
        return (B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x);
       
        //公式计算过程
        //直线公式:a * X + b * Y + c = 0
        //将线段端点代入公式
        // a * A.x + b * A.y + c = 0
        // a * B.x + b * B.y + c = 0
        //两式分别相加、相减
        // (A.x + B.x) * a + (A.y + B.y) * b + 2c = 0
        // (A.x - B.x) * a + (A.y - B.y) * b = 0
        //化简得
        // b = (B.x - A.x) / (A.y - B.y) * a
        // c = -a * (A.y * B.x - A.x * B.y) / (A.y - B.y)
        //原直线公式用a表示为
        // a * X + (B.x - A.x) / (A.y - B.y) * a * Y - a * (A.y * B.x - A.x * B.y) / (A.y - B.y) = 0
        //公式两边同时除a,直线公式用点A/B表示为
        // X + (B.x - A.x) / (A.y - B.y) * Y - (A.y * B.x - A.x * B.y) / (A.y - B.y) = 0
        //再次化简
        // (A.y - B.y) * X + (B.x - A.x) * Y - (A.y * B.x - A.x * B.y) = 0
        //公式左侧==0,点在直线上,公式左侧>0,点在直线右侧,工作左侧<0,点在直线左侧
        //将目标点M代入公式
        // (A.y - B.y) * M.x + (B.x - A.x) * M.y - (A.y * B.x - A.x * B.y)
        // A.y * M.x - B.y * M.x + B.x * M.y - A.x * M.y - A.y * B.x + A.x * B.y
        //整理为
        // (B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x)
    }

    //计算两线段交点,线段AB,线段CD
    Vector2 GetCrosPoint(Vector2 A, Vector2 B, Vector2 C, Vector2 D)
    {
        //直线方程:
        // Y = aX + b
        float x = 0, y = 0;

        float a1 = (B.y - A.y) / (B.x - A.x);
        float a2 = (D.y - C.y) / (D.x - C.x);

        if (IsZero((A.x - B.x) - (C.x - D.x)))
        {
            //两线段都没有斜率,或者两斜率相等,此时两线段平行
            return impossiblePoint;
        }
        else if (IsZero(A.x - B.x))
        {
            //线段AB竖直方向,交点为M

            //方法一:解方程式
            //两线段方程:(方程式由公式Y = aX + b代入两点参数得)
            // X = A.x
            // Y = a2 * X + (C.y - C.x * a2)
            //解方程式即可

            //方法二:三角形关系(不推荐)
            //计算过程如下:
            //交点M在线段AB上,其x坐标与线段任一点x坐标相同
            //交点M在线段CD上,CM与CD在同一条直线上,两线段斜率相等
            // (M.y - C.y) / (M.x - C.x) = a2
            // M.y = a2 * (M.x - C.x) + C.y
            // M.x = A.x
            // M.y = a2 * (A.x - C.x) + C.y

            //最终均可得
            x = A.x;
            y = a2 * (A.x - C.x) + C.y;
            return new Vector2(x, y);
        }
        else if (IsZero(C.x - D.x))
        {
            //线段CD竖直方向,计算过程同上
            x = C.x;
            y = a1 * (C.x - A.x) + A.y;
            return new Vector2(x, y);
        }
        else
        {
            //线段都有斜率,且不相等
            //解方程式,(方程式由公式Y = aX + b代入两点参数得)
            // Y = a1 * X + (A.y - A.x * a1)
            // Y = a2 * X + (C.y - C.x * a2)

            x = (a1 * A.x - a2 * C.x - A.y + C.y) / (a1 - a2);
            y = a1 * x - a1 * A.x + A.y;
            return new Vector2(x, y);
        }
    }

    // ----------

    //判断两线段重合并计算其重合部分(重合部分为线段,返回重合线段两个端点,不考虑只有一个重合点的情况)
    //线段AB,线段CD
    public Vector2[] LineCoincide(Vector2 A, Vector2 B, Vector2 C, Vector2 D)
    {
        //可能情况说明:
        //01 包含关系,一条线段包含在另一条线段内,比如:线段AB的端点A和端点B都在线段CD上
        //  onLineA && onLineB
        //  onLineC && onLineD
        //02 交错关系,各包含一个端点,比如:线段AB的端点A在线段CD上,线段CD的端点C在线段AB上
        //  onLineA && onLineC && A != C
        //  onLineA && onLineD && A != D
        //  onLineB && onLineC && B != C
        //  onLineB && onLineD && B != D

        //为使计算过程开起来更加直观,这里不对公式进行过多化简

        //计算各个端点与线段的关系(点是否在线段上)
        bool onLineA, onLineB, onLineC, onLineD;
        onLineA = IsPointOnLine(A, C, D);
        onLineB = IsPointOnLine(B, C, D);
        onLineC = IsPointOnLine(C, A, B);
        onLineD = IsPointOnLine(D, A, B);

        //如果只需要返回bool结果,不需要获取重合点,使用以下代码
        /*
        //直接判断bool值并返回
        bool coincide01 = (onLineA && onLineB) || (onLineC && onLineD);
        bool coincide02 = (onLineA && onLineC && (A != C))
            || (onLineA && onLineD && (A != D))
            || (onLineB && onLineC && (B != C))
            || (onLineB && onLineD && (B != D));
        return (coincide01 || coincide02);
        */

        // -- -- -- -- --

        //如果需要返回重合点,使用以下代码

        //coincide01
        if (onLineA && onLineB)
            return new Vector2[] { A, B };
        else if (onLineC && onLineD)
            return new Vector2[] { C, D };

        //coincide02
        if (onLineA && onLineC && (A != C))
            return new Vector2[] { A, C };
        else if (onLineA && onLineD && (A != D))
            return new Vector2[] { A, D };
        else if (onLineB && onLineC && (B != C))
            return new Vector2[] { B, C };
        else if (onLineB && onLineD && (B != D))
            return new Vector2[] { B, D };
        
        //若不符合以上条件,则不重合
        return null;
    }

    // 判断点是否在线段上,目标点M,线段AB
    bool IsPointOnLine(Vector2 M, Vector2 A, Vector2 B)
    {
        //可以直接利用PointPosionOfLine(M, A, B)方法计算
        //if (IsZero(PointPosionOfLine(M, A, B)))
        //    return true;
        //else
            //return false;

        //这里提供另一种计算方式,略显麻烦

        //与端点重合
        if (M == A || M == B)
            return true;
        
        //在同一竖直方向,线段竖直,点在该线段所在的直线上
        if (IsZero(A.x - B.x) && IsZero(A.x - M.x))
        {
            //已判定点在直线上,若点在两端点中间,即点在线段上
            //if ((M.y < B.y && M.y > A.y) || (M.y < A.y && M.y > B.y)),化简为:
            if ((A.y - M.y) * (M.y - B.y) > 0.0f)
            {
                return true;
            }
            return false;
        }
        //在同一水平方向
        else if (IsZero(A.y - B.y) && IsZero(A.y - M.y))
        {
            if ((A.x - M.x) * (M.x - B.x) > 0.0f)
            {
                return true;
            }
            return false;
        }
        //线段倾斜,此时线段所在直线存在斜率
        else
        {
            //点在直线上,MA与MB斜率相等,且有共同点M,此时MA与MB重合,即点M在直线AB上
            //(A.y - M.y) / (A.x - M.x) == (M.y - B.y) / (M.x - B.x))
            if (IsZero((A.y - M.y) / (A.x - M.x) - (M.y - B.y) / (M.x - B.x)))
            {
                //已判定点在直线上,若点在两端点中间,即点在线段上
                if (((A.y - M.y) * (M.y - B.y) > 0) && ((A.x - M.x) * (M.x - B.x) > 0))
                {
                    return true;
                }
            }
            return false;
        }
    }

    // ----------

    //合并两条线段,线段AB,线段CD
    public Vector2[] LineCombine(Vector2 A, Vector2 B, Vector2 C, Vector2 D)
    {
        //与LineCoincide(Vector2 A, Vector2 B, Vector2 C, Vector2 D)相似

        //可能情况说明:
        //01 有一个公共端点,两线段反向,夹角180度
        //  A == C && AB/CD共线
        //  A == D && AB/CD共线
        //  B == C && AB/CD共线
        //  B == D && AB/CD共线
        //02 交错关系,各包含一个端点,比如:线段AB的端点A在线段CD上,线段CD的端点C在线段AB上
        //  A != C && onLineA && onLineC
        //  A != D && onLineA && onLineD 
        //  B != C && onLineB && onLineC 
        //  B != D && onLineB && onLineD
        //03 包含关系,一条线段包含在另一条线段内,比如:线段AB的端点A和端点B都在线段CD上
        //  onLineA && onLineB
        //  onLineC && onLineD

        //以上01和02两种情况可以合并如下
        //一般情况下,点A在线段CD上,C在AB上,此时AB/CD已经共线,但A/C是同一点的时候特殊考虑,需要验证夹角180度
        //  onLineA && onLineC && AB/CD共线
        //  onLineA && onLineD && AB/CD共线
        //  onLineB && onLineC && AB/CD共线
        //  onLineB && onLineD && AB/CD共线

        //再将以上三种情况合并
        //只有两线段共线,才有可能合并

        //计算各个端点与线段的关系(点是否在线段上)
        bool onLineA, onLineB, onLineC, onLineD;
        onLineA = IsPointOnLine(A, C, D);
        onLineB = IsPointOnLine(B, C, D);
        onLineC = IsPointOnLine(C, A, B);
        onLineD = IsPointOnLine(D, A, B);

        //01,02,03
        if (IsLineDirection(A, B, C, D))
        {
            if (onLineA && onLineC)
                return new Vector2[] { B, D };
            else if (onLineA && onLineD)
                return new Vector2[] { B, C };
            else if (onLineB && onLineC)
                return new Vector2[] { A, D };
            else if (onLineB && onLineD)
                return new Vector2[] { A, C };
            else if (onLineA && onLineB)
                return new Vector2[] { C, D};
            else if (onLineC && onLineD)
                return new Vector2[] { A, B };
        }

        //若不符合以上条件,则不可合并
        return null;
    }

    //是否线段共线,线段AB,CD
    bool IsLineDirection(Vector2 A, Vector2 B, Vector2 C, Vector2 D)
    {
        // dotValue == 1 0度
        // dotValue == 0 90度
        // dotValue == -1 180度
        float dotValue = Vector2.Dot((A - B).normalized, (C - D).normalized);
        if (IsZero(dotValue - 1) || IsZero(dotValue + 1))
            return true;
        else
            return false;
    }

    // ----------

    //判断float == 0
    bool IsZero(float floatValue)
    {
        if (floatValue > -0.00001f & floatValue < 0.00001f)
            return true;
        else
            return false;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_39108767/article/details/81673921