• 判断两个形状是否相交二-GJK

    • 原文地址
    • 一 介绍
    • 二凸多边形性质
    • 三Minkowski和
    • 四单纯形
    • 五支持函数
    • 六创建单纯形
    • 七相交检测
    • 八迭代
      • 然后我们开始循环迭代
      • 第二次迭代
      • 第三次迭代
    • 九检测单纯形
    • 结束语

判断两个形状是否相交(二)-GJK

原文地址

一、 介绍:

GJK和SAT一样,只适用于凸多边形。GJK更为强大的地方在于,它可以通过“支持函数”(稍后讨论)支持所有的形状。因此,和SAT不同,你不用对曲边型使用特殊的代码或者方法进行特殊处理,就可以使用GJK。
GJK是一个迭代式的方法但是收敛的非常快,如果使用最新的渗透(分离)向量,它几乎是在一个很短的时间内得出结果。它是SAT在3D环境下的一个很好的替代,就因为SAT要测试的轴de数量非常多。
GJK的最初始的目的是为了计算两个凸边形的距离.GJK同样可以被用作在渗透很短的情况下的碰撞信息,还可以在其他的计算很大的渗透的方法中使用.

二、凸多边形性质:

就像之前说过的一样,GJK是一个只能被用在凸多边形上的方法。我看上一个关于SAT的博文了解更多关于凸多边形的信息。

三、Minkowski和:

GJK算法依靠一个很重要的理论叫做,闵科夫斯基和。闵科夫斯基和在原理上很容易理解:如果你有俩个形状,闵科夫斯基和就是形状aa的所有的点加上形状bb的所有的点,

A + B = {a + b|a∈A, b∈B}

如果两个形状都是凸多边形那么结果也是凸多边形。你可能会想“这又有什么意义呢?”。其实真正的意义所在是不再于“和”而在于“差”,(译者注,这里有些绕,姑且关注“闵科夫斯基差”即可)。

A – B = {a – b|a∈A, b∈B}
(译者注,表示形状A中的所有点和形状B中的所有点相减,如果A中有nn个点,B中有nn个点,那么最终将会产生n2n^2个点。)

在我们继续之前申明一下,尽管我们使用的是一个“差”操作,这同样是闵科夫斯基和的一部分,在以后的部分里我把它叫做“闵科夫斯基差”只是为了方便,它的名字同样还是“闵科夫斯基和”。

如果两个形状重叠或者相交那么这两个形状的闵科夫斯基差产生的点的外围形状会包含原点

如果有下图这样两个相交的图形

做闵科夫斯基差,可以得到12个点。这12个点的包含多边形会包含原点。如下图示:

如上面讨论过一样,做这样一个运算将要进行n2n^2次减法。如果两个多边形的顶点数目比较多这样会使巨大的运算量。不过幸运的是在使用GJK的时候可以不对所有的点都进行闵科夫斯基差操作,就可以得出想要的结果。

四、单纯形:

我们并不想真的计算出来闵科夫斯基差。取而代之的是我们只需要知道闵科夫斯基差是不是包含原点。如果它包含远点那么我们就可以得出结论,这两个多边形相交,反之亦然。
我们可以做的就是迭代地在闵科夫斯基差里面计算出来一个多边形去试图包含原点。如果我们建造的多边形(当然要在闵科夫斯基差内部)包含原点那么我们就可以说闵科夫斯基差包含原点,我们给我们要创建的这个多边形起名叫做“单纯形”(英文原文Simplex)。
单纯性就是一个三角形,只不过它的订单都是通过闵科夫斯基差计算出来的。

五、支持函数:

那么下一个问题就是我们如何创建这样一个单纯形。单纯形使用一个叫做支持函数的方法去创建。支持函数在给定两个形状之后应该返回一个在闵科夫斯基差内部的点。我们已经知道了我们可以从形状a中得到一个点然后从形状b中得到一个点,将这两个点相减就可以得到一个在闵科夫斯基差边缘上的点。但是我们不想总是得到同一个点。
如果我们让这个支持函数是以一个向量为基础的那么我们就可以保证在使用不同的向量是就可以得到不同的点。换句话说,如果我们让支持函数返回的是在一个方向上的最近的点,我们就可以选择不同的方向得到不同的点。(译者注,这儿看不懂没关系其实很简单,一会看到后面有很详细的例子看看就会明白了,这里只需明白,这个支持函数是输入一个向量返回一个点就行了)。
选择一个方向上的最近的两个点有着非常重要的意义,因为它能够创建一个包含最大面积的单纯形,这样就能够快速地增加让这个函数退出得到结果的机会了。而且,我们可以还可以利这种方式返回的点都在闵科夫斯基边缘的这样一个事实,因此如果我们不能够添加一个沿着一个特定向量的朝向原点方向的一个点,我们就可以得出结论,这个闵科夫斯基差不包含原点。这增加了算法快速得出结论的机会。下面会介绍更多信息。
支持函数如下

