歡迎您光臨本站 註冊首頁

Unity實現圖形相交檢測

←手機掃碼閱讀     zmcjlove @ 2020-05-05 , reply:0

前言
圖形相交檢測常常用在傷害判定,使用自定義的圖形相交檢測,可以在一定程度上控制性能。
比如2D格鬥遊戲中使用的矩形包圍盒(AABB),一些動作遊戲中常常出現的扇形攻擊。
2D的圖形相交檢測能夠滿足大部分的需求,且可以拓展成為柱狀的3D物體,2D比3D的計算複雜度會低很多,3D的圖形檢測原理與2D相似,本文會實現幾個圓形與其他2D圖形的相交檢測:
1、圓形與圓形
2、圓形與膠囊體
3、圓形與扇形
4、圓形與凸多邊形
5、圓形與AABB
6、圓形與OBB
通過簡單化處理,把被判定物都處理成由圓柱或多個圓柱構成的區域,所以只需要考慮圓形與其他形狀的相交。
圓形與圓形
兩個圓形的相交檢測非常簡單直觀,只需要判斷半徑只和與距離的大小。
定義圓形區間:
///

/// 圓形區間 ///

public struct CircleArea { public Vector2 o; public float r; }
o ――圓心座標
r ――圓半徑
相交判斷:
///

/// 判斷圓形與圓形相交 ///

///

///

///

public static bool Circle(CircleArea circleArea, CircleArea target) { return (circleArea.o - target.o).sqrMagnitude < (circleArea.r + target.r) * (circleArea.r + target.r); }
分離軸定理
分離軸定理(separating axis theorem, SAT)分離軸定理是指,兩個不相交的凸集必然存在一個分離軸,使兩個凸集在該軸上的投影是分離的。
判斷兩個形狀是否相交,實際上是判斷分離軸是否能把兩個形狀分離。若存在分離軸能使兩個圖形分離,則這兩個圖形是分離的。
基於以上理論,尋找分離軸是我們要做的工作,重新考慮兩個圓形的相交檢測,實際上我們做的是把圓心連線的方向作為分離軸:
上圖中兩圖形的投影在分離軸上是分離的,存在分離線將兩者隔開,於是我們可以斷定兩圖形是分離的。
膠囊體的本質
定義一個線段 u,距離 d。膠囊體實際上是與線段 u 的最短距離小於 d 的點的集合。判斷一個點 x 處於膠囊體內部,就是判斷點與線段的距離。
求點 x 與線段 u 最短距離的過程是:
1、求出點 x 在線段 u 所在直線上的投影點 P;
2、將投影點 P 限制在線段的範圍內(如右圖中投影點不在線段內,則限定到線段內);
3、x 與 P 的距離即為所求;
///

/// 線段與點的最短距離。 ///

///

線段起點///

線段向量///

求解點///

public static float SqrDistanceBetweenSegmentAndPoint(Vector2 x0, Vector2 u, Vector2 x) { float t = Vector2.Dot(x - x0, u) / u.sqrMagnitude; return (x - (x0 + Mathf.Clamp01(t) * u)).sqrMagnitude; }
為避免開方計算,結果使用距離的平方。
圓形與膠囊體
分離軸是線段上距離圓心最近的點P與圓心所在方向。
定義膠囊體:
///

/// 膠囊體 ///

public struct CapsuleArea { public Vector2 X0; public Vector2 U; public float d; }
相交判斷:
///

/// 判斷膠囊體與圓形相交 ///

///

///

///

public static bool Capsule(CapsuleArea capsuleArea, CircleArea circleArea) { float sqrD = SegmentPointSqrDistance(capsuleArea.X0, capsuleArea.U, circleArea.o); return sqrD < (circleArea.r + capsuleArea.d) * (circleArea.r + capsuleArea.d); }
圓形與扇形
當扇形角度大於180度時,就不再是凸多邊形了,不能適用於分離軸理論。我們可以找出相交時圓心的所有可能區域,並把區域劃分成可以簡單驗證的幾個區域,逐個試驗。
這裡共劃分了2個區間
1、半徑為兩者半徑和的扇形區間,角度方向同扇形。驗證方法是;驗證距離與夾角。
2、扇形邊為軸,圓形半徑為大小組成的膠囊體空間,由於扇形的對稱性,我們可以通過把圓心映射到一側,從而只需要計算1條邊。
定義扇形:
///

/// 扇形區間。 ///

public struct SectorArea { public Vector2 o; public float r; public Vector2 direction; public float angle; }
相交檢測:
///

/// 判斷圓形與扇形相交。 ///

///

///

///

