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

本文详细介绍了GJK算法如何用于计算两个不相交多边形之间的最近距离和最近点。通过理解GJK的基本原理,算法伪代码以及关键点,可以有效地找到多边形上的最近边并计算距离。文中还提供了计算最近距离的优化技巧,避免了开方运算,并给出了计算最近点的伪代码。此外,作者提供了Unity3D的Demo工程以供实践。
摘要由CSDN通过智能技术生成

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

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

在这里插入图片描述

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
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值