public Point support(Shape shape1, Shape shape2, Vector d) {// d is a vector direction (doesn't have to be normalized)// get points on the edge of the shapes in opposite directionsPoint p1 = shape1.getFarthestPointInDirection(d);Point p2 = shape2.getFarthestPointInDirection(d.negative());// perform the Minkowski DifferencePoint p3 = p1.subtract(p2);// p3 is now a point in Minkowski space on the edge of the Minkowski Differencereturn p3;
}

六、创建单纯形:

让我们通过一个实例开始。我们使用在图示2中的图形,运行三次支持函数。
第一次,我们选择d=(1,0)

p1(9,9);
p2(5,7);
p3=p1-p2=(4,2);

第二次 d=(-1,0)

p1=(4,5);
p2=(12,7);
p3=p1-p2=(-8,-2);

注意 p1 可以是(4,5)或者(4,11)。这两个都会产生一个在闵科夫斯基边缘上的点。
最后一次 d = (0, 1)

p1=(4,11);
p2=(10,2);
p3=p1-p2=(-6,9);

我们可以得到这样一个单纯形。
这三次运行的结果如下图

(译者注,一定要认真的看这个例子对照着图示2这样才能够看懂之前说的那些东西)

七、相交检测:

我们之前说过,如果两个多边形相交那么他们的闵科夫斯基差就会包含原点。在图示3中单纯形并没有包含原点,但是两个多边形明明相交。这里的问题在于,我们第一次选择的方向d并没有成功的的生产出一个包含原点的单纯形。
如果我们使用d=(0,-1)作为第三次的d,那么

p1=(4,5);
p2=(5,7);
p3=p1-p2=(-1,-2);

这样就能够产生一个下图所示的包含远点的单纯型,这样我们就可以判断出来两个多边形相交。

所以正如我们所见,d的选择能够影响结果。我们不能保证我们选择的三个点就能够包含原点,我们同样不能够保证闵科夫斯基差包含原点。我们用选择指向原点的点的方式替换原来的选择点的方式。如果我们改变我们选择第三个闵科夫斯基点的方式产生的简单型就能够包含原点了。

d = ...//这里表示还是和上面的前两步一样
a = support(..., d)
d = ...
b = support(..., d)
AB = b - a
AO = ORIGIN - a
d = (AB x AO) x AB//向量的叉乘,这样计算的点//在直线AB和原点同向的一侧
c = support(..., d)

因为d是依靠与ab形成的线,我们可以选择相反的方向作为第二次的d,这样就能够保证b和a的距离尽可能的远。

d = ...
a = support(..., d)
b = support(..., -d)
AB = b - a
AO = ORIGIN - a
d = (AB x AO) x AB
c = support(..., d)

那么现在我们需要做的就是为第一次闵科夫斯基差选择一个d。这里有很多的选择,一个随机的方向,这两个形状的中心连线创建的方向,等等。任何一种都可以,但是有一些选择更优。

八、迭代:

尽管我们改变了之前的做法来判断是否碰撞,但是我们依然没有通过那三个步骤得到一个包含原点的单纯形。我们必须创建单纯形,这样就能够保证我们得到的单型和原点的距离越来越近。迭代过程中有两点需要注意:

  1. 是否已经得到了一个包含原点的单纯形。
  2. 是否有能够得到一个包含原点的单纯型。

下面是算法示意代码:
(译者注:这个伪代码和下面的例子好好看)

Vector d = // choose a search direction
// get the first Minkowski Difference point
Simplex.add(support(A, B, d));
// negate d for the next point
d.negate();
// start looping
while (true) {// add a new point to the simplex because we haven't terminated yetSimplex.add(support(A, B, d));// make sure that the last point we added actually passed the originif (Simplex.getLast().dot(d) <= 0) {// if the point added last was not past the origin in the direction of d// then the Minkowski Sum cannot possibly contain the origin since// the last point added is on the edge of the Minkowski Differencereturn false;} else {// otherwise we need to determine if the origin is in// the current simplexif (Simplex.contains(ORIGIN)) {// if it does then we know there is a collisionreturn true;} else {// otherwise we cannot be certain so find the edge who is// closest to the origin and use its normal (in the direction// of the origin) as the new d and continue the loopd = getDirection(Simplex);}}
}

