-
序文
衝突検出の問題は、仮想現実、コンピュータ支援設計と製造、ゲーム、ロボットなどで広く使用されており、バウンディング ボックス アルゴリズムは衝突検出の重要な方法の 1 つです。
一般的な境界ボックスには次のものがあります。
- AABB バウンディング ボックス (軸に合わせたバウンディング ボックス)
- 球
- OBB バウンディング ボックス (方向付きバウンディング ボックス)
- 凸包境界ボックス (凸包)
- ...
Unity のコライダーには次のものが含まれます。
-
導入
ゲームでは、オブジェクト間の衝突検出操作を簡略化するために、通常、オブジェクトの周囲に規則的な幾何学的形状が作成されます。したがって、AABB バウンディング ボックスは、軸に合わせたバウンディング ボックスと呼ばれます。
AABB バウンディング ボックスは比較的単純な構造で、格納スペースが小さいですが、コンパクト性が劣ります。特に不規則な幾何学的形状の場合、余分なスペースが大きくなります。オブジェクトが回転すると、それに応じてオブジェクトを回転させることはできません (OBB バウンディング ボックスを使用)。
アルゴリズムの観点から見ると、AABB 境界ボックスを記述するために必要なのは、 pointMinとpointMaxの 2 つの点だけです。
AABB バウンディング ボックスと OBB バウンディング ボックスの最も直接的な違いは次のとおりです:
1. AABB バウンディング ボックスは回転できません
2. OBB バウンディング ボックスは回転できます、つまり方向性があります。
-
2D シーンの AABB バウンディング ボックス
2 次元シーンでのパッチの衝突を次の図に示します。
青と黄色の2つのパッチの四隅をXY軸に投影し、
青パッチのY軸方向の最大点座標をY1、最小点座標Y2、X軸方向の最小点座標X1とします。黄色パッチ内の最大点座標 X2
パッチの Y 軸方向の最大点座標は Y3、最小点座標は Y4、X 軸方向の最小点座標は X3、最大の点座標は X4 です
図中の赤い部分が各軸の重なっている部分です。
AABB の衝突検出には、青いパッチと黄色のパッチがそれぞれ 2 つの座標軸に沿って投影され、両方の座標軸が重なった場合にのみ、2 つのオブジェクトは衝突が発生したことを意味するというルールがあることがわかります
。
-
3D シーンの AABB バウンディング ボックス
3 次元シーン内のオブジェクトの AABB バウンディング ボックスは六面体です。2 次元座標系の場合、Z 軸が 1 つだけ追加されます。したがって、実際には、3 次元シーン内のオブジェクトの AABB 衝突検出は
、シーンは、4 点情報の決定を使用して実装できます。
つまり、オブジェクト A の 8 つの頂点とオブジェクト B の 8 つの頂点から最大と最小の 2 つの頂点を比較のために選択します。
上の図のように、画像内の黒い点の部分の座標が決まれば、8つの頂点の情報がすべて求まります。
コードでインターフェイスを定義します。
public interface IMathAABB
{
Vector3 MinVector { get; }
Vector3 MaxVector { get; }
Vector3 Center { get; }
Vector3[] Corners { get; }
/// <summary>
/// Gets the center point of the bounding box.
/// </summary>
/// <returns>获取中心点</returns>
Vector3 GetCenter();
/// <summary>
/// Near face, specified counter-clockwise looking towards the origin from the positive z-axis.
/// verts[0] : left top front
/// verts[1] : left bottom front
/// verts[2] : right bottom front
/// verts[3] : right top front
/// Far face, specified counter-clockwise looking towards the origin from the negative z-axis.
/// verts[4] : right top back
/// verts[5] : right bottom back
/// verts[6] : left bottom back
/// verts[7] : left top back
/// </summary>
/// <returns>获取包围盒八个顶点信息</returns>
void GetCorners();
/// <summary>
/// Tests whether this bounding box intersects the specified bounding object.
/// </summary>
/// <returns>判断两个包围盒是否碰撞</returns>
bool Intersects(IMathAABB aabb);
/// <summary>
/// check whether the point is in.
/// </summary>
/// <returns>返回这个点是否在包围盒中</returns>
bool ContainPoint(Vector3 point);
/// <summary>
/// Sets this bounding box to the smallest bounding box
/// that contains both this bounding object and the specified bounding box.
/// </summary>
/// <returns>生成一个新的包围盒 同时容纳两个包围盒,新的包围盒: min各轴要是其他两个最小的那个,max各轴要是其他两个最大的那个</returns>
void Merge(IMathAABB box);
/// <summary>
/// Sets this bounding box to the specified values.
/// </summary>
/// <param name="min"></param>
/// <param name="max"></param>
/// <returns>设置</returns>
void SetMinMax(Vector3 min, Vector3 max);
/// <summary>
/// reset min and max value.
/// </summary>
/// <returns>重置</returns>
void ResetMinMax();
bool IsEmpty();
}
-
クラスAABBCC
public class AABBCC : MonoBehaviour, IMathAABB
{
//修改此值控制m_CalcMin
[SerializeField]
private Vector3 m_Min = -Vector3.one;
//修改此值控制m_CalcMax
[SerializeField]
private Vector3 m_Max = Vector3.one;
[SerializeField, AABBDisable]
private Vector3 m_Center = Vector3.zero;
//保存包围盒八个顶点
[SerializeField, AABBDisable]
private Vector3[] m_Corners = new Vector3[8];
[SerializeField]
private Transform Target;
public Vector3 MinVector
{
get
{
return m_RealCalcMin;
}
}
public Vector3 MaxVector
{
get
{
return m_RealCalcMax;
}
}
public Vector3[] Corners
{
get
{
return m_Corners;
}
}
public Vector3 Center
{
get
{
return m_Center;
}
}
/// <summary>
/// 实际计算的最小值
/// </summary>
private Vector3 m_RealCalcMin;
/// <summary>
/// 实际计算的最大值
/// </summary>
private Vector3 m_RealCalcMax;
/// <summary>
/// 防止在update之前产生碰撞
/// </summary>
private void Awake()
{
UpdatePosition();
}
// Update is called once per frame
private void Update()
{
UpdatePosition();
}
/// <summary>
/// 更新位置
/// </summary>
private void UpdatePosition()
{
// position
if (Target != null)
{
SetMinMax(m_Min * 0.5f + Target.position, m_Max * 0.5f + Target.position);
}
else
{
SetMinMax(m_Min * 0.5f + transform.position, m_Max * 0.5f + transform.position);
}
}
public Vector3 GetCenter()
{
m_Center.x = 0.5f * (m_RealCalcMin.x + m_RealCalcMax.x);
m_Center.y = 0.5f * (m_RealCalcMin.y + m_RealCalcMax.y);
m_Center.z = 0.5f * (m_RealCalcMin.z + m_RealCalcMax.z);
return m_Center;
}
public void GetCorners()
{
// 朝着Z轴正方向的面
// 左上顶点坐标
m_Corners[0].Set(m_RealCalcMin.x, m_RealCalcMax.y, m_RealCalcMax.z);
// 左下顶点坐标
m_Corners[1].Set(m_RealCalcMin.x, m_RealCalcMin.y, m_RealCalcMax.z);
// 右下顶点坐标
m_Corners[2].Set(m_RealCalcMax.x, m_RealCalcMin.y, m_RealCalcMax.z);
// 右上顶点坐标
m_Corners[3].Set(m_RealCalcMax.x, m_RealCalcMax.y, m_RealCalcMax.z);
// 朝着Z轴负方向的面
// 右上顶点坐标
m_Corners[4].Set(m_RealCalcMax.x, m_RealCalcMax.y, m_RealCalcMin.z);
// 右下顶点坐标.
m_Corners[5].Set(m_RealCalcMax.x, m_RealCalcMin.y, m_RealCalcMin.z);
// 左下顶点坐标.
m_Corners[6].Set(m_RealCalcMin.x, m_RealCalcMin.y, m_RealCalcMin.z);
// 左上顶点坐标.
m_Corners[7].Set(m_RealCalcMin.x, m_RealCalcMax.y, m_RealCalcMin.z);
}
public bool Intersects(IMathAABB aabb)
{
//就是各轴 互相是否包含,(aabb 包含 当前包围盒)|| (当前的包围盒 包含 aabb)
return ((m_RealCalcMin.x >= aabb.MinVector.x && m_RealCalcMin.x <= aabb.MaxVector.x) || (aabb.MinVector.x >= m_RealCalcMin.x && aabb.MinVector.x <= m_RealCalcMax.x)) &&
((m_RealCalcMin.y >= aabb.MinVector.y && m_RealCalcMin.y <= aabb.MaxVector.y) || (aabb.MinVector.y >= m_RealCalcMin.y && aabb.MinVector.y <= m_RealCalcMax.y)) &&
((m_RealCalcMin.z >= aabb.MinVector.z && m_RealCalcMin.z <= aabb.MaxVector.z) || (aabb.MinVector.z >= m_RealCalcMin.z && aabb.MinVector.z <= m_RealCalcMax.z));
}
public bool ContainPoint(Vector3 point)
{
if (point.x < m_RealCalcMin.x) return false;
if (point.y < m_RealCalcMin.y) return false;
if (point.z < m_RealCalcMin.z) return false;
if (point.x > m_RealCalcMax.x) return false;
if (point.y > m_RealCalcMax.y) return false;
if (point.z > m_RealCalcMax.z) return false;
return true;
}
public void Merge(IMathAABB box)
{
// 计算新的最小点坐标
m_RealCalcMin.x = Mathf.Min(m_RealCalcMin.x, box.MinVector.x);
m_RealCalcMin.y = Mathf.Min(m_RealCalcMin.y, box.MinVector.y);
m_RealCalcMin.z = Mathf.Min(m_RealCalcMin.z, box.MinVector.z);
// 计算新的最大点坐标
m_RealCalcMax.x = Mathf.Max(m_RealCalcMax.x, box.MaxVector.x);
m_RealCalcMax.y = Mathf.Max(m_RealCalcMax.y, box.MaxVector.y);
m_RealCalcMax.z = Mathf.Max(m_RealCalcMax.z, box.MaxVector.z);
GetCenter();
GetCorners();
}
public void SetMinMax(Vector3 min, Vector3 max)
{
this.m_RealCalcMin = min;
this.m_RealCalcMax = max;
GetCenter();
GetCorners();
}
public bool IsEmpty()
{
return m_RealCalcMin.x > m_RealCalcMax.x || m_RealCalcMin.y > m_RealCalcMax.y || m_RealCalcMin.z > m_RealCalcMax.z;
}
public void ResetMinMax()
{
m_RealCalcMin.Set(-1, -1, -1);
m_RealCalcMax.Set(1, 1, 1);
GetCenter();
GetCorners();
}
}
AABBDisable プロパティは、ビュー ウィンドウでこの値を変更できません。
線の描画: Unity.Debug.DrawLine を使用します。
#if UNITY_EDITOR
// draw lines
Debug.DrawLine(Corners[0], Corners[1], m_DebugLineColor);
Debug.DrawLine(Corners[1], Corners[2], m_DebugLineColor);
Debug.DrawLine(Corners[2], Corners[3], m_DebugLineColor);
Debug.DrawLine(Corners[3], Corners[0], m_DebugLineColor);
Debug.DrawLine(Corners[4], Corners[5], m_DebugLineColor);
Debug.DrawLine(Corners[5], Corners[6], m_DebugLineColor);
Debug.DrawLine(Corners[6], Corners[7], m_DebugLineColor);
Debug.DrawLine(Corners[7], Corners[4], m_DebugLineColor);
Debug.DrawLine(Corners[0], Corners[7], m_DebugLineColor);
Debug.DrawLine(Corners[1], Corners[6], m_DebugLineColor);
Debug.DrawLine(Corners[2], Corners[5], m_DebugLineColor);
Debug.DrawLine(Corners[3], Corners[4], m_DebugLineColor);
#endif
-
効果
もちろん、Unity にパッケージ化されている UnityEngine.Bounds を使用することもできます。
-
予防
Updateではフレームごとに衝突を検出する ため、あるフレーム内でオブジェクトの動きが速すぎてバウンディングボックスの距離を超えた場合には衝突が発生しません。
参照できます:離散 離散検出、連続 連続検出
もしお役にたてましたらフォローしてもよろしいでしょうか?