交点 - 射线与AABB交点 - slab算法
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,此时可以用t = (x-o.x) / dir.x来求出t:
ta=(min.x-o.x)/dir.x,tb=(max.x-o.x)/dir.x, ta < tb
点与y-slab相交时,交点的y坐标就是min.y, max.y,此时可以用t= (y-o.y) / dir.y来求出t:
tc=(min.y-o.y)/dir.y,td=(max.y-o.y)/dir.y, tc < td
所以,判断的依据可以转变成:max(ta, tc) <= min(tb, 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; tmin = (min.y - o.y) * dir.y; //dir.y不是1就是-1 tmax = (max.y - o.y) * dir.y; if (tmin > tmax) { float temp = tmin; tmin = tmax; tmax = temp; } } else if (Mathf.Approximately(dir.y, 0)) //x轴平行 { if (o.y < min.y || o.y > max.y) return false; //用上面那种: *dir.x的方式也是一样的 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; } } else { float invDirX = 1 / dir.x; float tx1 = (min.x - o.x) * invDirX; //x-slab第1个交点 float tx2 = (max.x - o.x) * invDirX; //x-slab第2个交点 if (tx1 > tx2) //射线在x方向上从右往左时 { float temp = tx1; tx1 = tx2; tx2 = temp; } float invDirY = 1 / dir.y; float ty1 = (min.y - o.y) * invDirY; //y-slab第1个交点 float ty2 = (max.y - o.y) * invDirY; //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) 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); } }
参考
相交的情况参考
不相交的情况参考