Cálculo de vectores en el juego (juzgar la superposición, la intersección de segmentos de línea, la fusión de segmentos de línea y la relación entre puntos y líneas)

Estoy participando en la competencia individual del Concurso de creatividad de juegos de la comunidad de Nuggets. Para obtener más información, consulte: Concurso de creatividad de juegos

contenido principal:

  • Producto punto y producto cruz
  • Determinar la relación posicional entre el punto y la línea.
  • Determine si dos segmentos de línea se cruzan y calcule la intersección
  • Determinar la coincidencia de dos segmentos de línea y calcular sus partes coincidentes
  • todo fusiona dos segmentos de línea superpuestos

Producto escalar (producto escalar)

  • Fórmula de cálculo: a·b = (ax,ay)·(bx,by) = axbx+ayby = |a|·|b|·cosθ, el resultado es un escalar.
  • Calcule la proyección del vector b sobre el vector a (vector unitario): longitud de proyección = |b| cosθ; proyección = a |b| cosθ
  • Calcula el ángulo entre dos vectores: cosθ = a·b / (|a|·|b|) => θ = arccos(a·b / (|a|·|b|))
  • Cuanto mayor sea el resultado del producto escalar, menor será el ángulo entre los dos vectores; se puede usar para juzgar el tamaño del ángulo.
    • Resultado del producto escalar == 1: ángulo incluido θ == 0;
    • resultado del producto escalar > 0: 0 <= ángulo incluido θ < 90;
    • resultado del producto escalar == 0: ángulo incluido θ == 90;
    • Resultado del producto escalar < 0: 90 < ángulo θ < 180;
    • Resultado del producto escalar == -1: Ángulo θ == 190.

producto cruzado (producto cruzado)

  • La fórmula de cálculo es: a×b=(ax,ay,az)×(bx,by,bz)=(aybz-azby,azbx-axbz,axby-aybx).
  • El resultado es un vector perpendicular a ayb, que se puede utilizar para determinar la posición relativa de los vectores ayb (sentido horario o antihorario).
  • |a×b| = |a|·|b|·sinθ = área de un paralelogramo con a y b como lados

imagen.png

Relación de posición entre punto y línea

Condiciones conocidas: punto M, segmento de línea l (puntos finales A, B), determinar la relación posicional entre el punto y la línea

Método de cálculo uno:

Fórmula de línea recta: a * X + b * Y + c = 0

Fórmula derivada:

  1. Sustituye los extremos del segmento en la fórmula de la línea:
    a * Ax + b * Ay + c = 0
    a * Bx + b * By + c = 0
  2. 两式分别相加、相减:
    (A.x + B.x) * a + (A.y + B.y) * b + 2c = 0
    (A.x - B.x) * a + (A.y - B.y) * b = 0
  3. 公式化简得:
    b = (B.x - A.x) / (A.y - B.y) * a
    c = -a * (A.y * B.x - A.x * B.y) / (A.y - B.y)
  4. 原直线公式用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
  5. 公式两边同时除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
  6. 继续化简:
    (A.y - B.y) * X + (B.x - A.x) * Y - (A.y * B.x - A.x * B.y) = 0
  7. 公式左侧==0,点在直线上,公式左侧>0,点在直线右侧,公式左侧<0,点在直线左侧。
  8. 将目标点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
  9. 最终整理:
    (B.y - M.y) * (A.x - M.x) - (A.y - M.y) * (B.x - M.x)

代码实现:

    //点与直线的位置,目标点M,直线AB
    public float PointOfLine(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);
    }
    
    //判断点是否在线段上,目标点M,线段AB
    public bool IsPointOnLine(Vector2 M,Vector2 A,Vector2 B)
    {
        return IsZero(PointOfLine(M,A,B));
    }
复制代码

计算方式二:

点直线段位置判断步骤:

  1. 点M与端点A或B重合;
  2. 线段没有斜率或斜率为0,判断M是否在直线AB上,在判断是否在线段内;
  3. 线段有斜率,判断线段MA与BM斜率是否一致

代码实现:

    // 判断点是否在线段上,目标点M,线段AB
    public bool IsPointOnLine(Vector2 M,Vector2 A,Vector2 B)
    {
        //与端点重合
        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上
            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;
        }
    }
复制代码

判断两线段是否相交

已知条件:线段l1(端点A、B)、线段l2(端点C、D)

两线段位相交关系(不考虑重叠的情况,如果线段重叠,也只返回一个点):

  • 完全相交:线段AB的端点分别在线段CD的两侧,且线段CD的端点也分别在线段AB的两侧;
  • 不完全相交:线段一个端点在另一条线段上(比如:端点A在线段CD上,如果端点B也在视为重叠,暂不区分重叠的情况)
  • 不相交:线段的两个端点都在另一条线段的同一侧

先计算每个端点与直线的位置关系,在判断段线段的位置关系

