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

本文介绍了EPA算法,用于计算两个多边形碰撞后的穿透深度和方向。EPA从GJK算法得到的单形体开始,通过不断扩展寻找差集最近边。算法包括构造边集、查找最近边、插入新support点等步骤,解决浮点数误差问题。EPA适用于碰撞情况,而GJK用于无碰撞的最近距离计算。

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

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

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

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) = Q
     e.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

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

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值