快速移动物体的六角形碰撞检测?


39

对象具有位置和速度矢量。通常仅使用该位置来检查两个物体是否发生碰撞,这对于移动速度非常快的物体是有问题的,因为可能发生物体移动得如此之快,以至于在第一次碰撞检查中它位于第一个物体的前面,而在第二个碰撞检查中位于它的后面第二次碰撞检查。

BoundingBox碰撞失败

现在,还有基于行的碰撞检查,其中您仅检查每个对象的运动矢量是否与另一个对象的边界框相交。这可以看作是一点的扩展。这仅在快速移动的物体很小的情况下才有效。

六角碰撞胜利

所以我的想法是,为什么不扩展矩形而不是扩展点?这将产生一个六边形。

现在,到目前为止一切顺利。但是我实际上如何检查两个这种六角形是否相交?请注意,这些是非常特殊的六角形。

六角规格

奖励问题:是否可以计算出碰撞发生的确切位置(或确切地说,经过多少时间)?这对于检测实际发生的情况(例如在何处以及具有多少功率)以及模拟它们在碰撞到帧结束之间的时间中如何移动非常有用。


for(A中的线)for(B中的线)如果(线交叉)碰撞-除非B中的A或B中的B不覆盖A。嗯 =)
Jari Komppa,

4
您致力于箱子吗?您绘制的框可以用圆圈表示,但精度损失极小,但碰撞算法相对容易。搜索后掠圆碰撞检测。如果您的长宽比偏离1,则吸引力会降低。
史蒂夫·H

@SteveH我正在寻找最灵活的解决方案,因此长/宽比有点大。
API-Beast

1
您必须意识到,仅仅因为六边形相交并不意味着发生碰撞。即使您可以毫无疑问地告诉他们它们是否相交,您仍然需要做一些工作来确定是否发生碰撞,以及明显发生碰撞的位置和时间。因此,您现在还不能跳到奖金问题。
jrsala 2013年

2
我以前没有尝试过,但是似乎可以将2d的运动看作是3d空间中的体积,而不是2d空间中的六边形,其中一个轴是时间。然后,您将两个具有(x,y,t)坐标的3d多面体相交。如果两个实体对象相交,则您要找到最小的t值。您可以通过将B的所有坐标都转换为A的参考系来简化一点。我还没有实现,但这就是我要开始的地方。
amitp

Answers:


34

该解决方案实际上比预期的要简单。诀窍是在六角技术之前使用Minkowski减法

这是矩形A和B,速度分别为vAvB。请注意,vAvB没有实际的速度,他们是距离一帧期间行进。

第1步

现在,将矩形B替换为点P,将矩形A替换为矩形C = A +(-B),其维数为A和B的总和。当且仅当原始两个矩形之间发生碰撞时:

第2步

但是,如果矩形C沿矢量移动vA,而点P沿矢量移动vB,则参考框架的简单变化就告诉我们,这与矩形C静止不动,点P沿矢量移动一样vB-vA

第三步

然后,您可以使用简单的框段相交公式来判断新参考系中的碰撞发生位置。

最后一步是移回正确的参考系。只要除以点行进,直到向量的长度圆圈交点的距离vB-vA,你会得到一个值s,使得0 < s < 1。碰撞发生在时间s * T哪里T是你的帧的持续时间。

madshogo的评论
与Beast先生自己的回答相比,此技术的一个巨大优势是,如果不旋转,则可以为所有后续时间步计算一次“ Minkowski减法” A +(-B)

因此,所有这一切都需要时间的唯一算法(Minkowski和,复杂度O(mn),其中mA中的顶点数,nB中的顶点数)只能有效地使用一次,从而有效地使碰撞检测成为常数,时间问题!

以后,一旦确定AB处于场景的不同部分(四叉树?)并且不再发生碰撞,就可以将总和丢弃。

相比之下,Beast先生的方法在每个时间步都需要进行大量计算。

