GJK算法笔记

前言

参考链接:
看似简单的复杂问题,奇怪而优雅的解决方式(GJK算法) | Reducible
GJK 算法
(ps:自用笔记)
先抛出我们的问题: 计算机如何判断两个多边形相交?

从形状开始

任何多边形都可以分成凸形(convex shape)和凹形(concave shape).

凸形的性质是,在形状上取任意两点,连线总是在凸形的形状里.而对于凹形,则总是存在反例(如下图).在这里插入图片描述

处理凸形相交要比凹形简单(why?利用闵科夫斯特差的性质,见下文),因此一般会把凹形切割成若干凸形,这样判断两个多边形是否相交变成了凸形求交集问题。

在这里插入图片描述

闵科夫斯特差

把多边形看成这个形状内每一个点的集合,则如果有两个多边形相交,那我们可以找到两个点(向量),他们的差等于原点。

(ps: 如果两多边形相交,至少存在一个共同点,这个点在a集合可表示为(x1,y1),b集合可表示为(x2,y2))

在这里插入图片描述
闵科夫斯特差是两个多边形的任意两点相减,得出来的点的集合,,用公式表达: A + B = { a + b | a∈A ,b∈B}
(ps: 可以把每个点看成原点出发的向量,向量叠加)

在这里插入图片描述

闵科夫斯特差的两个性质:

  1. 取两个凸形的闵科夫斯特差,得到的形状也是凸形。
  2. 如果两个形状相交,则闵科夫斯特差必定包含原点(两个凸形必定包含一个共同点)。

利用上面的性质判断多边形相交。因此判断多边形是否相交最终变成了判断闵科夫斯特差是否存在原点。

单形

那如何检查原点在集合中呢(毕竟不可能检查集合中每个点)?在这里插入图片描述
挑选闵科夫斯特差上的点,尝试建立一个包含原点的三角形。如果找不到这样的三角形,说明两多边形不相交。构建的三角形即为单型(simplex)。

单型简单说就是用最简单的形状包围某维的某点,对于二维,三角形是最符合这个标准的形状。

在这里插入图片描述
然而并非集合中任意点构成的单形都包含原点,这引申出下一个问题: 如何更快地找到一个包围原点的单形?

支撑函数

根据凸形性质,任意一点都存在一个方向使他是此方向上最远的点,遍历所有可能方向就可以得到凸形。对于凹形则总是存在反例。

在这里插入图片描述
凸形的支撑函数为将方向向量映射到多边形上最远的点的函数,而对应的点叫做支撑点。两个凸形的支撑函数相减,得到闵科夫斯特差的支撑函数。
在这里插入图片描述
计算支撑点:

在这里插入图片描述

给定一个方向向量,求该方向上的支撑点。以上图的六边形为例。首先将顶点转换成从原点出发的向量,然后求与给定方向向量的点积,点积最大的就是支撑点。

为何计算点积?点积公式为 a·b = |a||b|cosθ 同时考虑了与给定向量的夹角和向量大小。

不同的形状的支撑点函数不同(多态),例如对于圆,支撑点为给定向量归一化后乘以圆半径。

利用支撑函数获得闵科夫斯特差的支撑点以构建单形,判断原点是否在单形中,如不存在,迭代寻找其他的支撑点构成新单行。具体操作见下。

GJK算法实现

直接上伪代码:

public Point support(Shape shape1,Shape shape2,Vector d)
{
    Point p1 = shape1.getFarthestPointInDirection(d);
    Point p2 = shape2.getFarthestPointInDirection(d);
    Point p3 = p1 - p2;
    return p3;
}

//选择一个搜索方向获得单形第一个支撑点
Vector d = Simplex.add(support(A,B,d));
d = d.negate();
while(true)
{
    //单形添加点
    simplex.add(support(A,B,d));
    //保证最后添加的点"经过"原点
    //Question1: 
    if(simplex.getLast().dot(d) <= 0)
    {
        //支撑点在闵科夫斯特差的边缘,并且当前点不经过原点,说明不包含原点
        return false;
    }
    else
    {
        //当前单形是否包含原点
        if(ContainOrigin(simplex,d))
        {
            return true;
        }
    }
}