代码实现:

    //判断两线段交叉并计算交叉点,线段AB,线段CD
    public Vector2? LineCross(Vector2 A,Vector2 B,Vector2 C,Vector2 D)
    {
        //计算各端点与线段的关系
        float pointPosA = PointOfLine(A,C,D);
        float pointPosB = PointOfLine(B,C,D);
        float pointPosC = PointOfLine(C,A,B);
        float pointPosD = PointOfLine(D,A,B);

        //完全相交:线段AB的端点分别在线段CD的两侧,且线段CD的端点也分别在线段AB的两侧
        if(pointPosA * pointPosB < 0 && pointPosC * pointPosD < 0)
        {
            return GetCrosPoint(A,B,C,D);
        }

        //不完全相交:线段一个端点在另一条线段上
        if(IsZero(pointPosA))
            return A;
        if(IsZero(pointPosB))
            return B;
        if(IsZero(pointPosC))
            return C;
        if(IsZero(pointPosD))
            return D;
        //若不符合以上条件,则不相交
        //判断条件:pointPosA * pointPosB > 0 || pointPosC * pointPosD > 0
        return null;
    }
复制代码

计算两条相交线段的交点

直线方程:Y = aX + b

相交线段分情况处理(前提是已经确定线段相交):

  1. if 两线段都没有斜率,或者两斜率相等,此时两线段平行
    • 不存在交点
  2. else if 只有一条线段没有斜率(竖直方向的线段没有斜率)
  • 方法一:解方程式
    • 计算公式(假设线段AB不存在斜率,线段CD存在斜率):
    • 线段AB的直线方程:直线不存在斜率,X = Ax
    • 线段CD的直线方程:
      • 根据已有两点计算斜率:a2 = (D.y - C.y) / (D.x - C.x)
      • 截距:b = Y - aX = (C.y - C.x * a2)
      • 将斜率带入获得直线公式:Y = a2 * X + (C.y - C.x * a2)
    • 根据两个直线公式计算交点
      • x = A.x;
      • y = a2 * (A.x - C.x) + C.y;
  • 方式二:三角形三边关系
    • 假设线段AB不存在斜率,线段CD存在斜率,交点为M
    • 交点M在线段AB上,其x坐标与线段任意一点x坐标相同
    • 交点M在线段CD上,CM与CD在同一条直线上,两线段斜率相等
    • (Mi - Cy) / (Mx - Cx) = a2
    • Mi = a2 * (Mx - Cx) + Cy
    • Mx = Hacha
    • Mi = a2 * (Ax - Cx) + Cy
  1. de lo contrario, ambos saltos de línea tienen pendientes
    • Calcula la pendiente y la intersección de la línea donde se encuentran los dos segmentos de línea; resuelve el sistema de ecuaciones.

Código:

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

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

            //方法一:解方程式
            //线段AB的直线方程:直线不存在斜率,X = Ax
            //线段CD的直线方程:
            //根据已有两点计算斜率:a2 = (D.y - C.y) / (D.x - C.x)
            //截距:b = Y - aX = (C.y - C.x * a2)
            //将斜率带入获得直线公式:Y = a2 * X + (C.y - C.x * a2)
            //根据两个直线公式计算交点
            //x = A.x;    y = a2 * (A.x - C.x) + C.y;

            //方法二:三角形关系(不推荐)
            //计算过程如下:
            //交点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

            //最终均可得
            float a2 = (D.y - C.y) / (D.x - C.x);
            float x = A.x;
            float y = a2 * (A.x - C.x) + C.y;
            return new Vector2(x,y);
        }
        else if(IsZero(C.x - D.x))
        {
            //线段CD竖直方向,计算过程同上
            float x = C.x;
            float a1 = (B.y - A.y) / (B.x - A.x);
            float 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)

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

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

Determine si dos segmentos de línea son coincidentes y calcule la parte coincidente

Coincidencia de segmento de línea:

  • Inclusión y coincidencia: un segmento de línea está contenido en otro segmento de línea, por ejemplo: el punto final A y el punto final B del segmento de línea AB están ambos en el segmento de línea CD;
  • Coincidencia cruzada: el punto final A del segmento de línea AB está en el segmento de línea CD, el punto final C del segmento de línea CD está en el segmento de línea AB y AC no es el mismo punto;
  • No polimerización.

Determinar la coincidencia de dos segmentos de línea y calcular sus partes coincidentes, segmento de línea AB, segmento de línea CD (la parte coincidente es un segmento de línea, devuelve los dos extremos del segmento de línea coincidente, independientemente del caso en que solo haya un punto coincidente ):

    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 = IsPointOnLine(A,C,D);
        bool onLineB = IsPointOnLine(B,C,D);
        bool onLineC = IsPointOnLine(C,A,B);
        bool onLineD = IsPointOnLine(D,A,B);

        //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;
    }
复制代码

Sólo juzgue si los dos segmentos de recta coinciden, segmento de recta AB, segmento de recta CD

    public bool IsLineCoincide(Vector2 A,Vector2 B,Vector2 C,Vector2 D)
    {
        bool onLineA = IsPointOnLine(A,C,D);
        bool onLineB = IsPointOnLine(B,C,D);
        bool onLineC = IsPointOnLine(C,A,B);
        bool onLineD = IsPointOnLine(D,A,B);

        if((onLineA && onLineB) || (onLineC && onLineD))
            return true;
        if((onLineA && onLineC && (A != C))
            || (onLineA && onLineD && (A != D))
            || (onLineB && onLineC && (B != C))
            || (onLineB && onLineD && (B != D)))
            return true;
        return false;
    }
复制代码
    //判断float == 0
    bool IsZero(float floatValue)
    {
        if(floatValue > -0.00001f & floatValue < 0.00001f)
            return true;
        else
            return false;
    }
复制代码

Supongo que te gusta

Origin juejin.im/post/7085274110015569927
Recomendado
Clasificación