圆内圆碰撞


9

在我的一个项目中,我有一个圆形的游戏区。在这个圆圈内,另一个小圆圈在移动。我要做的是防止小圆圈移到大圆圈之外。在下面可以看到,在第2帧中,小圆圈部分在外面,我需要一种方法将其移回即将要移到外面的位置。如何才能做到这一点?

基本例子

另外,我需要沿着大圆弧的碰撞点,以便可以更新小圆的速度。如何计算这一点?

我想做的是在移动小圆圈之前,先预测它的下一个位置,如果它在外面,我会发现t = 0和t = 1之间的碰撞时间(t = 1全时间步长)。如果我有碰撞时间t,那么我只是在t内移动小圆圈,而不是整个时间步长。但是,问题又来了,当我碰到两个圆圈,一个圆圈在另一个圆圈内时,我不知道该如何检测碰撞发生。

编辑:

我想找到碰撞点示例(绿色)。也许图片有点不对劲,但您明白了。

在此处输入图片说明

Answers:


10

假设大圆具有中心A和半径R,而小圆具有中心B和半径r朝向location C

有一个很好的方法来解决此问题,请使用Minkovski和(实际上是减法):将radius R的圆盘替换为radius的圆盘,而radius 的圆盘替换R-r为radius r的圆盘0即。位于的简单点B。该问题成为线圆相交的问题。

然后,您只需检查距离AC是否小于R-r。如果是,则圆圈不会发生碰撞。如果是较大的,只要找到点DBC的距离R-rA,这是你的小圆圈的中心的新位置。这等效于找到k以下内容:

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

替换vec(AD)vec(AB) + vec(BD)给出:

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

假设初始位置在大圆圈内,则该二次方程式k有一个正根。这是如何用伪代码求解方程式:

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

随着该值k,小圈的新中心是D这样BD = kBC

编辑:添加二次方程解


谢谢,它看起来确实很优雅,但是我不确定我是否理解。例如:“只要在BC的A距离Rr处找到D点”。我画了一幅画 ,试图更好地理解。因此,如果我们从B(AX,AY-(Rr))开始,而C是我们将以当前速度结束的位置。我理解引用文字的方式:在线段BC上找到一个点R,该点D与R距离A的距离为R。但是,在我绘制的图片上,我看到的是,只有B恰好是Rr距离A的距离。其他点将> Rr远离A。我想念什么?
dbostream 2012年

@dbostream您什么都不会丢失。如果两个圆已经接触,则没有真正的碰撞要检测:碰撞发生在B和中k=0。现在,如果您要使用冲突解决方案,那么我就没有在其答案中提及它,因为它需要了解对象的物理属性。应该发生什么?内圈应该在里面反弹吗?还是滚?扫?
sam hocevar

我希望小圆圈开始沿着大圆圈的弧线滑动。因此,如果我没记错的话,我希望碰撞点位于大圆弧上,以便可以使用其法线来更新速度。
dbostream 2012年

@dbostream如果应该以这种方式限制运动,那么我建议您尽快遵循该约束:如果速度为V,则使内圆V*t沿圆的圆周前进R-r。这意味着角V*t/(R-r)弧度围绕点旋转A。速度矢量可以相同的方式旋转。无需知道法线(始终始终指向圆心)或以其他任何方式更新速度。
sam hocevar

旋转之前,我仍然需要将小圆圈移动到碰撞点。当我尝试使用旋转矩阵旋转位置时,新位置与大圆圈中心的距离并不是完全(但几乎)是Rr,但是这一微小差异足以使我的碰撞测试在下一次运行中失败。我是否必须旋转位置才能找到新的位置?如果某些物体与直墙碰撞,就不可能像矢量一样使用矢量运算吗?
dbostream 2012年

4

假设大圆圈是圆圈A,小圆圈是圆圈B。

检查B是否在A内:

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

如果n-1B 帧在A内并且B 帧在A n之外,并且帧之间的时间不是太大(又称B的移动速度不太快),我们可以通过仅找到相对于B的笛卡尔坐标来近似碰撞点到A:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

然后我们可以将这些点转换为角度:

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

如果您想t第一次更准确地知道B在A之外是什么,则可以每帧进行一次射线-圆交点,然后比较从B到碰撞点的距离是否更大,那么距离B可以行进现在的速度。如果是这样,您可以计算出准确的碰撞时间。


谢谢,但是在进行相交测试时从小圆圈的中心发出光线真的正确吗?我们会不会遇到这张照片中间的场景?我的意思是小圆弧上与大圆弧碰撞的第一个点不一定是速度方向上的圆弧上的那个点。我想我需要在链接的图片的底部场景中找到类似的东西。我在第一篇文章中添加了一张新图片,其中显示了我认为需要的示例。
dbostream 2012年

嗯,我想这种情况是可能的。也许用一个新的圆C进行测试,该圆C具有B.Radius + B在该帧中的最大运动,请检查该圆是否与A相撞,然后对C上距A较远的点进行锻炼。(尚未尝试过此操作)
Roy T.2012年

3
用更少的词:if(distance(A,B))>(Ra-Rb)发生碰撞,您只需移动小圆圈以获得等于Ra-Rb的距离。否则,您通常会移动小圆圈。顺便说一下,@ dbostream您正在使用类似于简化形式的“投机联系”的方式,请尝试搜索该方式。
Darkwings'5

@Darkwings +1绝对正确,这听起来更简单!
罗伊(Roy T.)2012年

这听起来很简单,因为我剥离了所需的所有基本几何图形。可以将其命名为boundAB,而不是将其命名为“ collision”,因为它实际上是这样的:绑定到(0,0)的自由矢量AB。对其进行归一化后,您不仅可以获得与AB直线平行的方程式,而且还有一个有用的单位矢量。然后,您可以将该单位矢量乘以任意距离D并将新找到的参数添加到A中,以找到所需的碰撞点:C(Ax + Dx,Ay + Dy)。现在听起来更复杂了,但是是一样的东西:P
Darkwings 2012年

0

令(Xa,Ya)为大圆的位置及其半径R,而(Xb,Yb)为小圆的位置及其半径r。

您可以检查这两个圆是否碰撞

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

要找出碰撞的位置,请使用二进制搜索但步数固定,以找到圆碰撞的确切时刻。根据游戏的制作方式,您可以优化代码的这一部分(我提供的解决方案独立于小球的行为。如果它具有恒定的加速度或恒定的速度,则可以优化代码的这一部分并替换为一个简单的公式)。

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

一旦知道了碰撞时间,就可以计算出最终时间两个圆的位置,最终的碰撞点为

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya

0

我已经使用Sam Hocevar描述的算法在jsfiddle上实现了一个球弹跳的演示:

http://jsfiddle.net/klenwell/3ZdXf/

这是识别联系点的javascript:

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
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.