public boolean ContainOrigin(simplex s,Vector2 d)
{
    //单形最后添加的点
    a = simplex.GetLast();
    //-A
    ao = a.negate();
    if(simplex.points.size == 3)
    {
        //2D平面,单形为三角形
        b = simplex.getB();
        c = simplex.getC();
        //边
        ab = b - a;
        ac = c - a;
        //法向量
        //Question2:
        abPerp = tripleProduct(ac,ab,ab); //矢量三重积
        acPerp = tripleProduct(ab,ac,ac);
        //Question3:
        if(abPerp.dot(ao) > 0)
        {
            //移除c点
            simplex.remove(c);
            //搜索方向改为ab法向量
            d.set(abPerp);
        }
        else if(acPerp.dot(ao) > 0)
        {
            //移除b点
            simplex.remove(b);
            d.set(acPerp);
        }
        //R5区域已经包含原点
        else
        {
            return true;
        }
    }
    else
    {
        //不构成单形
        b = simplex.getB();
        ab = b - a;
        //法向量
        abPerp = tripleProduct(ab,ao,ab);
        d.Set(abPerp);
    }
    return false;
}

QA

Q1: 如何判断退出迭代?
在这里插入图片描述
如图,E为单形当前支撑点(simplex.last()),d为当前搜索方向。

d * EO < 0,表明当前的支撑点无法进一步向原点靠近,并且当前的单形也没有包含原点,说明不相交,提前退出迭代。

Q3: 如何选择迭代方向?

假设已经构建出一个单形,如下图ABC。其中A为当前迭代得到的最后一个支撑点。
图中L1、L2分别为AB、AC的法向量。

在这里插入图片描述
取法向量作为我们的搜索方向。此时我们迭代方向有两种可能: L1或者L2。如何选择呢?

点积 a * b = |a||b|cosx,通过夹角余弦值可判断法向量是靠近原点还是远离原点。计算AO和法向量的点积。其中L2和AO的点积大于0,说明L2为接近原点的方向,选择L2作为方向向量,并移除B点(L1方向远离原点)。

当L1、L2方向的点积都为0,说明当前单形已经包含原点,即可证明相交。

(ps: 为什么不考虑BC法向量取负作为搜索方向?这个问题在看别的帖子的时候感觉回答的不是很清楚,因此我尝试自己解释一下。如上图中,取BC的法向量作为搜索向量d,获得支撑点A。此时如果取-d,则获取的新的支撑点远离原点,构成的单形必定不包含原点)

Q2: 解释矢量三重积:
利用矢量三重积计算法向量:
在这里插入图片描述
矢量三重积公式为(A x B)x C = B(C.dot(A))– A(C.dot(B))

GJK算法(Gilbert-Johnson-Keerthi算法)是一种用于求解凸体相交的算法,其用途广泛应用在物理引擎、碰撞检测和计算机图形学等领域。 GJK算法的基本思想是通过迭代逼近的方式,在高维空间中找到两个凸体是否相交的最近点对,从而确定它们是否相交。具体来说,GJK算法分为三个主要步骤: 1. 初始化:选择两个初始点,一个位于第一个凸体上,一个位于第二个凸体上。这两个点可以是凸体的顶点、边界上的任意一点。 2. 迭代逼近:根据Minkowski差集(两个凸体的差集)形成的新凸体,找到离原点最近的点。该最近点必然位于Minkowski差集的边界上,可以通过求解凸包或者线段相交等方法来寻找。 3. 更新凸体:如果最近点距离原点足够小,说明两个凸体相交;否则,将最近点加入到Minkowski差集,进行下一轮迭代逼近。 由于GJK算法通过在高维空间中进行求解,虽然只需要两个凸体的形状信息,但能够得到相交的最近点对,进而支持接触深度、碰撞法向量等额外的信息。此外,GJK算法具有高效、可扩展性好等优点,因此被广泛应用于各种实时计算几何问题中。 总之,GJK算法是一种高效的求解凸体相交的算法,通过迭代逼近的方式找到相交的最近点对,其具有应用广泛的优势,可用于物理引擎、碰撞检测和计算机图形学等领域。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值