GJK碰撞检测算法

摘要:
GJKGJK是这次要实现的碰撞检测算法。快速而简单事实上,就我所知的碰撞检测算法而言,应用程序对象是凸多边形。MD是GJK算法的理论基础。实际上,上述实现不是真正的GJK算法。真正的GJK算法使用了一种非常巧妙的方法来减少循环次数,效果非常理想。GJK如何快速确定这两个数字是否冲突?代码片段交互示例只是介绍了实现GJK算法中更重要的几点。新选项卡中的GJK算法并不复杂,完整代码不到200行。

https://blog.lufei.so/#/collisionDetection/GJK/1

https://blog.lufei.so/#/collisionDetection/GJK/2

现实世界里我们对于是否碰撞的判断可以说极其容易而且准确,比如下图。在二进制的世界里,一切就没这么直观了。

GJK碰撞检测算法第1张

GJK(Gilbert-Johnson-Keerthi Distance Algorithm)

GJK就是此次要实现的碰撞检测算法。如果对碰撞算法有过了解的话,大概率听过另一个碰撞检测算法SAT(Separating Axis Theorem)。

GJK相较于SAT有什么优点吗?

  • 简单

实际上就我目前了解的碰撞检测算法,应用对象都是凸多边形(Convex polygon)。如果不是凸多边形,问题也不大,可以事先分割。

游戏里对于不规则物体,我们通常都是借助工具生成顶点数据。此时生成的数据通常都是处理过的(凹多边形被分解),如果你想了解更多关于凹多边形分解的知识,可以参考这两个库:poly-decomp.jsearcut

GJK碰撞检测算法第2张

Minkowski Difference

由于不知道Min到底是“明”还是“闵”,所以下面都用MD表示了。

MDGJK算法的理论基础。那么到底什么是MD

假设有两个凸多边形:

s1 =[{x:1, y:10},{x:3, y:10},{x:3, y:8},{x:1, y:8}]
s2 =[{x:2, y:9},{x:4, y:7},{x:2, y:7}]

那么它们的位置看起应该像下图这样。

GJK碰撞检测算法第3张

MD就是s1s2所有点的差形成的集合。

MD(s1, s2):
  s3 =[]for p1 in s1:for p2 in s2:
      s3.push(p1 - p2)return s3
MD(s1, s2)=>[{x:-1, y:1},{x:-3, y:3},{x:-1, y:3},{x:1, y:1},{x:-1, y:3},{x:1, y:3},{x:1, y:-1},{x:-1, y:1},{x:1, y:1},{x:-1, y:-1},{x:-3, y:1},{x:-1, y:1}]

这些点的布局如下图所示:

GJK碰撞检测算法第4张

关键的地方来了。首先要介绍一个新的概念叫Convex Hull

Convex hull是包含点集的最小凸多边形。拿上面的例子来说:

GJK碰撞检测算法第5张

铺垫了这么久,现在可以说结论了。

我们把s1 - s2点集形成的Convex hull命名为s3如果s3包含点(0, 0),那么s1s2发生碰撞。

有没有觉得很简单?对,原理就是这么简单。

至于怎么算出点集的Convex hull,翻译自维基百科Gift wrapping algorithm

functionwrap(points){const hull =[]let current ={x:Infinity}for(const p of points){if(p.x < current.x) current = p
  }let i =0, end

  while(true){
    hull[i]= current
    end = points[0]for(let j =1; j < points.length; j++){if((end.x === current.x && end.y === current.y)||inline(points[j], hull[i], end)>0){
        end = points[j]}}
    i +=1
    current = end

    if(end.x === hull[0].x && end.y === hull[0].y)break}return hull
}

Gift wrapping algorithm是获取Convex hull的一种算法,不是效率最高的但应该是最易懂的。

最后就是判断点是否在多边形内的算法了,可以看我之前的文章

交互示例

思考

有小伙伴不禁要问:这样的嵌套循环真的比SAT快?确实,上面的实现并不是真正意义上的GJK算法。但是核心思想是一样的:

如果两个凸多边形的Minkowski Difference所形成的Convex hull包含点(0, 0),那么这两个凸多边形相交。

怎么优化这个实现呢?

我们并不需要计算两个凸多边形所有点的Minkowski Difference。还是文章开始的例子:

GJK碰撞检测算法第6张

我们只需要尽早的从已知的条件里判断出是否包含原点即可。

比如:如果获取到的第一个点刚好是原点,说明相交停止循环,否则继续获取下一个点,如果原点在两点的连线上,说明相交停止循环,否则继续获取下一个点。

真正的GJK算法用了一个很取巧的方式来减少循环次数,而且效果很理想。当然这也是下篇文章里的内容了。

感兴趣的小伙伴也可以从下面的参考资料里先尝试一波。

参考资料

国际惯例先放图。

GJK碰撞检测算法第7张

GJK是怎么快速判断出这两个图形是否碰撞的呢?