public static bool Sector(SectorArea sectorArea, CircleArea target) { Vector2 tempDistance = target.o - sectorArea.o; float halfAngle = Mathf.Deg2Rad * sectorArea.angle / 2; if (tempDistance.sqrMagnitude < (sectorArea.r + target.r) * (sectorArea.r + target.r)) { if (Vector3.Angle(tempDistance, sectorArea.direction) < sectorArea.angle / 2) { return true; } else { Vector2 targetInSectorAxis = new Vector2(Vector2.Dot(tempDistance, sectorArea.direction), Mathf.Abs(Vector2.Dot(tempDistance, new Vector2(-sectorArea.direction.y, sectorArea.direction.x)))); Vector2 directionInSectorAxis = sectorArea.r * new Vector2(Mathf.Cos(halfAngle), Mathf.Sin(halfAngle)); return SegmentPointSqrDistance(Vector2.zero, directionInSectorAxis, targetInSectorAxis) <= target.r * target.r; } } return false; }
圓形與凸多邊形
定義多邊形:
///

/// 多邊形區域。 ///

public struct PolygonArea { public Vector2[] vertexes; }
相交檢測:
///

/// 判斷多邊形與圓形相交 ///

///

///

///

public static bool PolygonS(PolygonArea polygonArea, CircleArea target) { if (polygonArea.vertexes.Length < 3) { Debug.Log("多邊形邊數小於3."); return false; } #region 定義臨時變量 //圓心 Vector2 circleCenter = target.o; //半徑的平方 float sqrR = target.r * target.r; //多邊形頂點 Vector2[] polygonVertexes = polygonArea.vertexes; //圓心指向頂點的向量數組 Vector2[] directionBetweenCenterAndVertexes = new Vector2[polygonArea.vertexes.Length]; //多邊形的邊 Vector2[] polygonEdges = new Vector2[polygonArea.vertexes.Length]; for (int i = 0; i < polygonArea.vertexes.Length; i++) { directionBetweenCenterAndVertexes[i] = polygonVertexes[i] - circleCenter; polygonEdges[i] = polygonVertexes[i] - polygonVertexes[(i + 1)% polygonArea.vertexes.Length]; } #endregion #region 以下為圓心處於多邊形內的判斷。 //總夾角 float totalAngle = Vector2.SignedAngle(directionBetweenCenterAndVertexes[polygonVertexes.Length - 1], directionBetweenCenterAndVertexes[0]); for (int i = 0; i < polygonVertexes.Length - 1; i++) totalAngle += Vector2.SignedAngle(directionBetweenCenterAndVertexes[i], directionBetweenCenterAndVertexes[i + 1]); if (Mathf.Abs(Mathf.Abs(totalAngle) - 360f) < 0.1f) return true; #endregion #region 以下為多邊形的邊與圓形相交的判斷。 for (int i = 0; i < polygonEdges.Length; i++) if (SegmentPointSqrDistance(polygonVertexes[i], polygonEdges[i], circleCenter) < sqrR) return true; #endregion return false; }
圓形與AABB
定義AABB:
///

/// AABB區域 ///

public struct AABBArea { public Vector2 center; public Vector2 extents; }
AABB是凸多邊形的特例,是長寬邊分別與X/Y軸平行的矩形,這裡我們要充分的利用他的對稱性。
1 利用對稱性將目標圓心映射到,以AABB中心為原點、兩邊為座標軸的座標系,的第一象限
2 將目標圓心映射到,以AABB第一象限角點為原點、兩邊為座標軸的座標系,的第一象限
3 最後只需要判斷圓形半徑與步驟2中映射點的向量大小
相交檢測:
///

/// 判斷AABB與圓形相交 ///

///

///

///

public static bool AABB(AABBArea aABBArea, CircleArea target) { Vector2 v = Vector2.Max(aABBArea.center - target.o, -(aABBArea.center - target.o)); Vector2 u = Vector2.Max(v - aABBArea.extents,Vector2.zero); return u.sqrMagnitude < target.r * target.r; }
圓形與OBB
定義OBB:
///

/// OBB區域 ///

public struct OBBArea { public Vector2 center; public Vector2 extents; public float angle; }
OBB相對於AABB,矩形邊不與座標軸重合,對於它和圓形的相交檢測只需要把圓形旋轉到OBB邊所在座標系中,剩下的步驟與AABB的相同。
相交檢測:
///

/// 判斷OBB與圓形相交 ///

///

///

///

public static bool OBB(OBBArea oBBArea, CircleArea target) { Vector2 p = oBBArea.center - target.o; p = Quaternion.AngleAxis(-oBBArea.angle, Vector3.forward) * p; Vector2 v = Vector2.Max(p, -p); Vector2 u = Vector2.Max(v - oBBArea.extents, Vector2.zero); return u.sqrMagnitude < target.r * target.r; }


[zmcjlove ] Unity實現圖形相交檢測已經有316次圍觀

http://coctec.com/docs/program/show-post-232912.html