下面就让我们用这个方法进行测试:
初始化

d = c2 - c1 = (9, 5) - (5.5, 8.5) = (3.5, -3.5) = (1, -1);
//这里的c 表示图形的图心
p1 = support(A, B, d) = (9, 9) - (5, 7) = (4, 2);
Simplex.add(p1);
d.negate() = (-1, 1);

然后我们开始循环迭代:

需要注意的是:(A x B) x C = B(C.dot(A)) – A(C.dot(B))

last = support(A, B, d) = (4, 11) - (10, 2) = (-6, 9);
proj = (-6, 9).dot(-1, 1) = 6 + 9 = 15
// we past the origin so check if we contain the origin
// we dont because we are line
// get the new direction by (AB x AO) x AB
AB = (-6, 9) - (4, 2)  = (-10, 7);
AO = (0, 0) - (-6, 9) = (6, -9);
(AB x AO) x AB = AO(149) - AB(-123) = (894, -1341) - (1230, -861)
               = (-336, -480)
               = (-0.573, -0.819)

下面这张图展示了第一次迭代的结果

第二次迭代

last = support(A, B, d) = (4, 5) - (12, 7) = (-8, -2)
proj = (-8, -2).dot(-336, -480) = 2688 + 960 = 3648
// we past the origin so check if we contain the origin
// we dont (see Figure 6a)
// the new direction will be the perp of (4, 2) and (-8, -2)
// and the point (-6, 9) can be removed
AB = (-8, -2) - (4, 2)  = (-12, -4);
AO = (0, 0) - (-8, -2) = (8, 2);
(AB x AO) x AB = AO(160) - AB(-104) = (1280, 320) - (1248, 416)
               = (32, -96)
               = (0.316, -0.948)

在第二次迭代之后并没有得到一个包含原点的简单型,但这并不能说明我们不能最终计算出来。在第二次迭代中我们移除了点(-6,9)因为无论何时我们只需要三个点即可,而且在每次迭代开始的时候我们将插入一个新的点。

第二次循环产生的简单型如下图示

第二次迭代产生的方向如下图示

第三次迭代

last = support(A, B, d) = (4, 5) - (5, 7) = (-1, -2)
proj = (-1, -2).dot(32, -96) = -32 + 192 = 160
// we past the origin so check if we contain the origin
// we do (Figure 7)!(下图)

第三次迭代将产生期望的简单型,如下图

九、检测单纯形:

我们忽略了这个算法的两个操作。一个是,我们如何我们如何知道当前的单纯形包含原点,另一个是,我们如何选择下一个方向。在伪代码中,我为了描述方便将这些操作单列出来,但是在实际操作中,他们应该在一块进行,因为他们需要很多相同的信息。
我们可以通过对单纯形进行一系列的简单的点乘操作来判断原点和单纯形的位置关系。第一个要解决的问题是线段问题。让我们看一下上面的第一次循环。在第九行添加了第二个点之后,现在单纯形是一个线段。我们可以检测泰森多边形区域,来判断单纯形是否有可能包含原点。
泰森多边形示例如下图

线段被定义为从A到B,其中A是最后添加到单纯性中取得。我们知道了A和B都是在闵科夫斯基边缘上的,因此原点不能在R1(上图中的区域)或者R4.我们可以得出这样的假设因为第11行的检测返回false指示出我们可以包含原点。原点只可能落在R2或者R3中,而且一个线段不可能包含原点,所以下一步需要做的就是计算出一个新的方向。这可以按照先前的介绍的,通过AB的叉乘计算出朝向原点的向量。

// the perp of AB in the direction of O can be found by
AB = B - A;
AO = O - A;
perp = (AB x AO) x AB;

这里的问题在于,如果原点落在了单纯形的边上会发生什么样的情况,第11行的检测就会失败。这样会在如下两中情况下发生:
1. 原点在闵科夫斯基内部(译者注,不懂什么意思 这个!)
2. 在闵科夫斯基边缘。第二中情况表示接触但没有渗透(就是没有相交),所以具体如何处理这种情况需要你自己决定。在其他情况下,你可以使用AB的左法线或者右法线作为新的d。

