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

article/2025/9/25 1:47:54

EPA,是扩展多边形算法(Epanding Polytop Algorithm) ,用来计算两个多边形碰撞的穿透深度和方向,可用于将两个发生碰撞的多边形分离。本文的写作目的,主要是对GJK和EPA算法的理解和应用。对算法本身感兴趣的朋友,可以阅读源论文的文献。本系列GJK算法文章共三篇,本篇是第三篇,强烈建议从第一篇开始看:

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

本文作者游蓝海。原创不易,未经许可,禁止任何形式的转载。
在这里插入图片描述

1. 基本原理

当碰撞发生时,原点到最近的闵可夫斯基差集多边形的边(下文称作“差集最近边”)的距离,就是穿透深度,原点到该边的垂直向量就是穿透向量的方向。因此,核心问题就转换成了,如何求得距离原点最近的差集边。

当碰撞检测完毕后,我们会得到一个单形体(Simplex),该单形体可能包含两个或三个support点,将这些support点首尾相连构成封闭的多边形。EPA算法每次迭代的时候,从这个多边形中选择一条最近的边,沿着该边的法线方向(原点到边的垂线方向),向外扩展。直到某条边无法向外扩展时,则该边就是差集最近边。

可以看到,EPA算法和GJK求最近距离算法很相似,都是在找一个差集最近边。不过EPA用于发生碰撞的情况下,GJK求最近距离用于没有发生碰撞的情况下。理论上EPA也可以用于求两个多边形的最近边,只不过EPA收敛的速度没有GJK算法快。

2. 算法解析

EPA算法的前提是,两个多边形发生了碰撞。因此,我们要先使用GJK算法检测出碰撞,然后将得到的单形体,作为EPA算法开始的条件。

2.1 算法伪代码

Vector2 EPA(Simplex simplex)
{// 构造一个首尾相连的多边形simplexEdge.initEdges(simplex);while(true){// 找到距离原点最近的边Edge e = simplexEdge.findClosestEdge();// 沿着边的法线方向,尝试找一个新的support点Vector2 point = support(e.normal);// 无法找到能够跨越该边的support点了。也就是说,该边就是差集最近边float distance = Vector2.Dot(point, e.normal);if (distance - e.distance <= 0){// 返回穿透向量return e.normal * distance;}// 将新的support点插入到多边形中。// 也就是将边e从support点位置分割成两条新的边,并替换到多边形集合中。simplexEdge.insertEdgePoint(e, point);}
}

算法步骤描述:

  1. 构造一个首尾相连的多边形,也就是得到边的集合;
  2. EPA迭代开始;
  3. 找到一个距离原点最近的边;
  4. 沿着该边的法线方向,尝试查找一个support点;
  5. 如果无法找到更远的support点,则说明该边就是差集最近边,算法结束;
  6. 将新的support点插入多边形中,相当于多边形向外扩展了;
  7. 跳转到步骤2。

计算步骤的分解动图:
在这里插入图片描述

2.2 构造边集

我们需要得到的是单形体多边形的边的集合,只用将support点首尾即可。为了方便后续计算,我们把每条边的法线和到原点的距离也计算出来。法线就是原点到边的垂线,取向外的方向。

有一个特殊的情况,如果GJK算法结束时,原点恰好位于单形体的某条边上。此时单形体中将会只有两个support点,且这两点的连线是经过原点的。这种情况下,无法用计算垂足的方法计算垂线,需要用到数学的方法:向量(x, y)的垂直向量为: (y, -x),或者(-y, x)。随便用哪一个都行,因为两个support点首尾相连,会构成两条方向相反的边,恰好用数学方法求得的垂直向量方向也自然是相反的,EPA算法将会沿着两个相反的方向向外扩展。

