用SAT查找接触点


12

分离轴定理(SAT)使确定最小平移向量(即可以分离两个碰撞对象的最短向量)变得简单。但是,我需要的是沿着穿透物体正在移动的向量(即接触点)将物体分开的向量。

我画了一幅画以帮助澄清。有一个框,从前面的位置移动到后面的位置。在其后位置,它与灰色多边形相交。SAT可以轻松返回MTV,这是红色矢量。我正在寻找计算蓝色向量。

SAT图

我当前的解决方案在前后位置之间执行二进制搜索,直到已知蓝色矢量的长度达到某个阈值为止。它可以工作,但是这是一个非常昂贵的计算,因为需要在每个循环中重新计算形状之间的冲突。

有没有更简单和/或更有效的方法来找到接触点矢量?


1
您是否习惯使用SAT?MPR(Minkowski门户优化)之类的算法可以直接找到接触流形。使用SAT和GJK,您需要一种单独的算法来计算接触点。
肖恩·米德迪奇

Answers:


6

如果您在构造对象时首先要移动对象,然后进行碰撞测试,然后回退直到您离开对象,则要说的是相当困难的。最好将其视为动态相交测试:将移动对象与固定对象进行比较。

幸运的是,分离轴测试可以为您提供帮助!这是该算法的描述,由Ron Levine提供

该算法是这样的。您可以使用两个凸体的相对速度矢量。突出所述两个机构和相对速度矢量到在一个特定的分离轴线的 ₀给出了两个1-d的时间间隔和一个1-d速度,使得它很容易告诉两个区间是否相交,并且如果否,是否他们正在分开或一起移动。如果它们被分离并在任何分离轴上移动(或者实际上在任何轴上移动),那么您将知道将来不会发生碰撞。如果在任何分离轴上,两个投影间隔在t处相交₀或分开并在一起移动,则很容易(通过两个简单的1D线性表达式)计算两个间隔首先相交的最早的将来时间,并(假定继续直线运动)计算两个间隔的最近的将来时间间隔将最后相交并开始分开。(如果它们在交叉 ₀然后最早未来相交时间 ₀)。最多对所有分离轴执行此操作。如果最早的未来相交时间的所有轴上的最大值小于最新的未来相交时间的所有轴上的最小值,则该最大最早的未来相交时间是两个3D多面体第一次碰撞的确切时间,否则将来不会发生冲突。

换句话说,您遍历了静态分离轴测试中通常需要的所有轴。如果没有发现重叠,您可以继续前进并检查移动物体的投影速度,而不是尽早进行。如果它正在远离静态对象,那么您就要提前。否则,您可以相当轻松地解决最早和最近的联系时间(这是一个1D间隔移向另一个1D间隔)。如果对所有轴都这样做,并保持最早的相交时间的最大值和最短的最新相交时间的最小值,则您知道移动的对象是否会击中静态对象以及何时击中静态对象。因此,您可以将移动的对象精确地推进到它将撞击静态对象的位置。

这是该算法的一些粗略且完全未经验证的伪代码:

t_min := +∞
t_max := -∞
foreach axis in potential_separating_axes
    a_min := +∞
    a_max := -∞
    foreach vertex in a.vertices
        a_min = min(a_min, vertex · axis)
        a_max = max(a_max, vertex · axis)
    b_min := +∞
    b_max := -∞
    foreach vertex in b.vertices
        b_min = min(b_min, vertex · axis)
        b_max = max(b_max, vertex · axis)
    v := b.velocity · axis
    if v > 0 then
        if a_max < b_min then
            return no_intersection
        else if (a_min < b_min < a_max) or (b_min < a_min < b_max) then
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, 0)
        else
            t_min = min(t_min, (a_max - b_min) / v)
            t_max = max(t_max, (a_min - b_max) / v)
    else if v < 0 then
        // repeat the above case with a and b swapped
    else if v = 0 then
        if a_min < b_max and b_min < a_max then
            t_min = min(t_min, 0)
            t_max = max(t_max, 0)
        else
            return no_intersection
if t_max < t_min then
    // advance b by b.velocity * t_max
    return intersection
else
    return no_intersection

这是Gamasutra的文章,讨论了针对一些不同的原始测试实现的方法。请注意,就像SAT一样,这需要凸对象。

而且,这比简单的分离轴测试还要复杂。尝试之前,请务必确定需要它。大量游戏只是沿着最小平移矢量将对象彼此推开,因为它们在任何给定帧上都不会相互渗透太远,并且在视觉上几乎看不到。


2
这一切都很酷,但是并没有直接回答有关计算接触歧管的问题。另外,如果我理解正确,则此答案仅适用于线速度,因此不支持旋转对象。不知道提问者是否想要这个。
肖恩·米德迪奇

1
@seanmiddleditch是的,它忽略了框架上的旋转。您必须在开始时瞬时旋转。但是,除了保守的进步外,我所知道的任何方法都无法准确地解决轮换问题。但是,如果不旋转,则可以更好地估计接触点。
John Calsbeek 2012年

