射线与AABB相交检测

Box2D使用了一个叫做 slab 的碰撞检测算法。

在2D中AABB是一个矩形边界框,slab 指的是矩形一组平行线之间的范围,所以在2D中矩形边界框四条边,两两一组,可以组成两个 slab。

如下图:

平行于Y轴的两条边(紫色线)之间的范围是 x-slab,范围无限长,宽度限制在两条紫色线之间

平行于X轴的两条边(红色线)之间的范围是 y-slab ,范围无限长,宽度限制在两条红色线之间

中间灰色比较深的部分就是矩形框,它是两个slab的重合部分,即属于x-slab 也属于 y-slab 

射线与AABB相交检测_第1张图片

则有下面几条规则:

2D 中

1.如果一个点在 AABB内,则该点必定同时在 x-slab、y-slab

2.如果一条射线和AABB相交,则这条射线和 两个 slab 相交部分必定有重合部分

3.同理在3D中 1、2 依然满足,因为3D AABB是六个面,有三对相互平行的面,每一对平行的面之间的范围就是一个 slab。因为 3D 不好绘制,下面依然用 2D讲述

射线与AABB边界框相交情况如下图

射线与AABB相交检测_第2张图片

射线R1

在 t1 位置进 y-slab

在 t2 位置出 y-slab

在 t3 位置进 x-slab

在 t4 位置出  x-slab

注:t1、t2、t3、t4 为从射线起点到各个交点的距离

但是 区间 (t1, t2) 和 (t3, t4) 没有重叠部分,则判定R1与AABB不相交

射线R2

在 t1 位置进 x-slab

在 t3 位置进 y-slab

在 t2 位置出 x-slab

在 t4 位置出  y-slab

区间 (t1, t2) 和 (t3, t4) 有重叠部分(图中绿色线条) t3 到 t2 部分,绿色线条部分既属于 x-slab,又属于 y-slab 部分,则判定射线 R2 与 AABB相交

由上得出结论,如果射线与AABB各个面相交的 t 区间 有重叠部分,则射线与AABB相交

还有一种情况就是射线的反向延长线与 slab 相交,判定为相交无效

射线与AABB相交检测_第3张图片

下面计算射线与AABB各个面的相交结果

射线与x-slab相交的t值为 (t1,t2),t1 < t2

射线与y-slab相交的t值为 (t3,t4),t3 < t4

射线与z-slab 相交的 t值为 (t5,t6),t5 < t6

只要满足区间 (t1,t2) ,(t3,t4),(t5,t6),在一维数轴上有重叠部分就说明射线与AABB相交,到此可以发现我们将 3D、2D 问题降维到了 1维 数轴上数值比较的问题了。

方法就是判断 t1、t3、t5中最大值 < t2、t4、t6中最小值

相交条件为:Max(t1, t3, t5 ) < Min(t2, t4,t6)

射线方程: p + t * rayDir

其中 p为 射线起点,rayDir 为射线的方向向量,t 为射线长度系数。

平面方程为 S * n = d

注:上边两个方程中 * 的作用

当用在  点 * 向量 或 向量 * 向量 时就是 向量点乘(Dot)

当用在 数值 * 向量 就是 数值乘 (X)

其中 S 为平面上的点,n为平面法向量,d为原点(0, 0, 0)到平面的距离。

如果射线与平面相交,令交点为 V,则点 V 即在射线上,也在平面上

代入射线方程中:V = p + t * rayDir

代入平面方程中:V * n = d,即 Dot( V , n ) = d

注:下面使用 Dot 表示点乘

将 V 展开为(p + t * rayDir)得到

Dot(( p + t * rayDir) , n) = d

Dot(p, n) + Dot(t * rayDir, n) = d

Dot(t * rayDir, n) = d - Dot(p, n)

t * Dot(rayDir, n) = d - Dot(p, n)

t = (d - Dot(p, n)) / Dot(rayDir, n)

射线与平面相交点距离射线起点距离t的距离公式为 t = (d - Dot(p, n)) / Dot(rayDir, n)

注意:Dot(rayDir, n), 射线向量与AABB某个面法向量点乘,当这两个向量垂直时,也就是射线平行于平面时,点乘结果为 0

AABB 以最小点 Min(x, y, z)、最大点Max(x, y, z) 表示

AABB 的六个面的方程满足

Dot(Min, normal) = d

Dot(Max, normal) = d

其中 normal 可分别表示为 AABB的三个轴向 (1, 0, 0),(0, 1, 0),(0, 0, 1)

求射线与 x-slab 相交值为 t1,t2

令t1< t2,如果 t1 > t2 ,则 t1、t2值互换

求射线与 y-slab相交值为 t3,t4

令t3< t4,如果 t3 > t4 ,则t3、t4值互换

求射线与 z-slab相交值为 t5,t6

令t5< t6,如果 t5 > t6 ,则t5、t6值互换

