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

article/2025/9/25 0:39:14

计算多边形之间的最近距离,才是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 = A B L = AB L=AB,A、B是support点,构成A、B两的顶点分别自两个多边形的边 E 1 = A a − B a E1 = Aa - Ba E1=AaBa E 2 = A b − B b E2 = Ab - Bb E2=AbBb。则求两个凸包的最近距离,就演变成了求E1和E2两个边的最近距离。

设Q是原点到L的垂直向量,也就是垂足,则有:
{ L = B − A Q ⋅ L = 0 \begin{cases} L = B - A \\ Q · L = 0 \end{cases} {L=BAQL=0
因为Q是L上的点,可以用 r 1 、 r 2 r1、r2 r1r2来表示 Q Q Q: Q = A ∗ r 1 + B ∗ r 2 Q = A * r1 + B * r2 Q=Ar1+Br2 ,其中 r 1 + r 2 = 1 r1 + r2 = 1 r1+r2=1 。则有:
( A ∗ r 1 + B ∗ r 2 ) ⋅ L = 0 (A * r1 + B * r2) · L = 0 (Ar1+Br2)L=0
r 2 r2 r2代替 r 1 r1 r1 r 1 = 1 − r 2 r1 = 1 - r2 r1=1r2,则有:
( A − A ∗ r 2 + B ∗ r 2 ) ⋅ L = 0 ( A + ( B − A ) ∗ r 2 ) ⋅ L = 0 L ⋅ A + L ⋅ L ∗ r 2 = 0 r 2 = − ( 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) (AAr2+Br2)L=0(A+(BA)r2)L=0LA+LLr2=0r2=(LA)/(LL)

推导过程略显复杂,但是最终的公式却很简单:
{ r 2 = − ( L ⋅ A ) / ( L ⋅ L ) r 1 = 1 − r 2 Q a = A a ∗ r 1 + B a ∗ r 2 Q b = A b ∗ r 1 + B b ∗ r 2 \begin{cases} r2 = -(L · A) / (L · L)\\ r1 = 1 - r2 \\ Qa = Aa * r1 + Ba * r2 \\ Qb = Ab * r1 + Bb * r2 \end{cases} r2=(LA)/(LL)r1=1r2Qa=Aar1+Bar2Qb=Abr1+Bbr2
计算最近点的伪代码如下:


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

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


http://chatgpt.dhexx.cn/article/NRWNKA7j.shtml

相关文章

python碰撞检测算法_GJK碰撞检测算法

现实世界里我们对于是否碰撞的判断可以说极其容易而且准确,比如下图。在二进制的世界里,一切就没这么直观了。 GJK(Gilbert-Johnson-Keerthi Distance Algorithm) GJK 就是此次要实现的碰撞检测算法。如果对碰撞算法有过了解的话,大概率听过另一个碰撞检测算法 SAT(Separati…

【计算几何】凸多面体重叠判断算法:GJK 算法详解 C++代码实现二维情形的凸多边形重叠判断

文章目录 一、GJK 算法简介二、前置知识2.1 二维向量的点乘和叉乘2.2 三维向量叉乘2.3 凸多边形2.4 闵可夫斯基差2.5 单纯形2.6 Support 函数 三、GJK 算法讲解3.1 熟悉 GJK 算法流程3.1.1 多边形重叠的情形3.1.2 多边形不重叠的情形 3.2 总结 GJK 算法步骤3.3 讲解 GJK 算法细…

GJK 算法

GJK 算法(Gilbert–Johnson–Keerthi) 翻译自&#xff1a;http://www.dyn4j.org/2010/04/gjk-gilbert-johnson-keerthi/ 今天&#xff0c;我将讨论dyn4j项目随附的其他碰撞检测算法。您可以找到很多GJK文档&#xff0c;但是其中很多实际上是技术性的&#xff0c;主要是因为它…

碰撞检测算法之GJK算法

简介 参考&#xff1a; 碰撞检测算法之GJK算法 - 知乎 (zhihu.com) 运筹优化】凸多面体重叠判断算法&#xff1a;GJK 算法详解 & C代码实现二维情形的凸多边形重叠判断_c 凸多边形_WSKH0929的博客-CSDN博客 物理引擎学习03-GJK碰撞检测算法基础gjk算法游蓝海的博客-CSDN博客…

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

EPA&#xff0c;是扩展多边形算法(Epanding Polytop Algorithm) &#xff0c;用来计算两个多边形碰撞的穿透深度和方向&#xff0c;可用于将两个发生碰撞的多边形分离。本文的写作目的&#xff0c;主要是对GJK和EPA算法的理解和应用。对算法本身感兴趣的朋友&#xff0c;可以阅…

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

GJK是由Gilbert&#xff0c;Johnson&#xff0c;Keerthi 三位前辈发明的&#xff0c;用来计算两个凸多面体之间的碰撞检测&#xff0c;以及最近距离。GJK算法可以在O(MN)的时间复杂度内&#xff0c;检测出碰撞&#xff0c;算法在每次迭代的过程中&#xff0c;都会优先选择靠近原…

碰撞检测——GJK算法

目录 碰撞检测——GJK算法 1.GJK算法的原理及思想 1.1 Minkowski Sum&#xff08;明可夫斯基和&#xff09; 1.2 Simplex 1.3 support函数 1.4 构建Simplex 2. GJK算法步骤 3. GJK算法的优缺点分析 4. GJK算法与其他相关算法的比较分析 4.1 GJK算法和SAT算法的比较 …

QPM 之悬浮窗设置信息

QPM 开源地址&#xff1a;https://github.com/ZhuoKeTeam/QPM 更多实用信息&#xff1a; 手机的基本信息AndroidManifest.xml 信息App 中所有的 SharePreference 信息可配置的开关网络接口 手机基础信息 再也不用 去手机的复杂界面查看各种数据&#xff1b;再也不用 下载 辅…

QPM访谈问题

QPM&#xff1a;Quantitative Project Management&#xff0c;量化项目管理 一、访谈问题 1、请简要介绍贵司的QPM管理的管理方式&#xff1f; 依据公司整体商业目标和CMMI要求&#xff0c;制定和部署了《量化项目管理过程》和相关的指南、模板。 在项目执行中&#xff0c;…

QPM 性能监控组件——总篇

QPM &#xff08;Quality Performance Monitor&#xff09; 是一个质量性能监控组件&#xff0c;可以很方便的查看当前 App 的性能和常用数据。目前主要运行在 Android 平台上&#xff0c;通过集成 QPM 组件&#xff0c;可以在 App 中通过悬浮窗可视化相关实时数据。意在帮助广…

IDEA快捷键显示参数提示

一、IDEA显示参数的提示 1、理想状态是这样 2、某度了半天 都是让画show parameter 那个勾的&#xff0c;不是我想要的 IDEA默认的快捷键提示是 " ctrl p"&#xff0c;但是我改成eclipse的快捷键后就没有提示了 File---->settings---->keymap 需要给ecli…

Idea设置代码自动提示快捷键

使用eclipse都习惯使用快捷键ALT/ 来代码自动提示&#xff0c;后来使用IntelliJ Idea这个快捷键并不管用&#xff0c;十分不便&#xff0c;这里记录如何使更改idea代码自动提示快捷键。 哪个是代码自动提示快捷键 File–》Settings–》KeyMap(快捷键ctrlalts)进入快捷键设置界…

IDEA提示方法参数的快捷键

在写Java方法的时候有时想让软件提示一下方法的参数&#xff0c;解决方法是将鼠标放置到方法括号里&#xff0c;按下ctrlp&#xff0c;即可显示方法参数

idea 设置代码提示快捷键

Settings->Keymap 搜索框内搜索 completion Main Menu-Code-Code Completion-Basic 设置成对应的快捷键&#xff0c;如 Alt/

IDEA的使用—常用快捷键

IDEA常用快捷键 具体详解视频见于&#xff08;【零基础 快速学Java】韩顺平 零基础30天学会Java&#xff09; 文章目录 前言一、IDEA快捷键是什么&#xff1f;二、IDEA快捷键的设置 1.快捷键12.快捷键23.快捷键3总结 前言 IDEA快捷键能够提升编程速度以及防止出现书写代码时的低…

idea快捷键设置(Idea常用快捷键大全)

目录 友情提醒第一章、IDEA常用快捷键1.1&#xff09;快捷键&#xff1a;查找/提示类1.2&#xff09;快捷键&#xff1a;修改代码类1.3&#xff09;快捷键&#xff1a;光标移动类 第二章、Idea如何修改快捷键2.1&#xff09;已知快捷键&#xff0c;通过搜索快捷键查找2.2&#…

idea快捷键最全最新最好

持续更新(如果文档中没有的&#xff0c;麻烦在评论中添加) 常用快捷键 返回最顶头&#xff1a; home 返回最末尾&#xff1a;end AltInsert 可以新建类&#xff0c;文件&#xff0c;get或set方法&#xff0c;此快捷键又名创造一切 编辑区和文件区的跳转。 …

IntelliJ IDEA 设置代码提示或自动补全的快捷键

对于中国的Java开发者来说&#xff0c;可能使用Eclipse的人最多。 使用Idea的程序员也不少, 而且每个人都在鼓吹其好用之处。 试用半个月&#xff0c;感觉各有千秋&#xff0c;关键看熟练程度和配置是否好用。 自动提示快捷键 有时候希望使用自动补全&#xff0c;因为不偷懒…

【idea】idea 常用快捷键(每个都有操作演示)

【IDEA】 idea 常用快捷键&#xff08;每个都有操作演示&#xff09; IDEA 一款非常优秀的开发工具&#xff0c;本篇博客总结一些在 IDEA 中常用的快捷键&#xff0c;旨在提高开发效率&#xff0c; 下面的 “关键字” 指的是在快捷键搜索框中输入的内容&#xff0c;演示语言为…

Intellij IDEA 快捷键

官方快捷键资料&#xff1a;https://resources.jetbrains.com/storage/products/intellij-idea/docs/IntelliJIDEA_ReferenceCard.pdf 关闭SQL语法检查&#xff1a;https://blog.csdn.net/qq_35478963/article/details/76392947关闭field injection is not recommended警告&am…