抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

简介

OBB 检测,即有向包围盒(Oriented Bounding Box)检测,是一种检测两个物体有没有相交的算法。物体根据包围盒中心、包围盒大小以及包围盒旋转轴构造有向包围盒,该包围盒比 AABB 包围盒更贴合旋转物体。OBB 检测利用 SAT(Separating AxisTheorem)分离轴算法进行相交检测,比 AABB 检测具有更好的检测精度。

原理

对于两个凸多边形来说,若存在一条直线把两个凸多边形分开,那么垂直于这条直线的轴则称为分离轴。若两个物体在分离轴上的投影不相交,则两个物体不相交,并且一旦在某个轴上存在不相交的情况,则两个物体一定不相交,退出检测。

在实际运用中,我们常以凸多边形的边的垂线作为分离轴,依次检测物体在分离轴上的投影。

二维

在 2D 图形中,分离轴的数量为两个凸多边形物体的边数之和,如上图所示,4 条黑色的轴为分离轴。在分离轴 1 上,我们观察到物体 A 的投影和物体 B 的投影相交了,因此在分离轴 1 上,两个物体相交;在分离轴 2 上,我们观察到两个物体的投影没有相交,因此两个物体在分离轴 2 上没有相交。

由此,我们可以得出 A、B 两个物体没有相交。通过观察图像我们也知道这个结果没错。

三维

在 3D 中,我们以一个有向的长方体来包围物体,即 OBB。

由于长方体的坐标轴两两垂直,因此坐标轴即是分离轴。由于两个长方体之间还存在特殊的摆放。

在这种情况下,两个包围盒的坐标轴分离轴投影均相交,但是这两个物体并没有相交,两个物体之间还是存在一个面能够把两个物体分开,这个面就是向量 A 和向量 B 构成的平面。

利用向量叉乘的性质,我们可以求出一个垂直于这个面的向量,我们以这个向量为新的分离轴。两个物体每两条边构成的面都需要求垂直向量,因此两个 OBB 包围盒之间有 3 * 3 = 9 条新的分离轴。加上原来两个向量的 3 + 3 = 6 条分离轴,在 3D 中一共要检测15条分离轴。

对于其中一条分离轴,我们遍历顶点作为向量,投影到分离轴上,筛选出最大和最小值。对两个物体进行同样的操作后,我们比较两个物体的投影最值有无相交。每条分离轴以此方法操作即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//包围盒数据结构
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollisionData : MonoBehaviour
{
public Vector3[] vertexts = new Vector3[8];
public Vector3[] axes = new Vector3[3];
public Vector3 center = Vector3.zero;
}
---------------------------------------------
/// <summary>
/// SAT分离轴碰撞检测之OBB检测
/// <param name="data1"></param>
/// <param name="data2"></param>
/// </summary>
private bool CollisionOBB(CollisionData data1,CollisionData data2)
{
//求与两个OBB包围盒之间两两坐标轴垂直的法线轴 共9个
int len1 = data1.axes.Length;
int len2 = data2.axes.Length;
Vector3[] axes = new Vector3[len1 + len2 + len1 * len2];
int k = 0;
int initJ = len2;
for (int i = 0; i < len1; i++)
{
axes[k++] = data1.axes[i];
for (int j = 0; j < len2; j++)
{
if (initJ > 0)
{
initJ--;
axes[k++] = data2.axes[j];
}
axes[k++] = Vector3.Cross(data1.axes[i], data2.axes[j]);
}
}


for (int i = 0, len = axes.Length; i < len; i++)
{
if (NotInteractiveOBB(data1.vertexts, data2.vertexts, axes[i]))
{
//有一个不相交就退出
return false;
}
}
return true;
}

/// <summary>
/// 计算投影是否不相交
/// </summary>
/// <param name="vertexs1"></param>
/// <param name="vertexs2"></param>
/// <param name="axis"></param>
/// <returns></returns>
private bool NotInteractiveOBB(Vector3[] vertexs1, Vector3[] vertexs2, Vector3 axis)
{
//计算OBB包围盒在分离轴上的投影极限值
float[] limit1 = GetProjectionLimit(vertexs1, axis);
float[] limit2 = GetProjectionLimit(vertexs2, axis);
//两个包围盒极限值不相交,则不碰撞
return limit1[0] > limit2[1] || limit2[0] > limit1[1];
}

/// <summary>
/// 计算顶点投影极限值
/// </summary>
/// <param name="vertexts"></param>
/// <param name="axis"></param>
/// <returns></returns>
private float[] GetProjectionLimit(Vector3[] vertexts, Vector3 axis)
{
float[] result = new float[2] { float.MaxValue, float.MinValue };
for (int i = 0, len = vertexts.Length; i < len; i++)
{
Vector3 vertext = vertexts[i];
float dot = Vector3.Dot(vertext, axis);
result[0] = Mathf.Min(dot, result[0]);
result[1] = Mathf.Max(dot, result[1]);
}
return result;
}

其他

OBB 检测只能是凸多边形,不适用于凹多边形的情况。凹多边形可以分割为凸多边形进行检测。

碰撞检测示例工程

评论