在学习Ray-Box检测之前,首先来学习一些这个分离轴理论!
先说二维情况,一句话
Two convex polygons do not intersect if and only if there exists a line such that the projections of the two polygons onto the line do not intersect.
咳咳,翻译一下
两个凸包多边形,当且仅当存在一条线,这两个多边形在这条线上的投影不相交,则这两个多边形也不相交, 如下图所示
将多边形换成多面体,线变成面,就变成了三维空间中的分离轴了。
对于矩形,假设出现碰撞的情况,则存在分离轴平行矩形的一条边。(这个后面会证明)
在到三维之前,还是来看二维的情况,也就是Ray-Rect检测。
假定Rect的中心为原点,所以就是下面这样
首先要面对的一个问题就是射线的原点是否在矩形的内部,这里就用到了分离轴的定理。
将矩形投影到对应的轴上,如果没有和射线原点的投影重合,那么就不在矩形里面。
接下来判断是否相交,这里提到了一个简单 slab method,简单说来,首先将矩形的四条边无限延伸,那么整个平面就被矩形分割成了几个部分,用这个”井字“去切割射线,如果得得到的线段在矩形内,那么就相交了。如下图所示,绿色的射线是相交的,红色的没有相交。
简单的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
bool
intersection(box b, ray r) {
double
tmin = -INFINITY, tmax = INFINITY;
if
(ray.n.x != 0.0) {
double
tx1 = (b.min.x - r.x0.x)/r.n.x;
double
tx2 = (b.max.x - r.x0.x)/r.n.x;
tmin = max(tmin, min(tx1, tx2));
tmax = min(tmax, max(tx1, tx2));
}
if
(ray.n.y != 0.0) {
double
ty1 = (b.min.y - r.x0.y)/r.n.y;
double
ty2 = (b.max.y - r.x0.y)/r.n.y;
tmin = max(tmin, min(ty1, ty2));
tmax = min(tmax, max(ty1, ty2));
}
return
tmax >= tmin;
}
|
直接贴代码了。
加了坐标系的转换,代码是参考PhysX里优化过的代码,但原理基本不变。
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
|
public
static
bool
Raycast(Ray ray,
float
distance, Box box,
out
RaycastHitInfo hitInfo)
{
Quaternion inverRot = Quaternion.Inverse(box.rotation);
Vector3 origin = ray.origin - box.center;
Vector3 localOrigin = inverRot * origin;
Vector3 localDir = inverRot * ray.direction;
Ray localRay =
new
Ray(localOrigin, localDir);
if
(!IntersectRayAABB(localRay, distance, 0.5f * box.extents,
out
hitInfo))
{
return
false
;
}
hitInfo.normal = box.rotation * hitInfo.normal;
hitInfo.point = box.rotation * hitInfo.point + box.center;
return
true
;
}
public
static
bool
IntersectRayAABB(Ray ray,
float
distance, Vector3 dimension,
out
RaycastHitInfo hitInfo)
{
float
RAYAABB_EPSILON = 0.00001f;
hitInfo =
new
RaycastHitInfo();
Vector3 minPos = -dimension;
Vector3 maxPos = dimension;
Vector3 maxT = -Vector3.one;
bool
isInside =
true
;
for
(
int
i = 0; i < 3; i++)
{
if
(ray.origin[i] < minPos[i])
{
hitInfo.point[i] = minPos[i];
isInside =
false
;
if
(ray.direction[i] != 0)
maxT[i] = (minPos[i] - ray.origin[i]) / ray.direction[i];
}
else
if
(ray.origin[i] > maxPos[i])
{
hitInfo.point[i] = maxPos[i];
isInside =
false
;
if
(ray.direction[i] != 0)
maxT[i] = (maxPos[i] - ray.origin[i]) / ray.direction[i];
}
}
// Ray origin inside bounding box
if
(isInside)
{
hitInfo.point = ray.origin;
hitInfo.distance = 0;
hitInfo.normal = -ray.direction;
return
true
;
}
// Get largest of the maxT's for final choice of intersection
int
whichPlane = 0;
if
(maxT[1] > maxT[whichPlane]) whichPlane = 1;
if
(maxT[2] > maxT[whichPlane]) whichPlane = 2;
//Ray distance large than ray cast ditance
if
(maxT[whichPlane] > distance)
{
return
false
; }
// Check final candidate actually inside box
for
(
int
i = 0; i < 3; i++)
{
if
(i != whichPlane)
{
hitInfo.point[i] = ray.origin[i] + maxT[whichPlane] * ray.direction[i];
if
(hitInfo.point[i] < minPos[i] - RAYAABB_EPSILON || hitInfo.point[i] > maxPos[i] + RAYAABB_EPSILON)
return
false
;
// if (hitInfo.point[i] < minPos[i] || hitInfo.point[i] > maxPos[i])
// return false;
}
}
hitInfo.distance = maxT[whichPlane];
Vector3 normal = Vector3.zero;
normal[whichPlane] = (hitInfo.point[whichPlane] > 0) ? 1 : -1;
hitInfo.normal = normal;
return
true
;
}
|
测试代码
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
|
public
class
RayBoxTester : MonoBehaviour {
public
GameObject box;
Box _box;
// Use this for initialization
void
Start () {
_box =
new
Box(Vector3.zero, Vector3.one, Quaternion.identity);
}
// Update is called once per frame
void
Update () {
//Ray OBB test.
Ray ray2 =
new
Ray(Vector3.zero,
new
Vector3(1, 1, 1));
RaycastHitInfo hitinfo2;
//ray2.origin = rayOrigin.transform.position;
float
castDistance = 10f;
_box.center = box.transform.position;
_box.extents = box.transform.localScale;
_box.rotation = box.transform.rotation;
if
(NRaycastTests.Raycast(ray2, castDistance, _box,
out
hitinfo2))
{
Debug.DrawLine(ray2.origin, ray2.origin + ray2.direction * hitinfo2.distance, Color.red, 0,
false
);
Debug.DrawLine(hitinfo2.point, hitinfo2.point + hitinfo2.normal, Color.green, 0,
false
);
}
else
{
Debug.DrawLine(ray2.origin, ray2.origin + ray2.direction * castDistance, Color.blue, 0,
false
);
}
}
}
|
结果
收工。
FAST, BRANCHLESS RAY/BOUNDING BOX INTERSECTIONS - https://tavianator.com/fast-branchless-raybounding-box-intersections/
Hyperplane_separation_theorem - https://en.wikipedia.org/wiki/Hyperplane_separation_theorem
Ray - Box Intersection - http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter3.htm
PhysX 3.3 source code
碰撞检测之Ray-Box检测