计算多边形之间的最近距离,才是GJK算法原本的目的。只有两个多边形不相交,计算最近距离才有效。如果相交,则最近距离无效,但是可以使用EPA算法要计算碰撞深度。本文的写作目的,主要是对GJK算法的理解和应用。对算法本身感兴趣的朋友,可以阅读源论文的文献。本系列GJK算法文章共三篇,本篇是第二篇,强烈建议从第一篇开始看:

  • GJK碰撞检测算法基础
  • GJK计算多边形之间的最近距离
  • GJK和EPA计算穿透向量

本文作者游蓝海。原创不易,未经许可,禁止任何形式的转载。

文章目录

  • 1. 基本原理
  • 2. 算法解析
    • 2.1 算法伪代码
    • 2.2 计算多边形间的最近距离
    • 2.3 计算多边形间的最近点
  • 3. 小结
  • 4. 参考

1. 基本原理

计算多边形之间的距离,本质是在两个多边形上找到距离最近的边,然后计算两个边之间的最近距离。计算最近距离的算法和GJK碰撞检测算法类似,同样使用闵可夫斯基差来构建单形体,使用同样的support函数。当没有发生碰撞的时候,距离原点最近的闵可夫斯基差集多边形的边,就是我们要计算最近距离的关键。为了方便表示,下文将该边称作“差集最近边”。

GJK最近距离算法的两个关键点为:

  • 原点到差集最近边的距离,就是两个多边形之间的最近距离;
  • 构成差集最近边的两个support点,分别来自两个多边形的最近边。因此,通过support点,可以反推出两个多边形的最近边。

需要注意的是,两个多边形最近的位置不一定是边,也可能是顶点。不过这种情况,可以认为是长度为0的特殊边。

2. 算法解析

2.1 算法伪代码