现在让我们检测第二次循环,第二次循环将我们的单纯形转化为了三角形,下图所示。

白的区域不必测试因为他们通过了第11行的测试。R2不可能包含原点,因为最后的我们选择的方向是相反的方向。所以需要测试的就是R3,R4和R5。我们可以通过(AC X AB) X AB来计算出AB的法线。然后我们通过计算AB的法线点乘AO来判断原点是否在R4区域里。

AB = (-6, 9) - (-8, -2) = (2, 11)
AC = (4, 2) - (-8, -2) = (12, 4)
// (AC x AB) x AB = AB(AB.dot(AC)) - AC(AB.dot(AB))
ABPerp = AB(68) - AC(125)= (136, 748) - (1500, 500)
       = (-1364, 248)
       = (-11, 2)
// compute AO
AO = (0, 0) - (-8, -2) = (8, 2)
ABPerp.dot(AO) = -11 * 8 + 2 * 2 = -84
// its negative so the origin does not lie in R4

通过更多的测试我们可以判断出来原点在:

AB = (-6, 9) - (-8, -2) = (2, 11)
AC = (4, 2) - (-8, -2) = (12, 4)
// (AB x AC) x AC = AC(AC.dot(AB)) - AB(AC.dot(AC))
ACPerp = AC(68) - AB(160)= (816, 272) - (320, 1760)
       = (496, -1488)
       = (1, -3)
// compute AO
AO = (0, 0) - (-8, -2) = (8, 2)
ACPerp.dot(AO) = 1 * 8 + -3 * 2 = 2
// its positive so that means the origin lies in R3

所以我们判断出来原点在R3中,现在我们需要选择一个新的d作为我们得到下一个闵科夫斯基差的点的方向。这很简单,因为我们知道了AC的泰森多边形区域包含原点:

(AC x AO) x AC

现在最终的把所有的情况都考虑在内的算法为:

Vector d = // choose a search direction
// get the first Minkowski Difference point
Simplex.add(support(A, B, d));
// negate d for the next point
d.negate();
// start looping
while (true) {// add a new point to the simplex because we haven't terminated yetSimplex.add(support(A, B, d));// make sure that the last point we added actually passed the originif (Simplex.getLast().dot(d) <= 0) {// if the point added last was not past the origin in the direction of d// then the Minkowski Sum cannot possibly contain the origin since// the last point added is on the edge of the Minkowski Differencereturn false;} else {// otherwise we need to determine if the origin is in// the current simplexif (containsOrigin(Simplex, d) {// if it does then we know there is a collisionreturn true;}}
}public boolean containsOrigin(Simplex s, Vector d) {// get the last point added to the simplexa = Simplex.getLast();// compute AO (same thing as -A)ao = a.negate();if (Simplex.points.size() == 3) {// then its the triangle case// get b and cb = Simplex.getB();c = Simplex.getC();// compute the edgesab = b - a;ac = c - a;// compute the normalsabPerp = tripleProduct(ac, ab, ab);acPerp = tripleProduct(ab, ac, ac);// is the origin in R4if (abPerp.dot(ao) > 0) {// remove point cSimplex.remove(c);// set the new direction to abPerpd.set(abPerp);} else {// is the origin in R3if (acPerp.dot(ao) > 0) {// remove point bSimplex.remove(b);// set the new direction to acPerpd.set(acPerp);} else{// otherwise we know its in R5 so we can return truereturn true;}}} else {// then its the line segment caseb = Simplex.getB();// compute ABab = b - a;// get the perp to AB in the direction of the originabPerp = tripleProduct(ab, ao, ab);// set the direction to abPerpd.set(abPerp);}return false;
}

结束语

到这里 GJK 判断两个凸多边形是否相交就已经翻译完了,原文的下方有很多非常有意义的问答,都是一些这方面的爱好者对作者的提问,作者的耐心回答,很有意义的。如果有时间可以自己看,翻译那个东西太费事了。。。

