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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
| bool penDepth(const DT_GJK& gjk, const DT_Convex& a, const DT_Convex& b,
MT_Vector3& v, MT_Point3& pa, MT_Point3& pb)
{
// 得到Simplex中的点。y是Simplex的点,p和q则是对应的原始点
int num_verts = gjk.getSimplex(pBuf, qBuf, yBuf);
MT_Scalar tolerance = DT_Accuracy::tol_error * gjk.maxVertex();
num_triangles = 0;
g_triangleStore.clear();
switch (num_verts)
{
case 1:
// Simplex中只有一个点。两个物体是相贴,没有穿透深度
return false;
case 2:
{
// Simplex中只有一个线段。那么尝试增加新点将其构造成四面体
MT_Vector3 dir = yBuf[1] - yBuf[0];
dir /= length(dir);
// furthestAxis:找到距离此向量最远的轴
// 其实就是找到分量最小的那个轴(分量越小,dir与此轴越垂直)
// 0 - x axis, 1 - y axis, 2 - z axis
int axis = dir.furthestAxis();
static const MT_Scalar sin_60 = sqrt(MT_Scalar(3.0)) * MT_Scalar(0.5);
// 注意:四元数的sin是半角,这里实际是绕dir旋转120度
MT_Quaternion rot(dir[0] * sin_60, dir[1] * sin_60, dir[2] * sin_60, MT_Scalar(0.5));
MT_Matrix3x3 rot_mat(rot);
// 首先得到距离此向量最远的轴,然后和dir叉乘
// 之所以要算最远的轴,是因为叉乘的结果最大,最不容易产生数值精度问题
MT_Vector3 aux1 = cross(dir, MT_Vector3(axis == 0, axis == 1, axis == 2));
// aux1绕dir旋转120度
MT_Vector3 aux2 = rot_mat * aux1;
// aux1绕dir旋转240度
MT_Vector3 aux3 = rot_mat * aux2;
// 上面三个aux其实是找到Simplex中线段垂直的平面上的三个均匀的方向
// 下面用这三个方向尝试找支撑点并鼓起Simplex为四面体
pBuf[2] = a.support(aux1);
qBuf[2] = b.support(-aux1);
yBuf[2] = pBuf[2] - qBuf[2];
pBuf[3] = a.support(aux2);
qBuf[3] = b.support(-aux2);
yBuf[3] = pBuf[3] - qBuf[3];
pBuf[4] = a.support(aux3);
qBuf[4] = b.support(-aux3);
yBuf[4] = pBuf[4] - qBuf[4];
// 检查原点是否在Simplex内(通过检查原点和各个面对面顶点是否在面同侧得到)
// 分别检查新三点与原有线段两端点构成的四面体
if (originInTetrahedron(yBuf[0], yBuf[2], yBuf[3], yBuf[4]) == 0)
{
pBuf[1] = pBuf[4];
qBuf[1] = qBuf[4];
yBuf[1] = yBuf[4];
}
else if (originInTetrahedron(yBuf[1], yBuf[2], yBuf[3], yBuf[4]) == 0)
{
pBuf[0] = pBuf[4];
qBuf[0] = qBuf[4];
yBuf[0] = yBuf[4];
}
else
{
// 原点不在Simplex内,失败
return false;
}
num_verts = 4;
}
// Fall through allowed!!
case 4:
{
// Simplex是四面体
// 先看原点是否在四面体内
// 此函数返回值:
// 0 :原点在四面体内
// 1~4:坏的那个顶点,即和原点不在同侧的那个点。比如1就是yBuf[0]是坏的
int bad_vertex = originInTetrahedron(yBuf[0], yBuf[1], yBuf[2], yBuf[3]);
// 原点在四面体内
if (bad_vertex == 0)
{
// 构造四面体的四个面
// 当三角面退化成直线/点就会构造失败(通过Gram行列式发现线性相关时)
// 构造的同时会计算面到原点的距离,以及面上距离原点最近的点(用重心坐标表示)
Triangle *f0 = g_triangleStore.newTriangle(yBuf, 0, 1, 2);
Triangle *f1 = g_triangleStore.newTriangle(yBuf, 0, 3, 1);
Triangle *f2 = g_triangleStore.newTriangle(yBuf, 0, 2, 3);
Triangle *f3 = g_triangleStore.newTriangle(yBuf, 1, 3, 2);
// 这里有两种判断:
// 1. 面f不能是空
// 2. 原点是否刚好落在面上(f->getDist2() == MT_Scalar(0.0),距离不会小于0)
// 如果落在面上,意味着下一步的支撑点搜索方向是0向量,从而导致EPA死循环
if (!(f0 && f0->getDist2() > MT_Scalar(0.0) &&
f1 && f1->getDist2() > MT_Scalar(0.0) &&
f2 && f2->getDist2() > MT_Scalar(0.0) &&
f3 && f3->getDist2() > MT_Scalar(0.0)))
{
return false;
}
// 构建四面体的拓扑结构:通过边将相离面连接起来
// 这里Edge的参数是面和边的其实点序号。比如Edge(f0, 0)就是取f0面的0, 1两个点构成的边
// 而Edge(f2, 2)就是取f2面的2, 0两个点构成的边
link(Edge(f0, 0), Edge(f1, 2));
link(Edge(f0, 1), Edge(f3, 2));
link(Edge(f0, 2), Edge(f2, 0));
link(Edge(f1, 0), Edge(f2, 2));
link(Edge(f1, 1), Edge(f3, 0));
link(Edge(f2, 1), Edge(f3, 1));
// 将面塞入优先队列(实现中是堆)中,以便于后面拿出距离原点最近的面
// 第二个参数是上界。当f距离原点的距离大于上界,不塞入队列
// 此外,面f到原点的最近点还必须严格在面上
addCandidate(f0, MT_INFINITY);
addCandidate(f1, MT_INFINITY);
addCandidate(f2, MT_INFINITY);
addCandidate(f3, MT_INFINITY);
break;
}
// 删除坏的点(这里是将第四个点替换坏的点),现在Simplex只含三个点
if (bad_vertex < 4)
{
pBuf[bad_vertex - 1] = pBuf[4];
qBuf[bad_vertex - 1] = qBuf[4];
yBuf[bad_vertex - 1] = yBuf[4];
}
num_verts = 3;
}
// Fall through allowed!!
case 3:
{
// 当Simplex只有三个点(只有一个三角面)时:
// 使用面的正负法线找到支撑点,以构成一个含有五个点的双四面体
// 这是一个聪明的做法,原始GJK结束时,原点在数值上应该是得在三角面内。这时如果只是构建一个四面体,
// 那么面到原点最近点向量就是0,这样就无法找到新的支撑点,算法会死循环。
// 那我不如直接构建一个六面体,原点肯定在此六面体内。
MT_Vector3 v1 = yBuf[1] - yBuf[0];
MT_Vector3 v2 = yBuf[2] - yBuf[0];
MT_Vector3 vv = cross(v1, v2);
pBuf[3] = a.support(vv);
qBuf[3] = b.support(-vv);
yBuf[3] = pBuf[3] - qBuf[3];
pBuf[4] = a.support(-vv);
qBuf[4] = b.support(vv);
yBuf[4] = pBuf[4] - qBuf[4];
Triangle* f0 = g_triangleStore.newTriangle(yBuf, 0, 1, 3);
Triangle* f1 = g_triangleStore.newTriangle(yBuf, 1, 2, 3);
Triangle* f2 = g_triangleStore.newTriangle(yBuf, 2, 0, 3);
Triangle* f3 = g_triangleStore.newTriangle(yBuf, 0, 2, 4);
Triangle* f4 = g_triangleStore.newTriangle(yBuf, 2, 1, 4);
Triangle* f5 = g_triangleStore.newTriangle(yBuf, 1, 0, 4);
if (!(f0 && f0->getDist2() > MT_Scalar(0.0) &&
f1 && f1->getDist2() > MT_Scalar(0.0) &&
f2 && f2->getDist2() > MT_Scalar(0.0) &&
f3 && f3->getDist2() > MT_Scalar(0.0) &&
f4 && f4->getDist2() > MT_Scalar(0.0) &&
f5 && f5->getDist2() > MT_Scalar(0.0)))
{
return false;
}
link(Edge(f0, 1), Edge(f1, 2));
link(Edge(f1, 1), Edge(f2, 2));
link(Edge(f2, 1), Edge(f0, 2));
link(Edge(f0, 0), Edge(f5, 0));
link(Edge(f1, 0), Edge(f4, 0));
link(Edge(f2, 0), Edge(f3, 0));
link(Edge(f3, 1), Edge(f4, 2));
link(Edge(f4, 1), Edge(f5, 2));
link(Edge(f5, 1), Edge(f3, 2));
addCandidate(f0, MT_INFINITY);
addCandidate(f1, MT_INFINITY);
addCandidate(f2, MT_INFINITY);
addCandidate(f3, MT_INFINITY);
addCandidate(f4, MT_INFINITY);
addCandidate(f5, MT_INFINITY);
num_verts = 5;
}
break;
}
// Simplex中没有三角面了。失败
if (num_triangles == 0)
{
return false;
}
// at least one triangle on the heap.
Triangle *triangle = 0;
MT_Scalar upper_bound2 = MT_INFINITY;
// 上面步骤构建了合法的多面体。现在开始真正执行EPA算法
do
{
// 从堆中弹出距离原点最近的面
triangle = triangleHeap[0];
std::pop_heap(&triangleHeap[0], &triangleHeap[num_triangles], triangleComp);
--num_triangles;
// 判断面还有效。TriangleStore里面有一个静态200个元素大小的数组,为了性能考虑,删除面并不是真的删除而是标记为obsolete
if (!triangle->isObsolete())
{
// 到达支持上限(200个点),失败
if (num_verts == MaxSupportPoints)
{
#ifdef DEBUG
std::cout << "Ouch, no convergence!!!" << std::endl;
#endif
assert(false);
break;
}
// 得到面上最近点,以此点为方向找到新支撑点
pBuf[num_verts] = a.support( triangle->getClosest());
qBuf[num_verts] = b.support(-triangle->getClosest());
yBuf[num_verts] = pBuf[num_verts] - qBuf[num_verts];
int index = num_verts++;
// 得到穿透深度的上界
MT_Scalar far_dist = dot(yBuf[index], triangle->getClosest());
assert(far_dist > MT_Scalar(0.0));
MT_Scalar far_dist2 = far_dist * far_dist / triangle->getDist2();
// 更新upper_bound2为上界中较小的那个
GEN_set_min(upper_bound2, far_dist2);
// 算法终止条件判断:
// 1.上下界之差为 e|v|。
// 2.
// 这里为了防止摄入误差,还给了一个保底容差tolerance
MT_Scalar error = far_dist - triangle->getDist2();
if (error <= GEN_max(DT_Accuracy::rel_error2 * far_dist, tolerance)
|| yBuf[index] == yBuf[(*triangle)[0]]
|| yBuf[index] == yBuf[(*triangle)[1]]
|| yBuf[index] == yBuf[(*triangle)[2]]
)
{
break;
}
// 获得空闲的面下标
int i = g_triangleStore.getFree();
// 使用新点缝合面
// yBuf:当前顶点集合
// index:新点在yBuf中的下标
if (!triangle->silhouette(yBuf, index, g_triangleStore))
{
break;
}
// 遍历所有新面,将面塞入堆中
while (i != g_triangleStore.getFree())
{
Triangle *newTriangle = &g_triangleStore[i];
addCandidate(newTriangle, upper_bound2);
++i;
}
}
}
while (num_triangles > 0 && triangleHeap[0]->getDist2() <= upper_bound2);
#ifdef DEBUG
std::cout << "#triangles left = " << num_triangles << std::endl;
#endif
// 返回对应值
v = triangle->getClosest();
pa = triangle->getClosestPoint(pBuf);
pb = triangle->getClosestPoint(qBuf);
return true;
}
|