碰撞检测问题在虚拟现实、计算机辅助设计与制造、游戏、机器人等方面都有着广泛的应用,而包围盒算法是进行碰撞检测的重要方法之一。
而常见的包围盒有:
在Unity中的Collider包含:
在游戏中,为了简化物体之间的碰撞检测运算,通常会对物体创建一个规则的几何外形将其包围。故AABB包围盒被称为轴对齐包围盒。
AABB包围盒构造比较简单,存储空间小,但紧密性差,尤其对不规则几何形体,冗余空间很大,当对象旋转时,无法对其进行相应的旋转(使用OBB包围盒)。
从算法角度来看,只需要2个点pointMin和pointMax即可描述AABB包围盒。
AABB包围盒与OBB包围盒的最直接的区别就是:
1.AABB包围盒是不可以旋转的
2.OBB包围盒是可以旋转的,也就是有向的。
二维场景中的面片碰撞如下图所示:
我们将蓝黄两个面片各自的4个角投影到XY轴上
蓝色面片的Y轴方向最大点坐标为Y1,最小点坐标Y2,X轴方向最小点坐标X1,最大点坐标X2
黄色面片的Y轴方向最大点坐标为Y3,最小点坐标Y4,X轴方向最小点坐标X3,最大点坐标X4
图中红色区域为各轴上的的重叠部分。
可以看出,AABB碰撞检测具有如下规则:
蓝色面片与黄色面片分别沿两个坐标轴的投影,只有在两个坐标轴都发生重叠的情况下,两个物体才意味着发生了碰撞。
三维场景中物体的AABB包围盒是一个六面体,对于二维坐标系来讲只是多了一个Z轴
所以实际上在三维场景中物体的AABB碰撞检测依然可以采用四个点信息的判定来实现。
即:从A物体的八个顶点与B物体的八个顶点分别选出两个最大与最小的顶点进行对比。
如上图所示:只要确定了图中黑色点部分的坐标,就可以确定八个顶点的全部信息了。
代码中定义接口:
public interface IMathAABB
{
Vector3 MinVector { get; }
Vector3 MaxVector { get; }
Vector3 Center { get; }
Vector3[] Corners { get; }
///
/// Gets the center point of the bounding box.
///
/// 获取中心点
Vector3 GetCenter();
///
/// 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
///
/// 获取包围盒八个顶点信息
void GetCorners();
///
/// Tests whether this bounding box intersects the specified bounding object.
///
/// 判断两个包围盒是否碰撞
bool Intersects(IMathAABB aabb);
///
/// check whether the point is in.
///
/// 返回这个点是否在包围盒中
bool ContainPoint(Vector3 point);
///
/// Sets this bounding box to the smallest bounding box
/// that contains both this bounding object and the specified bounding box.
///
/// 生成一个新的包围盒 同时容纳两个包围盒,新的包围盒: min各轴要是其他两个最小的那个,max各轴要是其他两个最大的那个
void Merge(IMathAABB box);
///
/// Sets this bounding box to the specified values.
///
///
///
/// 设置
void SetMinMax(Vector3 min, Vector3 max);
///
/// reset min and max value.
///
/// 重置
void ResetMinMax();
bool IsEmpty();
}
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;
}
}
///
/// 实际计算的最小值
///
private Vector3 m_RealCalcMin;
///
/// 实际计算的最大值
///
private Vector3 m_RealCalcMax;
///
/// 防止在update之前产生碰撞
///
private void Awake()
{
UpdatePosition();
}
// Update is called once per frame
private void Update()
{
UpdatePosition();
}
///
/// 更新位置
///
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中每一帧去检测碰撞,所以当物体在某一帧移速过快,超过包围盒的距离,就会导致碰撞不产生。
可以参考:Discrete 离散检测、Continuous 连续检测
如果对你有帮助的话,能否关注一波