同样,对于轴对齐的矩形,与通过逐个顶点实际计算所有总和相比,可以更简单地计算A +(-B)。只需在A的高度上加上B的高度,然后在其宽度上增加B的宽度(每边的一半),即可展开A。

但是,只有当AB都不旋转并且都为凸形时,所有这些操作才有效。如果存在旋转,或者使用凹入形状,则必须使用扫掠的体积/区域。
评论结束


4
看起来这是一种非常有趣的方法,但是,我还不是100%掌握它,当对象非常小并且在两行之间移动时会发生什么?i.imgur.com/hRolvAF.png
API-Beast

-1:此方法绝对不能确保发生碰撞。在线段和拉伸体积相交的情况下,它仅使您可以确定不会发生这种情况。但是它们完全有可能相交,而不会发生碰撞。出现问题的是“现在您可以使用简单的线段与线段的交点来确定发生碰撞的位置”部分。
jrsala

2
@madshogo你是对的。我认为与对象大小相比,时间步长足够小,这将不是问题,但是在一般情况下,它当然不是很可靠。我将研究修复它。
sam hocevar

@SamHocevar如果您可以修改答案,那就太好了。
API-Beast

1
@LuisAlves是和不是…所有逻辑都起作用,但是随着时间的推移,您将必须替换vB-vAg(t)-f(t)where fgA和B的位置。由于这不再是一条直线,因此您必须解决一个框-参数曲线相交的问题。
sam hocevar,2014年

17

首先,对于轴对齐的矩形,Kevin Reid的答案是最好的,而算法是最快的。

其次,对于简单形状,请使用相对速度(如下所示)和分离轴定理进行碰撞检测。它告诉您在直线运动(无旋转)的情况下是否发生碰撞。而且,如果有轮换,那么您需要一个很小的时间步来使其精确。现在,回答问题:


一般情况下如何判断两个凸形是否相交?

我将为您提供一种适用于所有凸形而不只是六边形的算法。

假设XY是两个凸形。它们相交当且仅当他们有一个共同的点,即有一点X X和点ÿ∈Ÿ使得X = Y。如果将空间视为向量空间,则相当于说x-y = 0。现在我们开始从事Minkowski的业务:

Minkowski求和Xÿ是一组所有的X + YX∈Xÿ∈ÿ


X和Y的示例


X,Y及其Minkowski和X + Y

假设(-Y)是y∈Y 的所有-y集合,则给定上一段,当且仅当X +(-Y)包含0(即原点)时XY相交

旁注:为什么我写X +(-Y 而不是X-Y?好了,因为在数学中,有一个名为的闵可夫斯基差的操作一个它有时书面说明X - Y却一无所有做的集合中的所有的说明X - YX∈XY ^∈ÿ(真正的闵可夫斯基区别要复杂一些)。

因此,我们想计算X-Y的Minkowski和,并确定它是否包含原点。与任何其他点相比,原点都不是特殊的,因此要确定原点是否在某个域内,我们使用一种算法,该算法可以告诉我们任何给定点是否属于该域。

XY的Minkowski总和具有很酷的性质,即如果XY是凸的,则X + Y也是。与确定一个点是否属于凸集相比,确定该点是否属于凸集容易得多。

我们不可能计算所有的说明X - YX∈XY ^∈ÿ因为有这样的点的无限XŸ,所以希望,因为XÿX + Y凹凸有致,我们可以只使用定义形状XY的“最外”点,它们是它们的顶点,我们将获得X + Y的最外点,以及更多一些点。

这些附加点被X + Y的最外面的点“包围”,因此它们对定义新获得的凸形没有帮助。我们说他们没有定义点集的“ 凸包 ”。因此,我们要做的是摆脱它们,为最终的算法做准备,该算法告诉我们原点是否在凸包内。


X + Y的凸包。我们删除了“内部”顶点。

因此,我们得到

第一种天真的算法

boolean intersect(Shape X, Shape Y) {

  SetOfVertices minkowski = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      minkowski.addVertice(x-y);
    }
  }
  return contains(convexHull(minkowski), Vector2D(0,0));

}

