交点 - 射线与线段交点 - 直线方程方式

发布时间 2023-11-29 00:39:41作者: yanghui01

效果

 

//求射线与线段交点 - 直线方程方式
public static bool IsRaySegmentIntersect(Vector2 o, Vector2 dir, Vector2 a, Vector2 b, out Vector2 point)
{
    point = Vector2.zero;

    //已知直线上的两点a, b, 直线的两点式为: (x-a.x)/(b.x-a.x)=(y-a.y)/(b.y-a.y)
    //展开并整理后可得: (b.y-a.y)*x + (a.x-b.x)*y = a.x*.b.y - a.y*b.x
    //用A表示(b.y-a.y), 用B表示(a.x-b.x), 用E表示(a.x*.b.y - a.y*b.x), 就表示成A*x+B*y=E
    float A = (b.y - a.y);
    float B = (a.x - b.x);
    float E = a.x * b.y - a.y * b.x;

    //射线上的两点c, d
    var c = o;
    var d = o + dir;
    //射线所在直线的方程: C*x+D*y=F
    float C = (d.y - c.y);
    float D = (c.x - d.x);
    float F = c.x * d.y - c.y * d.x;

    //利用矩阵求直线方程组求出交点
    //x=(D*E-B*F)/(A*D-B*C)
    //y=(-C*E+A*F)/(A*D-B*C)
    float detDown = A * D - B * C;

    //先用叉乘判断下两直线是否平行, cd×ab的结果为0时表示两直线平行或共线
    //cd×ab=(d-c)×(b-a)=(d.x-c.x)*(b.y-a.y)-(d.y-c.y)*(b.x-a.x)=A*(-D) - (-B)*C
    //和detDown只是符号的差别, 所以可以直接用detDown
    if (Mathf.Approximately(detDown, 0)) //float.Epsilon
        return false;

    // 线段所在直线的交点坐标 (x , y)
    float invDetDown = 1 / detDown;
    float x = (D * E - B * F) * invDetDown;
    float y = (-C * E + A * F) * invDetDown;

    //交点不在线段ab上, 则不相交
    var ab = b - a;

    if (Mathf.Approximately(ab.x, 0)) //与y轴平行
    {
        if ((y - a.y) * (y - b.y) > 0) return false;
    }
    else if (Mathf.Approximately(ab.y, 0)) //与x轴平行
    {
        if ((x - a.x) * (x - b.x) > 0) return false;
    }
    else if ((x - a.x) * (x - b.x) > 0 || (y - a.y) * (y - b.y) > 0)
    {
        return false;
    }

    var p = new Vector2(x, y);
    //交点在射线反方向, 则不相交
    var op = p - o;
    if (Vector2.Dot(dir, op) < 0)
        return false;

    point = p;
    return true;
}

 

测试代码

using System;
using UnityEditor;
using UnityEngine;

public class RaySegmentTest : CollideTestBase
{
    public Transform m_RayEnd; //射线指向位置

    public Transform m_A;
    public Transform m_B;

    public Vector2 m_Point; //交点

    private Vector3 m_CubeSize = new Vector3(0.02f, 0.02f, 0.01f);

    void Update()
    {
        m_IsIntersect = false;
        m_Point = Vector3.zero;
        if (m_RayEnd && m_A && m_B)
        {
            var origin = this.transform.position;
            var dir = m_RayEnd.position - origin;

            var t1 = DateTime.Now;
            switch (m_ApiType)
            {
            case 1:
                for (int i = 0; i < m_InvokeCount; ++i)
                    m_IsIntersect = Shape2DHelper.IsRaySegmentIntersect(origin, dir, m_A.position, m_B.position, out m_Point);
                break;
            }

            CheckTimeCost(t1, 1);
        }
    }

#if UNITY_EDITOR

    private void OnDrawGizmos()
    {
        if (m_RayEnd && m_A && m_B)
        {
            var origin = this.transform.position;
            origin.z = 0;
            var endPoint = m_RayEnd.position;
            endPoint.z = 0;

            if (m_IsIntersect)
            {
                Gizmos.color = Color.red;
                Gizmos.DrawLine(origin, endPoint);
                Gizmos.DrawLine(m_A.position, m_B.position);

                Gizmos.color = Color.green;
                DrawPoint(m_Point, ref m_CubeSize);
                Gizmos.color = Color.white;
            }
            else
            {
                Gizmos.DrawLine(origin, endPoint);
                Gizmos.DrawLine(m_A.position, m_B.position);
            }
        }
    }

    private static void DrawPoint(Vector2 point, ref Vector3 pointCubeSize)
    {
        float handleSize = HandleUtility.GetHandleSize(point) * 0.1f;
        pointCubeSize.Set(handleSize, handleSize, 0.01f);
        Gizmos.DrawCube(point, pointCubeSize);
    }

#endif

}

 

参考

直线射线线段的相交判断_判断射线与线段是否相交-CSDN博客