接上一篇 OBB2D相交检测
在2D 中是只要有一个轴、线将两个 OBB 分离到两侧就判定两个 OBB不相交
在3D 中基本类似2D,但是在3D 中要将分离线替换为分离面,就是只要是找到一个面,能将两个 OBB分离在面的两侧,就说明两个OBB不相交。看下图
面P平行于OBB的一个面 ABCD,面P可以将两个 OBB分离到两侧
像面P这样的面理论上可以找到几个?
首先平行于每个OBB六个面的,因为一个OBB的六个面是俩俩平行的,所以一个OBB可以贡献 3 个面,两个OBB可以贡献 6个面。看下图,画的不像,可以自己脑补想象一下
除了平行于面的面还有下面这种,下面这个将两个OBB分离的面的法向量是 两个红色边叉乘得到的。
两个OBB分别命名为 A、B
则 A 有三个轴 a_axisX,a_axisY, a_axisZ
则 B 有三个轴 b_axisX,b_axisY, b_axisZ
总共有 9 中组合分别为
cross1 = a_axisX 叉乘 b_axisX
cross2 = a_axisX 叉乘 b_axisY
cross3 = a_axisX 叉乘 b_axisZ
cross4 = a_axisY 叉乘 b_axisX
cross5 = a_axisY 叉乘 b_axisY
cross6 = a_axisY 叉乘 b_axisZ
cross7 = a_axisZ 叉乘 b_axisX
cross8 = a_axisZ 叉乘 b_axisY
cross9 = a_axisZ 叉乘 b_axisZ
所以在3D轴分离轴总共有 6 + 9 = 15 中。
点到分离轴的投影方式同 2D 计算相同,只是点坐标和向量的 Vector2 调整为 Vector3
2D 中一个OBB 有两个轴,四个顶点
3D 中一个OBB 有三个轴,八个顶点
代码逻辑如下
public class OBB3D
{
/// <summary>
/// X 轴方向向量
/// </summary>
public Vector3 _axisX;
/// <summary>
/// Y 轴方向向量
/// </summary>
public Vector3 _axisY;
/// <summary>
/// Z 轴方向向量
/// </summary>
public Vector3 _axisZ;
/// <summary>
/// 中心点坐标
/// </summary>
public Vector3 _center;
/// <summary>
/// 三条边长度
/// </summary>
public Vector3 _size;
/// <summary>
/// 八个顶点坐标
/// </summary>
public Vector3[] _vertexs;
public OBB3D(){ }
public void Set(Vector3 axisX, Vector3 axisY, Vector3 axisZ, Vector3 center, Vector3 size)
{
_axisX = axisX;
_axisY = axisY;
_axisZ = axisZ;
_center = center;
_size = size;
_vertexs = new Vector3[8];
_vertexs[0] = center + (axisX * size.x + axisY * size.y + axisZ * size.z) * 0.5f;
_vertexs[1] = center + (axisX * size.x - axisY * size.y + axisZ * size.z) * 0.5f;
_vertexs[2] = center + (axisX * size.x + axisY * size.y - axisZ * size.z) * 0.5f;
_vertexs[3] = center + (axisX * size.x - axisY * size.y - axisZ * size.z) * 0.5f;
_vertexs[4] = center + (-axisX * size.x + axisY * size.y - axisZ * size.z) * 0.5f;
_vertexs[5] = center + (-axisX * size.x - axisY * size.y - axisZ * size.z) * 0.5f;
_vertexs[6] = center + (-axisX * size.x - axisY * size.y + axisZ * size.z) * 0.5f;
_vertexs[7] = center + (-axisX * size.x + axisY * size.y + axisZ * size.z) * 0.5f;
}
}
public class OBB3DIntersect
{
public bool IsIntersect3D(OBB3D a, OBB3D b)
{
Vector3 cross1 = Vector3.Cross(a._axisX, b._axisX);
Vector3 cross2 = Vector3.Cross(a._axisX, b._axisY);
Vector3 cross3 = Vector3.Cross(a._axisX, b._axisZ);
Vector3 cross4 = Vector3.Cross(a._axisY, b._axisX);
Vector3 cross5 = Vector3.Cross(a._axisY, b._axisY);
Vector3 cross6 = Vector3.Cross(a._axisY, b._axisZ);
Vector3 cross7 = Vector3.Cross(a._axisZ, b._axisX);
Vector3 cross8 = Vector3.Cross(a._axisZ, b._axisY);
Vector3 cross9 = Vector3.Cross(a._axisZ, b._axisZ);
// 如果有一个分离轴上不相交,则OBB1 和 OBB2 不相交
return !(IsNotIntersectInAxis(a._vertexs, b._vertexs, a._axisX)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, a._axisY)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, a._axisZ)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, b._axisX)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, b._axisY)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, b._axisZ)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross1)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross2)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross3)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross4)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross5)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross6)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross7)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross8)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, cross9));
}
/// <summary>
/// OBB 在 axis 轴上没有相交
/// </summary>
/// <param name="vertexs1">OBB1 的所有顶点</param>
/// <param name="vertexs2">OBB2 的所有顶点</param>
/// <param name="axis">分离轴</param>
/// <returns></returns>
private bool IsNotIntersectInAxis(Vector3[] vertexs1, Vector3[] vertexs2, Vector3 axis)
{
float[] range1 = VertexProject(vertexs1, axis);
float[] range2 = VertexProject(vertexs2, axis);
return range1[0] > range2[1] || range2[0] > range1[1];
}
/// <summary>
/// 顶点在轴上的投影的最小值和最大值
/// </summary>
/// <param name="vertexs">顶点</param>
/// <param name="axis">分离轴</param>
/// <returns></returns>
private float[] VertexProject(Vector3[] vertexs, Vector3 axis)
{
float[] range = new float[] { float.MaxValue, float.MinValue };
for (int i = 0; i < vertexs.Length; ++i)
{
Vector3 vertex = vertexs[i];
float dot = Vector3.Dot(vertex, axis);
range[0] = Math.Min(range[0], dot);
range[1] = Math.Max(range[1], dot);
}
return range;
}
}
测试代码如下
public class OBB3DCollisionController : MonoBehaviour
{
// Cube 立方体 A
public Transform A;
// Cube 立方体 B
public Transform B;
// 定义两个 OBB3D 立方体
private OBB3D a;
private OBB3D b;
// 相交检测逻辑
private OBB3DIntersect obb3DIntersect;
void Start()
{
// 实例化
a = new OBB3D();
b = new OBB3D();
obb3DIntersect = new OBB3DIntersect();
}
private bool result = false;
void Update()
{
// 将两个Cube 的数据分别赋值给 OBB3D
SetOBB(A, a);
SetOBB(B, b);
// 判断两立方体是否相交
bool intersect = obb3DIntersect.IsIntersect3D(a, b);
if (result != intersect)
{
result = intersect;
// 如果两个立方体相交,则设置为红色
A.GetComponent<Renderer>().material.color = result ? Color.red : Color.white;
B.GetComponent<Renderer>().material.color = result ? Color.red : Color.white;
}
}
private void SetOBB(Transform tr, OBB3D obb)
{
// OBB3D 的三个轴分别使用 Cube.transform:right、up、forward
// OBB3D 的坐标使用 Cube.transform.position
// OBB3D 的size 使用 Cube.transform.localScale
obb.Set(tr.right, tr.up, tr.forward, tr.position, tr.localScale);
}
}
测试如下