循环显然具有复杂度O(mn),其中mn是每种形状的顶点数。该minkoswki集合最多包含mn个元素。该convexHull算法的复杂度取决于您使用的算法,您可以针对O(k log(k)),其中k是点集的大小,因此在本例中,我们得到O(mn log(mn) )。该contains算法的复杂度与凸包的边(在2D中)或面(在3D中)的数量成线性关系,因此它确实取决于您的起始形状,但不会大于O(mn)

我会让你用谷歌搜索contains凸形算法,这是一种很常见的算法。如果有时间,我可以把它放在这里。


但这是我们正在进行的碰撞检测,因此我们可以对其进行很多优化

我们最初有两个物体AB在一个时间步dt内不旋转地移动(根据我的观察照片可以看出)。我们叫v 一个v 各自的速度,这对我们的持续时间时间步长期间是不变的DT。我们得到以下内容:

而且,正如您在图片中指出的那样,这些物体在移动时确实会扫过区域(或3D体积):

在时间步长之后,它们最终分别为A'B'

要在这里应用我们的幼稚算法,我们只需要计算扫掠的体积即可。但是我们没有这样做。

B的参考系中,B不移动(du!)。和具有一定的速度 相对于乙您通过计算得到v - v (可以做相反的,计算的相对速度在参考帧)。

相对运动

从左到右:基本参考系中的速度;相对速度;计算相对速度。

通过将B视为在其自己的参考系中不动的,您只需计算Adt期间以其相对速度v A -v B移动时扫过的体积

这减少了Minkowski和计算中使用的顶点数量(有时会大大减少)。

另一个可能的优化是在计算被一个物体扫过的体积时,假设为A。您不必平移组成A的所有顶点。只有那些属于边(在3D中为面)的顶点外部法线“面”的方向扫掠。当然,当您计算正方形的扫掠面积时,您已经注意到了。您可以使用法线的点积与扫掠方向(必须为正)来判断法线是否朝向扫掠方向。

最后的优化与您关于交叉路口的问题无关,在我们的案例中非常有用。它使用了我们提到的那些相对速度和所谓的分离轴方法。您当然已经知道了。

假设你知道半径一个相对于它们的质心(即,质量中心,并从它的顶点最远的距离),就像这样:

仅当A的边界圆与B的边界圆匹配时,才可能发生碰撞。我们在这里看到它不会,并且告诉计算机的方法是如下图所示计算C BI的距离,并确保它大于AB的半径之和。如果更大,则不会发生碰撞。如果较小,则发生碰撞。

这对于较长的形状效果不佳,但是在正方形或其他此类形状的情况下,排除碰撞是一种很好的启发式方法。

应用于B的分离轴定理和被A扫过的体积确实会告诉您是否发生碰撞。关联算法的复杂度与每个凸形的顶点数量的总和成线性关系,但是当实际处理碰撞时,它的魔力就不那么大了。

我们的新算法更好,它使用相交来帮助检测碰撞,但仍不如分离轴定理有效地判断碰撞是否发生

boolean mayCollide(Body A, Body B) {

  Vector2D relativeVelocity = A.velocity - B.velocity;
  if (radiiHeuristic(A, B, relativeVelocity)) {
    return false; // there is a separating axis between them
  }

  Volume sweptA = sweptVolume(A, relativeVelocity);
  return contains(convexHull(minkowskiMinus(sweptA, B)), Vector2D(0,0));

}

boolean radiiHeuristic(A, B, relativeVelocity)) {
  // the code here
}

Volume convexHull(SetOfVertices s) {
  // the code here
}

boolean contains(Volume v, Vector2D p) {
  // the code here
}

SetOfVertices minkowskiMinus(Body X, Body Y) {

  SetOfVertices result = new SetOfVertices();
  for (Vertice x in X) {
    for (Vertice y in Y) {
      result.addVertice(x-y);
    }
  }
  return result;

}

2