(t1,t2) ,(t3,t4),(t5,t6) 如果有一组数据两个值都小于 0,如 (t1 = -1、t2 = -3) ,则射线与AABB不相交,

下一步:如果 t1、t2 、t3 、t4、 t5 、t6 有 小于 0 的,令其值 = 0

代码逻辑如下,关于图3的情况我还没有验证

AABB定义

public class AABB
{
    public Vector3 min;
    public Vector3 max;

    public AABB(Vector3 min, Vector3 max)
    {
        this.min = min;
        this.max = max;
    }
}

逻辑代码

/// 
/// 射线与AABB相交检测
/// 下面方法是 射线与 3D AABB 相交的计算
/// 如果想计算 射线与 2D AABB 相交,则将下方关于 z 坐标的部分删除即可
/// 
public class RayAABBCollision
{
    /// 
    /// 判断射线与AABB是否相交
    /// 
    /// 射线起点
    /// 射线方向
    /// AABB
    /// 
    public bool IsCollision(Vector3 source, Vector3 rayDir, AABB aabb)
    {
        float length = 0;
        return IsCollision(source, rayDir, aabb, ref length);
    }

    /// 
    /// 判断射线与AABB是否相交
    /// 
    /// 射线起点
    /// 射线方向
    /// AABB
    /// 射线与AABB交点坐标
    /// 
    public bool IsCollision(Vector3 raySource, Vector3 rayDir, AABB aabb, ref Vector3 point)
    {
        float length = 0;
        bool collision = IsCollision(raySource, rayDir, aabb, ref length);
        point = raySource + rayDir * length;
        return collision;
    }

    /// 
    /// 判断射线与AABB是否相交
    /// 
    /// 射线起点
    /// 射线方向向量
    /// AABB
    /// 射线起点到相交点距离
    /// 
    public bool IsCollision(Vector3 raySource, Vector3 rayDir, AABB aabb, ref float length)
    {
        float t1 = 0;
        float t2 = 0;
        bool collision = Calculate(raySource, rayDir, aabb, new Vector3(1, 0, 0), ref t1, ref t2);
        if (!collision || !CheckValue(ref t1, ref t2))
        {
            return false;
        }

        float t3 = 0;
        float t4 = 0;
        collision = Calculate(raySource, rayDir, aabb, new Vector3(0, 1, 0), ref t3, ref t4);
        if (!collision || !CheckValue(ref t3, ref t4))
        {
            return false;
        }

        float t5 = 0;
        float t6 = 0;
        collision = Calculate(raySource, rayDir, aabb, new Vector3(0, 0, 1), ref t5, ref t6);
        if (!collision || !CheckValue(ref t5, ref t6))
        {
            return false;
        }

        float entryT1 = Math.Max(Math.Max(t1, t3), t5);
        float entryT2 = Math.Min(Math.Min(t2, t4), t6);
        length = entryT1;
        return (entryT1 < entryT2);
    }

    /// 
    /// 射线与平面相交计算
    /// 
    /// 射线起点
    /// 射线方向向量
    /// aabb
    /// 平面法向量
    /// t = (d - Dot(raySource, normal)) / Dot(rayDir, normal)
    /// d = Dot(planePoint, normal)
    /// t = (Dot(planePoint, normal)  - Dot(raySource, normal)) / Dot(rayDir, normal)
    /// t = Dot((planePoint - raySource), normal) / Dot(rayDir, normal)
    private bool Calculate(Vector3 raySource, Vector3 rayDir, AABB aabb, Vector3 normal, ref float t1, ref float t2)
    {
        float p_sub_r_dot_n1 = Vector3.Dot(aabb.min - raySource, normal);
        float p_sub_r_dot_n2 = Vector3.Dot(aabb.max - raySource, normal);
        float r_dot_n = Vector3.Dot(rayDir, normal);

        if (Math.Abs(r_dot_n) <= float.Epsilon)  // 射线垂直于平面法向量,所以射线与平面平行
        {
            float dot1 = Vector3.Dot(aabb.min - raySource, normal);
            float dot2 = Vector3.Dot(aabb.max - raySource, normal);
            if (dot1 * dot2 > 0)
            {
                return false;
            }
        }

        t1 = p_sub_r_dot_n1 / r_dot_n;
        t2 = p_sub_r_dot_n2 / r_dot_n;
        return true;
    }

    private bool CheckValue(ref float value1, ref float value2)
    {
        if (value1 < 0 && value2 < 0)
        {
            return false;
        }
        value1 = Mathf.Clamp(value1, 0, value1);
        value2 = Mathf.Clamp(value2, 0, value2);
        if (value1 > value2)
        {
            float temp = value1;
            value1 = value2;
            value2 = temp;
        }
        return true;
    }
}

测试结果,测试动画总AABB边红色为相交

射线与AABB相交检测_第4张图片

你可能感兴趣的:(3D数学基础图形,射线与AABB相交检测)