当前位置:   article > 正文

射线与AABB相交检测_liqiangeastsun

liqiangeastsun

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

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

如下图:

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

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

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

则有下面几条规则:

2D 中

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

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

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

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

射线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各个面的相交结果

射线与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定义

  1. public class AABB
  2. {
  3. public Vector3 min;
  4. public Vector3 max;
  5. public AABB(Vector3 min, Vector3 max)
  6. {
  7. this.min = min;
  8. this.max = max;
  9. }
  10. }

逻辑代码

  1. /// <summary>
  2. /// 射线与AABB相交检测
  3. /// 下面方法是 射线与 3D AABB 相交的计算
  4. /// 如果想计算 射线与 2D AABB 相交,则将下方关于 z 坐标的部分删除即可
  5. /// </summary>
  6. public class RayAABBCollision
  7. {
  8. /// <summary>
  9. /// 判断射线与AABB是否相交
  10. /// </summary>
  11. /// <param name="source">射线起点</param>
  12. /// <param name="rayDir">射线方向</param>
  13. /// <param name="aabb">AABB</param>
  14. /// <returns></returns>
  15. public bool IsCollision(Vector3 source, Vector3 rayDir, AABB aabb)
  16. {
  17. float length = 0;
  18. return IsCollision(source, rayDir, aabb, ref length);
  19. }
  20. /// <summary>
  21. /// 判断射线与AABB是否相交
  22. /// </summary>
  23. /// <param name="raySource">射线起点</param>
  24. /// <param name="rayDir">射线方向</param>
  25. /// <param name="aabb">AABB</param>
  26. /// <param name="point">射线与AABB交点坐标</param>
  27. /// <returns></returns>
  28. public bool IsCollision(Vector3 raySource, Vector3 rayDir, AABB aabb, ref Vector3 point)
  29. {
  30. float length = 0;
  31. bool collision = IsCollision(raySource, rayDir, aabb, ref length);
  32. point = raySource + rayDir * length;
  33. return collision;
  34. }
  35. /// <summary>
  36. /// 判断射线与AABB是否相交
  37. /// </summary>
  38. /// <param name="raySource">射线起点</param>
  39. /// <param name="rayDir">射线方向向量</param>
  40. /// <param name="aabb">AABB</param>
  41. /// <param name="length">射线起点到相交点距离</param>
  42. /// <returns></returns>
  43. public bool IsCollision(Vector3 raySource, Vector3 rayDir, AABB aabb, ref float length)
  44. {
  45. float t1 = 0;
  46. float t2 = 0;
  47. bool collision = Calculate(raySource, rayDir, aabb, new Vector3(1, 0, 0), ref t1, ref t2);
  48. if (!collision || !CheckValue(ref t1, ref t2))
  49. {
  50. return false;
  51. }
  52. float t3 = 0;
  53. float t4 = 0;
  54. collision = Calculate(raySource, rayDir, aabb, new Vector3(0, 1, 0), ref t3, ref t4);
  55. if (!collision || !CheckValue(ref t3, ref t4))
  56. {
  57. return false;
  58. }
  59. float t5 = 0;
  60. float t6 = 0;
  61. collision = Calculate(raySource, rayDir, aabb, new Vector3(0, 0, 1), ref t5, ref t6);
  62. if (!collision || !CheckValue(ref t5, ref t6))
  63. {
  64. return false;
  65. }
  66. float entryT1 = Math.Max(Math.Max(t1, t3), t5);
  67. float entryT2 = Math.Min(Math.Min(t2, t4), t6);
  68. length = entryT1;
  69. return (entryT1 < entryT2);
  70. }
  71. /// <summary>
  72. /// 射线与平面相交计算
  73. /// </summary>
  74. /// <param name="raySource">射线起点</param>
  75. /// <param name="rayDir">射线方向向量</param>
  76. /// <param name="aabb">aabb</param>
  77. /// <param name="normal">平面法向量</param>
  78. /// t = (d - Dot(raySource, normal)) / Dot(rayDir, normal)
  79. /// d = Dot(planePoint, normal)
  80. /// t = (Dot(planePoint, normal) - Dot(raySource, normal)) / Dot(rayDir, normal)
  81. /// t = Dot((planePoint - raySource), normal) / Dot(rayDir, normal)
  82. private bool Calculate(Vector3 raySource, Vector3 rayDir, AABB aabb, Vector3 normal, ref float t1, ref float t2)
  83. {
  84. float p_sub_r_dot_n1 = Vector3.Dot(aabb.min - raySource, normal);
  85. float p_sub_r_dot_n2 = Vector3.Dot(aabb.max - raySource, normal);
  86. float r_dot_n = Vector3.Dot(rayDir, normal);
  87. if (Math.Abs(r_dot_n) <= float.Epsilon) // 射线垂直于平面法向量,所以射线与平面平行
  88. {
  89. float dot1 = Vector3.Dot(aabb.min - raySource, normal);
  90. float dot2 = Vector3.Dot(aabb.max - raySource, normal);
  91. if (dot1 * dot2 > 0)
  92. {
  93. return false;
  94. }
  95. }
  96. t1 = p_sub_r_dot_n1 / r_dot_n;
  97. t2 = p_sub_r_dot_n2 / r_dot_n;
  98. return true;
  99. }
  100. private bool CheckValue(ref float value1, ref float value2)
  101. {
  102. if (value1 < 0 && value2 < 0)
  103. {
  104. return false;
  105. }
  106. value1 = Mathf.Clamp(value1, 0, value1);
  107. value2 = Mathf.Clamp(value2, 0, value2);
  108. if (value1 > value2)
  109. {
  110. float temp = value1;
  111. value1 = value2;
  112. value2 = temp;
  113. }
  114. return true;
  115. }
  116. }

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/酷酷是懒虫/article/detail/997426
推荐阅读
相关标签
  

闽ICP备14008679号