void initEdges(Simplex simplex)
{int n = simplex.count();for (int i = 0; i < n; ++i){int iNext = (i + 1) % n;Edge edge = createEdge(simplex.get(i), simplex.get(iNext));edge.index = i;edges.Add(edge);}
}Edge createEdge(Vector2 a, Vector2 b)
{Edge e = new Edge();e.a = a;e.b = b;// 计算垂足Q。则法线向量为 OQ = Q - (0, 0) = Qe.normal = GJKTool.getPerpendicularToOrigin(a, b);e.distance = e.normal.magnitude;// 单位化边if (e.distance > float.Epsilon){e.normal *= 1.0f / e.distance;}else{// 如果距离原点太近,用数学的方法来得到直线的垂线// 方向可以随便取,刚好另外一边是反着来的Vector2 v = a - b;v.Normalize();e.normal = new Vector2(-v.y, v.x);}return e;}

2.3 查找最近边

找到距离原点最近的边即可:

Edge findClosestEdge()
{float minDistance = float.MaxValue;Edge ret = null;foreach (var e in edges){if (e.distance < minDistance){ret = e;minDistance = e.distance;}}return ret;
}

2.4 插入新的support点

将边e断开,e原来的两个端点分别与support点进行连接,然后重新插入到边的集合中。

void insertEdgePoint(Edge e, Vector2 point)
{Edge e1 = createEdge(e.a, point);// 覆盖掉原来e的位置edges[e.index] = e1;Edge e2 = createEdge(point, e.b);// 插入新的边edges.Insert(e.index + 1, e2);// 重新分配边的索引updateEdgeIndex();
}

2.5 浮点数误差问题

(增加于2021/08/22) 当GJK算法结束后,如果某个单形体的边经过了原点或者离原点非常近,则这条边的法线方向将会无法正确的计算出来,会导致后续的EPA扩张方向出现混乱,无法正确的计算出穿透向量。因此,最稳妥的方法是,进入EPA算法的时候,仅保留单形体的一条边,也就是只留下2个顶点。则EPA初始的边集只有两个有向边 ab和ba,而且两者的法线方向刚好相反,后续会沿着相反的方向进行扩张,不会产生交叉混乱的情况。

// 仅保留距离原点最近的一条边,避免浮点数误差引起原点落在了边上,造成无法计算出该边的法线方向
if (simplex.count() > 2)
{findNextDirection(); // 会自动删除一个离远点最远的点
}
...
edges.Add(createInitEdge(simplex.get(0), simplex.get(1)));
edges.Add(createInitEdge(simplex.get(1), simplex.get(0)));

3. 小结

EPA算法计算穿透向量,本质上是求得差集最近边。EPA从GJK算法结束后开始,不断的扩展单形体,直到达到边界。

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

4. 参考

  • GJK算法论文: https://ieeexplore.ieee.org/document/2083?arnumber=2083
  • EPA (Expanding Polytope Algorithm): http://www.dyn4j.org/2010/05/epa-expanding-polytope-algorithm

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


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

相关文章

物理引擎学习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…

IDEA常用快捷键及修改快捷键

IDEA常用快捷键 快捷键功能AltEnter导入包&#xff0c;自动修正代码CtrlY删除光标所在行CtrlD复制光标所在行的内容&#xff0c;插入光标位置下面CtrlAltL格式化代码Ctrl/单行注释CtrlShift/选中代码注释&#xff0c;多行注释&#xff0c;再按取消注释AltIns自动生成代码&…

IDEA快捷键大全及修改IDEA快捷键

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

基础开始IntelliJ IDEA 设置代码提示或自动补全的快捷键 (附IntelliJ IDEA常用快捷键)

修改方法如下&#xff1a; 点击 文件菜单(File) –> 点击 设置(Settings… CtrlAltS), –> 打开设置对话框。 在左侧的导航框中点击 KeyMap。 接着在右边的树型框中选择 Main menu –> Code –> Completion. 接着需要做两件事&#xff1a; 1. 移除原来的Cycle Ex…

Idea智能提示快捷键

修改智能提示快捷键&#xff1a; 原文地址&#xff1a;https://blog.csdn.net/gaoqiao1988/article/details/73299670 Idea(File –>Settings)(CtrlAltS), –> 打开设置对话框。 在左侧的导航框中点击 KeyMap。 接着在右边的树型框中选择 Main menu –> Code –>…

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

前言&#xff1a;使用 eclipse 都习惯使用快捷键ALT/ 来代码自动提示&#xff0c;后来使用IntelliJ Idea这个快捷键并不管用&#xff0c;十分不便&#xff0c;这里记录如何使更改idea代码自动提示快捷键。 打开Settings设置 [ 快捷键 Ctrl Alt S ] File ––> Settings –…