GJK 算法

GJK算法是一种用于检测凸包之间碰撞的迭代方法,基于闵科夫斯基和的概念。它不需要计算完整的闵科夫斯基差,而是通过构建包含原点的单纯形来判断碰撞。算法的关键包括支持函数和单纯形迭代,适用于具有支持函数的任何形状。GJK在3D环境中比SAT更快,特别适合确定两个凸形之间的距离和小距离穿透情况下的碰撞信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GJK 算法(Gilbert–Johnson–Keerthi)

翻译自:http://www.dyn4j.org/2010/04/gjk-gilbert-johnson-keerthi/

今天,我将讨论dyn4j项目随附的其他碰撞检测算法。您可以找到很多GJK文档,但是其中很多实际上是技术性的,主要是因为它们是研究论文。我强烈推荐该视频教程,老实说,看完之后,您甚至不需要进一步阅读。但是,如果您在观看视频后觉得需要更多信息,请继续阅读。

  1. 介绍
  2. 凸包
  3. 闵可夫斯基和
  4. 单纯形
  5. Support函数
  6. 创建单纯形
  7. 确定碰撞
  8. 迭代
  9. 检查单纯形

简介
GJK与SAT一样,仅在凸包上运行。GJK更具吸引力的功能之一是,它可以支持实现“
support ”函数的任何形状(我们将在后面讨论)。因此,与SAT不同,您不需要增加特殊的代码或算法来处理弯曲的形状。

GJK是一种迭代方法,但是收敛速度非常快,如果使用最终的穿透/分离向量进行约束,则可以在几乎恒定的时间内运行。在3D环境中,它是SAT的更好替代方案,因为SAT必须测试轴数。

GJK的初衷是确定两个凸包之间的距离。GJK还可以用于在小距离穿透情况下获取碰撞信息,并可以通过其他算法进行补充以实现更大距离的穿透。

凸包
正如我之前说的,GJK是只能用于凸包的算法。请参阅我在SAT上的帖子以获取有关凸包的解释。

闵科夫斯基和
GJK算法在很大程度上依赖于称为闵科夫斯基和的概念。闵科夫斯基和在概念上非常容易理解。假设您有两个形状,这些形状的闵科夫斯基和就是shape1中的所有点加到shape2中的所有点上{得到的是另外一个更大更复杂的形状}:

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

如果两个形状均为凸包,则所得形状为凸包{如果需要深入了解,可以参考wiki百科}。

您可能会想,“好,那很好,但是这有什么关系呢?”重要性不在于加法,而是在于减法:

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

作为补充,在继续操作之前,请注意,即使我们使用的是“减法”运算符,然而事实上它不该被称为闵科夫斯基差,因为其仍然是闵科夫斯基和{相当于先将B中的所有点相对于原点进行镜像,使用得到的形状与A计算闵科夫斯基和}。不过,在本文的其余部分中,为清楚起见,我将其称为闵科夫斯基差。

继续,在闵科夫斯基和中执行减法操作的关键是:

如果两个形状重叠/相交,则闵科夫斯基差将包含原点。

图1:两个凸形状相交

图1:两个凸形状相交

让我们看一个例子,取图1中的两个形状并对它们执行闵科夫斯基差,您将得到图2中的结果形状。请注意,结果形状包含原点,因为图1中的两个形状是相交的。

执行此操作需要shape1.vertices.size * shape2.vertices.size * 2个减法。这是很重要的,因为形状是由无穷多个点组成的。由于这两个形状都是凸包,且由最外面的顶点定义,所以我们只需要对这些顶点执行此操作。关于GJK伟大的事情是,你并不真正需要计算闵科夫斯基差。

图2:Minkowski差异

图2:闵科夫斯基差

单纯形(Simplex)
我们不想计算闵科夫斯基差,而只是想知道闵科夫斯基差是否包含原点。如果包含,那么两个形体就是是相交的;如果不包含,则它们不相交。

我们可以做的是,在闵科夫斯基差形体内迭代构建一个试图包含原点的多边形。如果我们构建的多边形包含原点(也形体包含在闵科夫斯基差形体内),那么我们可以说闵科夫斯基差包含原点。我们要构建的多边形称为“单纯形(Simplex)”。

Support函数
那么下一个问题是我们如何构建单纯形?Simplex是使用所谓的Support函数构建的。给定两个形体,Support函数应在闵科夫斯基差内返回一个点。我们已经知道我们可以从shape1中获取一个点,并从shape2中获取一个点,并将其相减以获得闵科夫斯基差中的一个点,但是我们不希望每次都得到相同的点。

如果使Support函数依赖于方向,我们可以确保每次调用Support函数时都不会得到相同的点。换句话说,如果使Support函数在某个方向上返回最远的点,则可以后续选择其他方向来获取其他点。

选择方向上最远的点很重要,因为它会创建一个最大包含面积的单纯形,因此增加了算法快速退出的机会。另外,我们可以利用这样的事实,即以这种方式返回的所有点都在闵科夫斯基差的边缘,因此,如果我们不能沿某个方向添加一个超出原点的点,我们就知道闵科夫斯基差不包含原点。这增加了算法在非相交情况下快速退出的机会。稍后对此进行更多讨论。

 

1

2

3

4

5

6

7

8

9

10

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 directions

  Point p1 = shape1.getFarthestPointInDirection(d);

  Point p2 = shape2.getFarthestPointInDirection(d.negative());

  // perform the Minkowski Difference

  Point p3 = p1.subtract(p2);

  // p3 is now a point in Minkowski space on the edge of the Minkowski Difference

  return p3;

}

创建单纯形

让我们从一个例子开始。使用图2中的形状并执行3次Support函数:
首先让我们开始使用d =(1,0)

 

1

2

3

p1 = (9, 9);

p2 = (5, 7);

p3 = p1 - p2 = (4, 2);

接下来让我们使用d =(-1,0)

 

1

2

3

p1 = (4, 5);

p2 = (12, 7);

p3 = p1 - p2 = (-8, -2);

请注意,p1可能是(4,5)或(4,11)。两者都会在闵可夫斯基差的边缘产生一个点。
接下来让我们使用d =(0,1)

 

1

2

3

p1 = (4, 11);

p2 = (10, 2);

p3 = p1 - p2 = (-6, 9);

我们获得图3中所示的Simplex。

图3:示例单纯形

图3:示例单纯形

确定碰撞

前面我们说过,如果闵可夫斯基差中的单纯形包含原点,则我们知道这两个形状是相交的。在图3中,单纯形不包含原点,但是我们知道这两个形状是相交的。这里的问题是,我们的第一个猜测(在选择方向时)没有产生包含原点的单纯形。

如果相反,我为第三个闵科夫斯基差方向选择d =(0,-1):

 

1

2

3

p1 = (4, 5);

p2 = (5, 7);

p3 = p1 - p2 = (-1, -2);

这样就产生了图4所示的单纯形,现在我们包含了原点并可以确定存在碰撞。

图4:包含原点的示例单纯形

图4:包含原点的示例单纯形

因此,正如我们所看到的,方向的选择会影响结果。我们还可以看到,如果获得不包含原点的单纯形,则可以计算另一个点,并使用它。

这就是算法的迭代部分出现的地方。我们不能保证我们选择的前3个点将包含原点,也不能保证Minkowski差值包含原点。我们可以通过仅沿原点方向选择点来修改选择点的方式。如果我们将选择第三个Minkowski差异点的方式更改为下方,则可以将原点围起来。

 

1

2

3

4

5

6

7

8

d = ...

a

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值