我认为使用“六边形”并没有帮助。这是获取与轴对齐的矩形的精确碰撞的方法的示意图:

当且仅当两个轴对齐的矩形的X坐标范围重叠且它们的Y坐标范围重叠时,它们才重叠。(这可以看作是分隔轴定理的一种特殊情况。)也就是说,如果将矩形投影到X轴和Y轴上,则可以将问题简化为两个线与线的交点。

计算一个轴上的两条线相交的时间间隔(例如,它在时间开始(物体的当前间隔/物体的相对接近速度)开始),对另一根轴执行相同的操作。如果这些时间间隔重叠,则重叠内的最早时间就是碰撞时间。


3
你忘了草图。
MichaelHouse

2
@ Byte56不,我的意思是它是算法的草图,甚至不是伪代码。
凯文·里德

哦,我懂了。我的错。
MichaelHouse

这实际上是最简单的方法。我添加了相应的代码来实现它。
Pasha

1

我认为没有简单的方法可以计算出多边形比矩形多的多边形的碰撞。我将其分解为原始形状,例如线条和正方形:

function objectsWillCollide(object1,object2) {
    var lineA, lineB, lineC, lineD;
    //get projected paths of objects and store them in the 'line' variables

    var AC = lineCollision(lineA,lineC);
    var AD = lineCollision(lineA,lineD);
    var BC = lineCollision(lineB,lineC);
    var BD = lineCollision(lineB,lineD);
    var objectToObjectCollision = rectangleCollision(object1.getRectangle(), object2.getRectangle());

    return (AC || AD || BC || BD || objectToObjectCollision);
}

对象的路径和最终状态的图示

请注意,我如何忽略每个对象的开始状态,因为在上一次计算期间应该检查该状态。


3
问题在于,如果对象的大小相差很大,则较小的对象可以在较大对象的路径内移动而不会触发碰撞。
API-Beast

0

分离轴定理

分离轴定理说:“如果我们找到两个凸形不相交的轴,则这两个形不相交”或对于IT部门更可行:

“两个凸形只有在所有可能的轴上相交才相交。”

对于轴对齐的矩形,恰好有2个可能的轴:x和y。但是,该定理不仅限于矩形,它可以通过将形状可能相交的其他轴相加而应用于任何凸形。有关该主题的更多详细信息,请查看N开发人员的本教程:http : //www.metanetsoftware.com/technique/tutorialA.html#section1

实现它看起来像这样:

axes = [... possible axes ...];
collision = true;
for every index i of axes
{
  range1[i] = shape1.getRangeOnAxis(axes[i]);
  range2[i] = shape2.getRangeOnAxis(axes[i]);
  rangeIntersection[i] = range1[i].intersectionWith(range2[i]);
  if(rangeIntersection[i].length() <= 0)
  {
    collision = false;
    break;
  }
}

轴可以表示为归一化向量。

范围是一维线。开始应设置为最小投影点,结束应设置为最大投影点。

将其应用于“扫过的”矩形

问题中的六边形是通过“扫掠”对象的AABB产生的。扫描将任意一种可能的碰撞轴恰好添加到任何形状:运动矢量。

shape1 = sweep(originalShape1, movementVectorOfShape1);
shape2 = sweep(originalShape2, movementVectorOfShape2);

axes[0] = vector2f(1.0, 0.0); // X-Axis
axes[1] = vector2f(0.0, 1.0); // Y-Axis
axes[2] = movementVectorOfShape1.normalized();
axes[3] = movementVectorOfShape2.normalized();

到目前为止,到目前为止,我们已经可以检查两个六角形是否相交了。但它变得更好。

此解决方案将适用于任何凸形(例如三角形)和任何已扫过的凸形(例如已扫过的八边形)。但是,形状越复杂,效果越差。


奖励:魔术发生的地方。

如我所说,唯一的附加轴是运动矢量。运动是时间乘以速度,因此从某种意义上说,它们不仅仅是空轴,还是时空轴。