2

您要使用多边形裁剪。最好用我没有的图片来解释,但是这个家伙有,所以我让他解释。

http://www.codezealot.org/archives/394

接触歧管将在一个物体上返回一个对碰撞最“负责”的点,而不是直接碰撞点。但是,您实际上并不需要该直接碰撞点。您可以简单地使用已经具有的穿透深度和法向力将对象分开,并使用接触式流形施加其他物理影响(例如,使框滚动/向下倾斜)。

请注意,您的图片说明了一个小问题:在任何物理模拟中都不会找到您要的蓝色矢量上的点,因为这实际上并不是盒子会碰到的地方。当盒子的一小部分穿透时,盒子的左下角就会撞到斜坡的上方。

穿透深度将相对较小,只需沿穿透法线将其从坡度中推出,即可使该盒足够接近“正确”位置,在实践中几乎看不到它,特别是如果该盒要弹跳,翻滚时,或者之后再滑动。


您是否知道有一种使用SAT计算“蓝色矢量”(将物体沿着速度矢量从形状中推出所需的方法)的方法吗?
塔拉2015年

@Dudeson:不使用SAT,不。那不是SAT所做的。SAT为您提供最小穿透深度的边缘,而不是第一个接触边缘。我认为,您必须使用扫掠形状碰撞检测来完成您要问的事情。
肖恩·米德迪奇

我知道SAT会做什么。我以前已经实现了。但是,如果我仅使用SAT的输出来计算第一个接触边,就会遇到一个可以解决的问题。另请参阅“ someguy”的答案。这表明有可能,但并不能很好地解释它。
塔拉2015年

@Dudeson:最小穿透的边缘/轴不一定是初次接触的边缘,因此我仍然看不到SAT在这里有什么帮助。我绝不是这个主题的专家,所以我承认我可能是错的。:)
肖恩·米德迪奇

究竟。这就是为什么我不确定这是否可能的原因。但是,这意味着,someguy的答案是错误的。但是仍然感谢您的帮助!:D
塔拉

0

只需将MAT向量投影到方向向量上即可。可以将生成的矢量添加到方向矢量以补偿穿透。进行投影时,就像在执行SAT时在Axis上所做的一样。这将对象精确设置在它与另一个对象接触的位置上。添加一个小的epsilon来解决浮点问题。


1
“ MAT向量”?你是说“ MTV”吗?
塔拉2015年

0

我的答案有两个警告,我将首先避开:它仅处理非旋转边界框。它假定您正在尝试处理隧道问题,即由对象高速移动引起的问题。

一旦确定了MTV,便知道需要测试的边缘/表面法线。您还知道互穿对象的线速度矢量。

一旦你已经确定在一些点在帧期间,发生了一个路口,就可以执行二进制半步操作,基于以下出发点:确定该帧中第一穿透了顶点:

vec3 vertex;
float mindot = FLT_MAX;
for ( vert : vertices )
{
    if (dot(vert, MTV) < mindot)
    {
         mindot = dot(vert, MTV);
         vertex = vert;
    }
}

一旦确定了顶点,二分之一半步就变得便宜得多:

//mindistance is the where the reference edge/plane intersects it's own normal. 
//The max dot product of all vertices in B along the MTV will get you this value.
halfstep = 1.0f;
vec3 cp = vertex;
vec3 v = A.velocity*framedurationSeconds;
float errorThreshold = 0.01f; //choose meaningful value here
//alternatively, set the while condition to be while halfstep > some minimum value
while (abs(dot(cp,normal)) > errorThreshold)
{            
    halfstep*=0.5f;
    if (dot(cp,normal) < mindistance) //cp is inside the object, move backward
    {
        cp += v*(-1*halfstep);
    }
    else if ( dot(cp,normal) > mindistance) //cp is outside, move it forward
    {
        cp += v*(halfstep);
    }
}

return cp;

这是相当准确的,但在单个情况下将仅提供单个碰撞点。

事实是,通常可以提前告知一个对象是否每帧移动得足够快,从而能够像这样进行隧穿,所以最好的建议是沿着速度识别前导顶点并沿着速度矢量进行射线测试。在旋转物体的情况下,您必须执行某种二进制的半步跟踪,以声明正确的接触点。

但是,在大多数情况下,可以安全地假定场景中的大多数对象移动得不够快,无法在单个帧中穿透那么远,因此不需要半步步进,并且离散碰撞检测就足够了。移动速度太快以至于看不见的高速物体(如子弹)可以被追踪到接触点。

有趣的是,这种半步方法还可以为您提供(几乎)对象在帧中发生的确切时间:

float collisionTime = frametimeSeconds * halfstep;

如果您要进行某种物理冲突解决,则可以通过以下方法校正A的位置:

v - (v*halfstep)

那么您就可以从那里正常进行物理学了。不利的一面是,如果对象运动得相当快,您将看到它沿其速度矢量向后移动。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.