判断两个形状是否相交(二)-GJK相关推荐

  1. 编程之美:编程判断两个链表是否相交

    1.问题描述 给出两个单向链表的头指针,比如h1.h2,判断两个链表是否相交.编程之美为了简化问题,假设两个链表均不带环. 如下图: 2.分析与解法 解法一:直观法,先判断第一个链表的每个节点是否在第 ...

  2. 判断两个圆柱体是否相交

    问题 看到有人问这个问题.然后我写了解决这个问题的一个有用的理论准备.接下来简单介绍如何使用它判断两个圆柱是否相交. 解法 问题中的圆柱假设有中轴线,lil_i, 用固定点 Xi=(ai,bi,ci) ...

  3. 3.6 判断两个链表是否相交

    判断两个链表是否相交,若相交则求其交点(指第一个相交的点). 思路1,利用计数法: 遍历链表1,将其节点的内存地址存入map或者hashmap内.然后遍历链表2,并查询map或者hashmap,判断链 ...

  4. 判断两个链表是否相交

    方法:获得两个链表的长度,获得长度的差值len,然后首先遍历较长的链表len次,然后再同时遍历两个链表,如果有相同部分,两个链表就相交,如果没有,则不相交,即没有公共部分. 代码: #include ...

  5. ]数据结构:单链表之判断两个链表是否相交及求交点(带环、不带环)

    1.判断两个链表是否相交,若相交,求交点.(假设链表不带环) 两个指针同时指向两个链表,分别依次往后遍历链表到最后一个节点,如指针的值相同(即节点地址相同),反之没有交点. int IsCross(N ...

  6. 如何判断两条直线是否相交

    之前写过一篇如何判断两条线段是否相交,我们紧接这个主题,再来谈谈如何判断两条直线是否相交 如何判断两条直线是否相交 总体来上,判断直线是否相交比判断线段是否相交容易多了 两条直线相交只有两种情况 第一 ...

  7. 判断两条线段是否相交 java_判断两个线段是否相交02

    写在前面 在其他博客中看到这方面的知识,很多都是重复,并且说的总是云里雾里的,所以这里我就自己总结一下这种问题如何求解,判断两个线段是否相交在前面我们提到了会用到叉积的一点知识,那么这里就来详细说一下 ...

  8. 判断两个多边形是否相交相交

    // 1 判断相交//判断两多边形线段是否相交function isSegmentsIntersectant(segA, segB) {//线线const abc = (segA[0][0] - se ...

  9. 判断两条直线是否相交c语言,计算几何-两条线段是否相交(三种算法)

    原标题:计算几何-两条线段是否相交(三种算法) 计算几何中,判断线段是否相交是最基本的题目. 所谓几何, 最基本的当然就是坐标, 从坐标中我们可以知道位置和方向,比如:一个点就是一个位置,两点确定一条 ...

最新文章

  1. python快速编程入门黑马-新手如何快速入门Python编程?/开发python入门教程
  2. Delphi 2009 新增单元 Character[2]: IsLetter、IsUpper、IsLower、IsDigit、IsNumber
  3. GDCM:gdcm::XMLPrinter的测试程序
  4. ES6-9 对象密封4种方式、assign、取值函数的拷贝
  5. Codeforces Round #632 (Div. 2) F. Kate and imperfection 数论 + 贪心
  6. 服务器被黑 追寻ip_我的服务器被打死,源IP暴露怎么办补救
  7. HashSet 与HashMap底层实现
  8. mac下编译hadoop-3.0.3
  9. Spring-3.2.4 + Quartz-2.2.0集成实例
  10. (研究向)如何使用Windows任务管理器看BadApple
  11. matlab上的派克变换变换,Matlab_Simulink中Clark变换和Park变换的深度总结
  12. 程序员职业规划(转)
  13. OBJ 模型文件与MTL材质文件 介绍
  14. php+bmp+加密,郁闷啊,谁知道BMP图片加密技术吗
  15. 基于anyrtc的sdk实现直播连麦互动
  16. thingsboard 编译及分析
  17. 家用计算机中PCB板材质,介绍PCB电路板的主要原材料
  18. 菜鸟初识脚本 and 脚本语言
  19. for和of引导的不定式结构的区别
  20. 使用virtuoso和calibre对版图进行DRC LVS的检查

热门文章

  1. 【java之GUI设计】传说逐渐“退隐江湖”的java之GUI!
  2. Android 为应用添加打开方式
  3. noahbuscher\macaw Fatal error
  4. 用微信小程序开店之八——小程序组件4:“表单”(2)
  5. 引导页onboarding页面XIB实现
  6. 爱神说.松勤性能测试项目实战2022学完了笔记+学后感
  7. 工业相机曝光和增益的基本概念
  8. RabbitMQ第三篇:采用不同的交换机规则
  9. JAVA经典例题二(10 examples)
  10. java mysql 备份