这意味着我们可以从这两个轴得出碰撞发生的时间。为此,我们需要找到运动轴上两个交点之间的交点。在执行此操作之前,我们需要将两个范围都进行归一化,以便我们可以实际比较它们。

shapeRange1 = originalShape1.getRangeOnAxis(axes[2]);
shapeRange2 = originalShape2.getRangeOnAxis(axes[3]);
// Project them on a scale from 0-1 so we can compare the time ranges
timeFrame1 = (rangeIntersection[2] - shapeRange1.center())/movementVectorOfShape1.project(axes[2]);
timeFrame2 = (rangeIntersection[3] - shapeRange2.center())/movementVectorOfShape2.project(axes[3]);
timeIntersection = timeFrame1.intersectionWith(timeFrame2);

当我问这个问题时,我已经接受了一种折衷方案,即这种方法会出现一些罕见的误报。但是我错了,通过检查这个时间交叉点,我们可以测试碰撞是否“实际上”发生了,我们可以用它来排除那些误报:

if(collision)
{
  [... timeIntersection = see above ...]
  if(timeIntersection.length() <= 0)
    collision = false;
  else
    collisionTime = timeIntersection.start; // 0: Start of the frame, 1: End of the frame
}

如果您在代码示例中发现任何错误,请告诉我,我尚未实现它,因此无法对其进行测试。


1
恭喜您找到解决方案!但是正如我之前所说:仅仅因为六边形相交并不意味着会有碰撞。您可以使用您的方法来计算所需的所有碰撞时间,如果没有碰撞,则它不是很有用。其次,您可以使用相对速度,以便只计算1个扫掠体积,并简化使用SAT时的计算。最后,我对“交点时间”技巧的工作原理只有一个粗略的了解,因为也许您的索引混杂在一起,就像shapeRange1 == shapeRange2您的代码一样,不是吗?
jrsala 2013年

@madshogo现在应该更有意义。
API-Beast

我仍然不了解范围归一化的工作原理,但是我想那是因为我需要一张照片。我希望这个对你有用。
jrsala 2013年

-2

只要两个扫掠区域都是封闭的(边缘线形成的边界中没有间隙),以下方法将起作用(只需将碰撞测试减少到直线和点校正/点三点):

  1. 它们的边缘触碰到吗?(线-线碰撞)检查扫掠区域的任何边缘线是否与其他扫掠区域的任何边缘线相交。每个扫描区域都有6面。

  2. 小家伙在大家伙里面吗?(使用轴对齐的形状(point-rect和point-tri))重新定向(旋转)扫掠区域,以使较大的区域与轴对齐,并测试较小的区域是否是内部区域(通过测试是否有角点(应该是全部或全部)在轴对齐的扫掠区域内)。这是通过将您的十六进制分解为三边形和矩形来完成的。

您首先进行哪种测试取决于每种测试的可能性(首先进行最常见的测试)。

您可能会发现使用后掠边界圆(胶囊而不是十六进制)会更容易,因为将轴对齐时,将其分为两个半圆和一个矩形更容易。..我让你来画答案


如果其中一个矩形很小并且在两条边线之间的空间内移动,则不起作用。
jrsala

@madshogo我刚刚添加到我的回复中。现在应该是一个完整的解决方案。
轴突

1
“使用轴对齐的形状(point-rect和point-tri)”:如何甚至将三角形或“ point-triangle”(无论是什么意思)与轴对齐?“使较大的一个与轴对齐”:如何辨别哪个大于另一个?你计算他们的面积吗?“这是通过将您的十六进制分解为三边形和矩形来完成的。”:哪个十六进制?那里有两个。“(如果您想让我为您举例说明,请反对此答复)”:您是认真的吗?
jrsala

“您如何甚至将三角形与轴对齐?” 答:将obj的路径对准后掠区域。挑选一条边缘并使用Trig。“你怎么知道哪一个比另一个大?” 答:例如,使用矩形的两个对角线相对点(十六进制的中间)之间的距离。“哪个十六进制?” 答:大。
轴突
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.