/// 按步骤分解,碰撞检测
public bool GJK(Shape shapeA, Shape shapeB)
{Vector2 direction = findFirstDirection();simplex.add(support(direction));simplex.add(support(-direction));direction = -getClosestPointToOrigin(simplex.get(0), simplex.get(1));while(true){SupportPoint p = support(direction);// 新点与之前的点重合了。也就是沿着dir的方向,已经找不到更近的点了。if (Vector2.Distance(p.point, simplex.get(0)) == 0 ||Vector2.Distance(p.point, simplex.get(1)) == 0){break;}simplex.add(p);// 单形体包含原点了if (simplex.contains(Vector2.zero)){return true;}direction = findNextDirection();}ComputeClosetPoint();return false;
}

该算法与上一章的GJK碰撞检测算法很相似,但有两个细微的差别:

  1. 选择下次的迭代方向不同。上一章用的是原点到直线的垂线作为迭代方向;本章用的是原点到线段的最近向量,作为迭代方向。注意,原点到线段的最近距离不一定就是垂足,可能是线段的某个端点
  2. 不发生碰撞的结束条件不同。上一章迭代算法最终结束时,可能是一个不包含原点的三角形,为了得到差集最近边,我们可以舍弃掉距离原点较远的那个边。这里我们让算法多迭代一次,恰好可以舍弃掉那个点,同时会额外会产生一个重复的support点。因此只要发现support点位置重叠了,就表明迭代算法可以结束了。

计算步骤的分解动图:

2.2 计算多边形间的最近距离

最近距离就是原点到差集最近边的距离。先求出原点到线段的最近点,然后计算原点到最近点的距离即可。设线段为ab,原点为o,先计算出ao在ab上的投影长度:

  • 如果投影小于0,说明最近点为a点;
  • 如果大于ab的长度,说明最近点为b点;
  • 否则,就在ab之间。

这里有个小技巧,计算投影长度,需要将被投影向量ab单位化,也就是要求ab的长度。但是为了让ao在ab上投影长度单位化到[0, 1]之间,需要额外除以ab的长度,因此计算投影的时候,直接就除以ab长度的平方了,就省去了计算长度时的开方运算。

Vector2 getClosestPointToOrigin(Vector2 a, Vector2 b)
{Vector2 ab = b - a;Vector2 ao = Vector2.zero - a;float sqrLength = ab.sqrMagnitude;// ab点重合了if(sqrLength < float.Epsilon){return a;}// 计算投影长度,并单位化到[0, 1],需要额外除以ab的长度。因此这里就直接除以长度的平方了。float projection = Vector2.Dot(ab, ao) / sqrLength;if (projection < 0){return a;}else if (projection > 1.0f){return b;}else{return a + ab * projection;}
}

2.3 计算多边形间的最近点

我们改造一下support方法,每次将生成support点用到的两个顶点也记录下来。这样通过support点,可以反向找回多边形的边。

设是差集最近边为L=ABL = ABL=AB,A、B是support点,构成A、B两的顶点分别自两个多边形的边E1=Aa−BaE1 = Aa - BaE1=Aa−Ba、E2=Ab−BbE2 = Ab - BbE2=Ab−Bb。则求两个凸包的最近距离,就演变成了求E1和E2两个边的最近距离。

设Q是原点到L的垂直向量,也就是垂足,则有:
{L=B−AQ⋅L=0\begin{cases} L = B - A \\ Q · L = 0 \end{cases} {L=B−AQ⋅L=0​
因为Q是L上的点,可以用r1、r2r1、r2r1、r2来表示QQQ: Q=A∗r1+B∗r2Q = A * r1 + B * r2Q=A∗r1+B∗r2 ,其中r1+r2=1r1 + r2 = 1r1+r2=1 。则有:
(A∗r1+B∗r2)⋅L=0(A * r1 + B * r2) · L = 0 (A∗r1+B∗r2)⋅L=0
用r2r2r2代替r1r1r1,r1=1−r2r1 = 1 - r2r1=1−r2,则有:
(A−A∗r2+B∗r2)⋅L=0(A+(B−A)∗r2)⋅L=0L⋅A+L⋅L∗r2=0r2=−(L⋅A)/(L⋅L)(A - A * r2 + B * r2) · L = 0 \\ (A + (B - A) * r2) · L = 0 \\ L · A + L · L * r2 = 0 \\ r2 = -(L · A) / (L · L) (A−A∗r2+B∗r2)⋅L=0(A+(B−A)∗r2)⋅L=0L⋅A+L⋅L∗r2=0r2=−(L⋅A)/(L⋅L)

推导过程略显复杂,但是最终的公式却很简单:
{r2=−(L⋅A)/(L⋅L)r1=1−r2Qa=Aa∗r1+Ba∗r2Qb=Ab∗r1+Bb∗r2\begin{cases} r2 = -(L · A) / (L · L)\\ r1 = 1 - r2 \\ Qa = Aa * r1 + Ba * r2 \\ Qb = Ab * r1 + Bb * r2 \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧​r2=−(L⋅A)/(L⋅L)r1=1−r2Qa=Aa∗r1+Ba∗r2Qb=Ab∗r1+Bb∗r2​
计算最近点的伪代码如下:


void ComputeClosetPoint()
{SupportPoint A = simplex.getSupport(0);SupportPoint B = simplex.getSupport(1);Vector2 L = B.point - A.point;float sqrDistanceL = L.sqrMagnitude;// support点重合了if (sqrDistanceL < 0.0001f){closestOnA = closestOnB = A.point;}else{float r2 = -Vector2.Dot(L, A.point) / sqrDistanceL;r2 = Mathf.Clamp01(r2);float r1 = 1.0f - r2;closestOnA = A.fromA * r1 + B.fromA * r2;closestOnB = A.fromB * r1 + B.fromB * r2;}
}

3. 小结

GJK计算多边形之间的最近距离,本质上是使用GJK算法求得差集最近边。而构成差集最近边的两个support点,就是来自两个多边形的最近边上的4个顶点。然后就将问题转换成,计算两条边之间的最近距离和最近点。

本章Demo使用Unity3D引擎开发,Demo工程已上传github: https://github.com/youlanhai/learn-physics/tree/master/Assets/04-gjk-closest-point

4. 参考

  • GJK算法论文: https://ieeexplore.ieee.org/document/2083?arnumber=2083
  • GJK – Distance & Closest Points: http://www.dyn4j.org/2010/04/gjk-distance-closest-points

本系列文章会和我的个人公众号同步更新,感兴趣的朋友可以关注下我的公众号:游戏引擎学习。扫下面的二维码加关注:

物理引擎学习04-GJK计算多边形之间的最近距离相关推荐

  1. 物理引擎学习05-GJK和EPA计算穿透向量

    EPA,是扩展多边形算法(Epanding Polytop Algorithm) ,用来计算两个多边形碰撞的穿透深度和方向,可用于将两个发生碰撞的多边形分离.本文的写作目的,主要是对GJK和EPA算法 ...

  2. 物理引擎学习03-GJK碰撞检测算法基础

    GJK是由Gilbert,Johnson,Keerthi 三位前辈发明的,用来计算两个凸多面体之间的碰撞检测,以及最近距离.GJK算法可以在O(M+N)的时间复杂度内,检测出碰撞,算法在每次迭代的过程 ...

  3. Java-高德地图根据经纬度计算两坐标之间的直线距离

    Java-高德地图根据经纬度计算两坐标之间的直线距离 最近在做毕设项目,项目打卡需要用到高德地图,看了下高德地图计算两坐标距离的方法,官网上提供的开发包中也有相关的方法. /**** @author ...

  4. 物理引擎学习06-碰撞反馈

    原本计划06章是一个碰撞检测的小demo,上手之后才发现,碰撞反馈也是一个非常复杂的话题,所以就单拎出来一章,详细说明.碰撞反馈是基于碰撞检测的结果,将发生接触的物体分离开,同时应用上物理效果,使碰撞 ...

  5. 物理引擎学习07-小游戏飞机大战

    到目前为止,碰撞检测的基本内容(狭义的碰撞检测)已经讲完了.广义的碰撞检测,我们到下一阶段再继续.本小节,在上节"碰撞反馈"的基础之上,扩展支持多物体间的碰撞检测.使用最简单的方法 ...

  6. Chipmunk-js物理引擎学习笔记

    一.基本概念 空间:在Chipmunk中,空间是所有对象容器.因此,刚体.形状.链接节点等对象都需要添加到空间中.空间控制这些对象的相互作用. 刚体:物理上的刚体指的是在运动和受力作用后,形状和大小不 ...

  7. 物理引擎学习08-AABB树

    AABB树是由AABB包围盒结点构成的二叉树,常用来加速场景中的射线检测和碰撞检测.树的每个结点都是一个包围盒,且结点的包围盒包裹了所有子结点的包围盒.本文深入的讲解了AABB树相关的算法,以及结合物 ...

  8. [Unity 3D] 物理引擎学习笔记(一)

    刚体: 同是物理引擎提供的功能,碰撞检测只需要有 Collider 便可以运作,但所有与作用力相关的属性和函数却都依赖 Rigidbody. 重力: 一旦使用了 Rigidbody 组件,这个 Gam ...

  9. JS高德地图计算两地之间的实际距离

    这个是通过导航的方式来获取两地之间的实际距离,和消耗的时间(key值自己去申请哈) <!doctype html> <html> <head><meta ch ...

最新文章

  1. keystone java,搭建KeystoneJS
  2. python高频面试题_2019下半年金九银十Python高频面试题(第四弹)
  3. 【SICP练习】127 练习3.58
  4. ROS 中的camera支持
  5. fpga摄像头模块_FPGA开源项目:双目测距(一)之双目图像采集显示以及图片保存...
  6. Linux启动管理:grub
  7. C语言课后习题(13)
  8. (转)函数式编程实战教程(Python版)
  9. Linux 命令整理
  10. spark共享变量(广播变量Broadcast Variable,累加器Accumulators)
  11. 【一天一个C++小知识】004.C++中内部链接和外部链接
  12. NTUSER.DAT
  13. ubuntu18.04安装网卡驱动
  14. 安卓手机上计算机的各按键功能,手机按键里那些你不知道的功能
  15. 今天把中国建设银行APP4.2.1版iOS客户端里所有的功能都点了一遍
  16. 毕业设计 嵌入式 stm32车牌识别系统
  17. 计算机相关的著名的期刊和会议
  18. 变分法 (Calculus of Variations)
  19. 【noip模拟赛1】古韵之鹊桥相会(最短路)
  20. 南邮 OJ 2043 有才华的罗老师

热门文章

  1. 【设计模式】策略模式+工厂模式动态绑定类名的几种方式
  2. 电机轴承故障诊断EWT与EEMD方法对比时遇见的问题
  3. 【推荐】教你轻松将文字转化成视频?一个软件搞定!
  4. 适合用在高校的人脸识别闸机系统
  5. 【数据结构学习1】-递归与循环实现区别(python实例)
  6. CG动画制作——实训项目前期工作(二)
  7. 前端布局推进剂 - 间距规范化
  8. 技术之美[程序人生]我在IBM实习的日子
  9. 【设计模式_青春版】创建型|原型模式
  10. 【log+科学计数法】HDU-1060 Leftmost Digit