GJK碰撞检测算法第8张

Support Function

Support Function用来计算凸体在给定方向上的最远点。什么意思呢?

GJK碰撞检测算法第9张代码片段

图示中的例子带入,可以得到(9, 6) = (0, 4) - (-9, -2),正是图二三角形的一个顶点。

将方向取反再调用getSupport,我们可以得到(-1, -2) = (-6, 0) - (-5, 2),也是图二三角形的一个顶点。

这样做的意义是什么呢?因为可以确保我们所取的两个点跨度足够大,有更大的概率包含原点,减少循环次数。

GJK碰撞检测算法第10张

那么问题来了:

  • 初始给定的方向是怎么来的?

    随机。更推荐的是凸体中心的差:direction = shapeA.center - shapeB.center

  • 已经获取了两个点,那么第三个点如何确定呢?

    通过a(9, 6)b(-1, -2),可以计算出垂直于向量ab(-10, -8)且指向原点方向的向量,这个向量将会作为direction来获取第三个点。

    GJK碰撞检测算法第11张

    这里要用到向量积来计算出direction

    代码片段

核心算法

获取到三个点后,我们需要判断原点的是否在这三个所形成的多边形内。如果在说明碰撞,不在则剔除一个点后继续寻找下一个点。

GJK碰撞检测算法第12张

上面这种情况:w * AO > 0,说明原点在AB外部,则剔除点C并以wdirection继续寻找下一个点。

GJK碰撞检测算法第13张

上面这种情况:w * AO < 0,说明原点在AB内部,则验证剩余的边(实际上不需要验证所有的边)。假如我开始获取到的两个点是B,C,则我们只需要验证AB,AC,因为原点一定在BC内部。

这里的关键点在于:如何计算出垂直于AB且指向远离点C的方向的向量w

直接贴代码了,毕竟也解释不了为何是这样的运算顺序。

代码片段

交互示例

上面只是介绍了我觉得实现GJK算法中比较重要的点。整个流程可能并不够清晰,所以这里附上完整的步骤分解示例。

新标签页中打开

总结

GJK算法并不复杂,完整的代码不到200行。主要用到的数学知识是数量积向量积

参考资料

免责声明:文章转载自《GJK碰撞检测算法》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇百度DMA+小度App的蓝牙语音解决方案技术难点解析让Apache支持URL重写下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

leetcode常规算法题复盘(第七期)——区间和的个数(附带排序算法归纳)

题目原文   327. 区间和的个数   给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper。区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。 说明:最直观的算法复杂度是 O(n2) ,请在此基础上优化你的算法。 示例: 输入...

bootstrap 弹窗或者提示框插件 bootstrap-growl 和bootstrap-notify

Bootstrap简单好用的页面右上角咆哮提示框 - daidaineteasy的专栏 - CSDN博客https://blog.csdn.net/daidaineteasy/article/details/42026223 Bootstrap弹出窗示例 - sunnylinner的博客 - CSDN博客https://blog.csdn.net/sunn...

Fiddler 菜单功能 Host配置 请求伪造 接口调试

菜单功能:   Fiddler工具栏上每个按钮的功能只要鼠标停留在按钮上面就会出现英文描述的功能。   小气泡:增加备注,点击气泡即可对下面捕捉到的会话增加备注(很少使用)   Replay回放按钮:较常用,捕捉到一个会话后想回放这个会话,点这个按钮即可。   remove按钮:清空监控面板,下面还附有很多remove规则,可按照需要选择相应的remove...

win10 Jmeter5.1进行websocet压力测试笔记 服务端 net core2.2

1. win10安装jmeter5.1 参考文件 https://blog.csdn.net/Jenny_He/article/details/88926605 2. JMeter 扩展实现 WebSocket 支持 JMeter 是目前最为流行的开源性能测试工具,JMeter 本身提供的基于插件的机制允许第三方实现标准 JMeter 所不支持的协议,而...

国家集训队论文分类整理(转)

距离ACM/ICPC的时间越来越少了,选择性地看一些集训队论文是很有必要的。 (在此给已经看过所有论文的神牛跪了= =)http://www.cnblogs.com/AbandonZHANG/archive/2012/07/21/2601889.html 所以,我在此整理了一下,供大家参考。 组合数学 计数与统计 2001 - 符文杰:《P...

OpenCV学习笔记(30)KAZE 算法原理与源码分析(四)KAZE特征的性能分析与比较

KAZE系列笔记:1.OpenCV学习笔记(27)KAZE 算法原理与源码分析(一)非线性扩散滤波 2.OpenCV学习笔记(28)KAZE 算法原理与源码分析(二)非线性尺度空间构建 3.OpenCV学习笔记(29)KAZE 算法原理与源码分析(三)特征检测与描述 4.OpenCV学习笔记(30)KAZE 算法原理与源码分析(四)KAZE特征的性能分...