.在前文中,我们可以通过2个点来确定一个立方体。在此基础上添加3个轴向
public Vector3 XAxis { get { return transform.right; } }
public Vector3 YAxis { get { return transform.up; } }
public Vector3 ZAxis { get { return transform.forward; } }
已知一个点的坐标和物体的四元数,我们就可以得知旋转完的点
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);
// 根据旋转修改8个点的坐标
for (int i = 0; i < m_Corners.Length; i++)
{
Vector3 dis = m_Corners[i] - transform.position;
m_Corners[i] = transform.position + transform.localRotation * dis;
}
}
这样一个可以跟着旋转的8个顶点坐标就算出来了
OBB包围盒的碰撞检测方法
检测方法常采用的是分离轴定理。说白了就是去检测并判断两个图形之间是否有间隙。
找到一个轴,两个凸形状在该轴上的投影不重叠,则这两个形状不相交。如果这个轴不存在,并且那些形状是凸形的,则可以确定两个形状相交。
在算法上就是取两个OBB的坐标轴各3个以及垂直于每个轴的9个轴。
1.先将点投影到坐标抽上
/// <summary>
/// 将点投影到坐标轴
/// </summary>
/// <param name="point"></param>
/// <param name="axis"></param>
/// <returns>投影的坐标</returns>
private float ProjectPoint(Vector3 point, Vector3 axis)
{
Vector3 projectPoint = Vector3.Project(point, axis);
float result = projectPoint.magnitude * Mathf.Sign(Vector3.Dot(projectPoint, axis));
return result;
}
2.获取8个点在轴上投影的最大值和最小值
/// <summary>
/// 计算最大最小投影值
/// </summary>
/// <param name="corners"></param>
/// <param name="axis"></param>
/// <param name="min"></param>
/// <param name="max"></param>
private void GetInterval(Vector3[] corners, Vector3 axis, out float min, out float max)
{
float value;
//分别投影八个点,取最大和最小值
min = max = ProjectPoint(corners[0], axis);
for (int i = 1; i < corners.Length; i++)
{
value = ProjectPoint(corners[i], axis);
min = Mathf.Min(min, value);
max = Mathf.Max(max, value);
}
}
3.判断是不是交叉点
/// <summary>
/// 预测是不是交叉点
/// </summary>
/// <param name="aCorners"></param>
/// <param name="bCorners"></param>
/// <param name="axis"></param>
/// <returns></returns>
private bool AxisProjection(Vector3[] aCorners, Vector3[] bCorners, Vector3 axis)
{
GetInterval(aCorners, axis, out float xMin, out float xMax);
GetInterval(bCorners, axis, out float yMin, out float yMax);
if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;
if (xMin >= yMin && xMin <= yMax) return false;
if (xMax >= yMin && xMax <= yMax) return false;
return true;
}
4.根据2个包围盒的3个轴以及垂直于每个轴的9个轴判断是否有交集
public bool Intersects(IMathAABB aabb)
{
m_IsNotIntersect = false;
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, XAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, YAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, ZAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.XAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.YAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.ZAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.XAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.YAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.ZAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.XAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.YAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.ZAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.XAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.YAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.ZAxis).normalized);
return !m_IsNotIntersect;
}
完整代码
public class OBB : MonoBehaviour, IMathAABB
{
//修改此值控制m_CalcMin
[SerializeField, AABBModify("MinVector")]
private Vector3 m_Min = -Vector3.one;
//修改此值控制m_CalcMax
[SerializeField, AABBModify("MaxVector")]
private Vector3 m_Max = Vector3.one;
/// <summary>
/// 中心点
/// </summary>
[SerializeField, AABBDisable]
private Vector3 m_Center = Vector3.zero;
/// <summary>
/// 保存包围盒八个顶点
/// </summary>
[SerializeField, AABBDisable]
private Vector3[] m_Corners = new Vector3[8];
#if UNITY_EDITOR
[SerializeField]
private Color m_DebugLineColor = Color.green;
#endif
public Vector3 MinVector
{
set
{
SetMinMax(m_Min, m_Max);
}
get
{
return m_RealCalcMin;
}
}
public Vector3 MaxVector
{
set
{
SetMinMax(m_Min, m_Max);
}
get
{
return m_RealCalcMax;
}
}
public Vector3[] Corners
{
get
{
return m_Corners;
}
}
public Vector3 Center
{
get
{
return m_Center;
}
}
public Vector3 XAxis { get { return transform.right; } }
public Vector3 YAxis { get { return transform.up; } }
public Vector3 ZAxis { get { return transform.forward; } }
/// <summary>
/// 实际计算的最小值
/// </summary>
private Vector3 m_RealCalcMin;
/// <summary>
/// 实际计算的最大值
/// </summary>
private Vector3 m_RealCalcMax;
private bool m_IsNotIntersect = false;
/// <summary>
/// 防止在update之前产生碰撞
/// </summary>
private void Awake()
{
SetMinMax(m_Min, m_Max);
}
// Update is called once per frame
private void Update()
{
SetMinMax(m_Min, m_Max);
}
#if UNITY_EDITOR
void OnDrawGizmos()
{
//
// draw lines
Gizmos.color = m_DebugLineColor;
Gizmos.DrawLine(Corners[0], Corners[1]);
Gizmos.DrawLine(Corners[1], Corners[2]);
Gizmos.DrawLine(Corners[2], Corners[3]);
Gizmos.DrawLine(Corners[3], Corners[0]);
Gizmos.DrawLine(Corners[4], Corners[5]);
Gizmos.DrawLine(Corners[5], Corners[6]);
Gizmos.DrawLine(Corners[6], Corners[7]);
Gizmos.DrawLine(Corners[7], Corners[4]);
Gizmos.DrawLine(Corners[0], Corners[7]);
Gizmos.DrawLine(Corners[1], Corners[6]);
Gizmos.DrawLine(Corners[2], Corners[5]);
Gizmos.DrawLine(Corners[3], Corners[4]);
}
#endif
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);
// 根据旋转修改8个点的坐标
for (int i = 0; i < m_Corners.Length; i++)
{
Vector3 dis = m_Corners[i] - transform.position;
m_Corners[i] = transform.position + transform.localRotation * dis;
}
}
public bool Intersects(IMathAABB aabb)
{
m_IsNotIntersect = false;
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, XAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, YAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, ZAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.XAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.YAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.ZAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.XAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.YAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.ZAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.XAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.YAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.ZAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.XAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.YAxis).normalized);
m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.ZAxis).normalized);
return !m_IsNotIntersect;
}
public bool ContainPoint(Vector3 point)
{
m_IsNotIntersect = false;
m_IsNotIntersect |= AxisProjection(this.Corners, point, XAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, point, YAxis);
m_IsNotIntersect |= AxisProjection(this.Corners, point, ZAxis);
return !m_IsNotIntersect;
}
public void Merge(IMathAABB box)
{
throw new System.NotImplementedException("Please ingore 'Merge(IMathAABB box)' function !");
}
public void SetMinMax(Vector3 min, Vector3 max)
{
this.m_RealCalcMin = min * 0.5f + transform.position;
this.m_RealCalcMax = max * 0.5f + transform.position;
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();
}
/// <summary>
/// 预测是不是交叉点
/// </summary>
/// <param name="aCorners"></param>
/// <param name="bCorners"></param>
/// <param name="axis"></param>
/// <returns></returns>
private bool AxisProjection(Vector3[] aCorners, Vector3[] bCorners, Vector3 axis)
{
GetInterval(aCorners, axis, out float xMin, out float xMax);
GetInterval(bCorners, axis, out float yMin, out float yMax);
if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;
if (xMin >= yMin && xMin <= yMax) return false;
if (xMax >= yMin && xMax <= yMax) return false;
return true;
}
/// <summary>
/// 预测是不是交叉点
/// </summary>
/// <param name="aCorners"></param>
/// <param name="point"></param>
/// <param name="axis"></param>
/// <returns></returns>
private bool AxisProjection(Vector3[] aCorners, Vector3 point, Vector3 axis)
{
GetInterval(aCorners, axis, out float xMin, out float xMax);
float yMin = ProjectPoint(point, axis);
float yMax = ProjectPoint(point, axis);
if (yMin >= xMin && yMin <= xMax) return false;
if (yMax >= xMin && yMax <= xMax) return false;
if (xMin >= yMin && xMin <= yMax) return false;
if (xMax >= yMin && xMax <= yMax) return false;
return true;
}
/// <summary>
/// 计算最大最小投影值
/// </summary>
/// <param name="corners"></param>
/// <param name="axis"></param>
/// <param name="min"></param>
/// <param name="max"></param>
private void GetInterval(Vector3[] corners, Vector3 axis, out float min, out float max)
{
float value;
//分别投影八个点,取最大和最小值
min = max = ProjectPoint(corners[0], axis);
for (int i = 1; i < corners.Length; i++)
{
value = ProjectPoint(corners[i], axis);
min = Mathf.Min(min, value);
max = Mathf.Max(max, value);
}
}
/// <summary>
/// 将点投影到坐标轴
/// </summary>
/// <param name="point"></param>
/// <param name="axis"></param>
/// <returns>投影的坐标</returns>
private float ProjectPoint(Vector3 point, Vector3 axis)
{
Vector3 projectPoint = Vector3.Project(point, axis);
float result = projectPoint.magnitude * Mathf.Sign(Vector3.Dot(projectPoint, axis));
return result;
}
}