交点 - 射线与AABB交点 - slab算法

发布时间 2023-12-04 00:21:18作者: yanghui01

slab指的就是一组平行线之间的距离

AABB的width为平行于y轴的两条边之间的距离,叫x-slab;height为平行于x轴的两条边之间的距离,y-slab;

x-slab和y-slab重叠的部分,就是矩形框;

 

判断依据

如果射线和AABB相交,则射线与x-slab相交部分和y-slab相交部分必定有重叠

  

射线与AABB相交的条件就是判断共线线段是否重叠的逻辑:max(两线段的min端点) <= min(两线段的max端点)

 

射线方程

设射线方程为:r=o+t*dir,其中o为射线起点,dir为射线方向,t为射线方向的距离(是一个变量) 

射线与AABB的交点肯定也在射线上,所以交点的坐标可以表示为:

,所以:t = (x-o.x) / dir.x,或 t= (y-o.y) / dir.y

 

点与x-slab相交式,交点的x坐标就是min.x,max.x;y坐标无法知道,此时t用t = (x-o.x) / dir.x来表示:

ta=(min.x-o.x)/dir.x,tb=(max.x-o.x)/dir.x, ta < tb

 

点与y-slab相交,交点的x坐标无法知道,y坐标就是min.y, max.y,此时t用t= (y-o.y) / dir.y来表示

tc=(min.y-o.y)/dir.y,td=(max.y-o.y)/dir.y, tc < td

 

所以,如果max(ta, tc) > min(tb, td)则不相交

 

 

//求射线矩形交点
public static bool IsRayRectIntersect(Vector2 o, Vector2 dir, Vector2 min, Vector2 max, out Vector2 pNear, out Vector2 pFar)
{
    pNear = Vector2.zero;
    pFar = Vector2.zero;

    float tmin = 0;
    float tmax = 1;
    if (Mathf.Approximately(dir.x, 0)) //y轴平行
    {
        if (o.x < min.x || o.x > max.x)
            return false;

        float t1 = min.y - o.y;
        float t2 = max.y - o.y;
        if (dir.y > 0)
        {
            tmin = min.y - o.y;
            tmax = max.y - o.y;
        }
        else
        {
            tmin = o.y - max.y;
            tmax = o.y - min.y;
        }
        if (tmax < 0)
            return false;
    }
    else if (Mathf.Approximately(dir.y, 0)) //x轴平行
    {
        if (o.y < min.y || o.y > max.y)
            return false;

        if (dir.x > 0)
        {
            tmin = min.x - o.x;
            tmax = max.x - o.x;
        }
        else
        {
            tmin = o.x - max.x;
            tmax = o.x - min.x;
        }
        if (tmax < 0)
            return false;
    }
    else
    {

        float tx1 = (min.x - o.x) / dir.x; //x-slab第1个交点
        float tx2 = (max.x - o.x) / dir.x; //x-slab第2个交点
        if (tx1 > tx2) //射线在x方向上从右往左时
        {
            float temp = tx1;
            tx1 = tx2;
            tx2 = temp;
        }

        float ty1 = (min.y - o.y) / dir.y; //y-slab第1个交点
        float ty2 = (max.y - o.y) / dir.y; //y-slab第2个交点
        if (ty1 > ty2) //射线在y方向上从上往下时
        {
            float temp = ty1;
            ty1 = ty2;
            ty2 = temp;
        }

        //共线线段无重叠:max(两线段的min端点) > min(两线段的max端点)
        tmin = Mathf.Max(tx1, ty1);
        tmax = Mathf.Min(tx2, ty2);
        if (tmin > tmax) //线段没相交
            return false;

        if (tmax < 0) //射线起点不在AABB内
            return false;
    }

    pFar = o + dir * tmax;
    if (tmin < 0) //起点在AABB内, 则只有一个交点
        pNear = pFar;
    else
        pNear = o + dir * tmin;
    return true;
}

 

效果

 

测试代码

using System;
using UnityEditor;
using UnityEngine;

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

    public Transform m_Min;
    public Transform m_Max;

    public Vector2 m_Point1; //交点1
    public Vector2 m_Point2; //交点2

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

    void Update()
    {
        m_IsIntersect = false;
        m_Point1 = Vector3.zero;
        m_Point2 = Vector3.zero;
        if (m_RayEnd && m_Min && m_Max)
        {
            var origin = this.transform.position;
            var dir = m_RayEnd.position - origin;
            dir.Normalize();

            var t1 = DateTime.Now;
            switch (m_ApiType)
            {
            case 1:
                for (int i = 0; i < m_InvokeCount; ++i)
                {
                    m_IsIntersect = Shape2DHelper.IsRayRectIntersect(origin, dir, m_Min.position, m_Max.position, out m_Point1, out m_Point2);
                }
                break;
            }

            CheckTimeCost(t1, 1);
        }
    }

    private void OnDrawGizmos()
    {
        if (m_RayEnd && m_Min && m_Max)
        {
            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);
                DrawRect(m_Min.position, m_Max.position);

                Gizmos.color = Color.green;
                DrawPoint(m_Point1, ref m_CubeSize);
                DrawPoint(m_Point2, ref m_CubeSize);

                Gizmos.color = Color.white;
            }
            else
            {
                Gizmos.DrawLine(origin, endPoint);
                DrawRect(m_Min.position, m_Max.position);
            }
        }
    }

    private static void DrawRect(Vector2 min, Vector2 max)
    {
        var leftTop = new Vector2(min.x, max.y);
        var rightBottom = new Vector2(max.x, min.y);

        Gizmos.DrawLine(min, leftTop);
        Gizmos.DrawLine(leftTop, max);
        Gizmos.DrawLine(max, rightBottom);
        Gizmos.DrawLine(rightBottom, min);
    }

    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);
    }

}

 

参考

检测射线与矩形相交_射线是否穿过矩形-CSDN博客

射线与AABB相交检测-CSDN博客

 

 

相交的情况参考

   

    

   

  

 

不相交的情况参考