球到球的碰撞 - 检测和处理

问题描述 投票:258回答:11

随着堆栈溢出社会的帮助下我已经写了一个非常基本的,但有趣的物理模拟器。

您单击并拖动鼠标,启动一个球。它会反弹,并最终停止在“地板”。

我的下一个大的功能,我想补充的是球球的碰撞。球的运动被分解成x和y的速度向量。我有重力(小减小y向量的每个步骤),我有摩擦(小减少两个矢量的每个碰撞与壁)。球诚实地左右移动以一种非常现实的方式。

我想我的问题有两个部分:

  1. 什么是检测球球碰撞的最好方法是什么? 我只是有一个为O(n ^ 2)循环,在每一个球,并检查所有其他球迭代来看看它的半径重叠?
  2. 我该使用什么公式来处理球球的碰撞?物理101 它是如何实现两个球速度X / Y引导?什么是两个球头出得到的方向是什么?我该如何申请这每个球?

处理“墙”,并将所得矢量变化的碰撞检测是容易的,但我看到球,球碰撞并发症较多。随着墙壁我只是不得不采取适当的X或Y矢量的负和关闭它会走在正确的方向。随着球我不认为它是这样的。

一些快速澄清:为了简单起见,我确定有一个完全弹性碰撞就目前而言,还都是我的球具有相同的质量的权利,但我可能会改变,在未来的。


编辑:资源我发现有用

2D球物理与载体:2-Dimensional Collisions Without Trigonometry.pdf 2D球碰撞检测例如:Adding Collision Detection


成功!

我有球的碰撞检测和响应工作太棒了!

相关的代码:

碰撞检测:

for (int i = 0; i < ballCount; i++)  
{  
    for (int j = i + 1; j < ballCount; j++)  
    {  
        if (balls[i].colliding(balls[j]))  
        {
            balls[i].resolveCollision(balls[j]);
        }
    }
}

这将检查每球之间的碰撞,但跳过冗余检查(如果你要检查,如果球1球2,那么碰撞就不需要检查,如果球2次与碰撞球1。此外,它跳过自身碰撞检测)。

然后,在我的球类我有我的碰撞()和resolveCollision()方法:

public boolean colliding(Ball ball)
{
    float xd = position.getX() - ball.position.getX();
    float yd = position.getY() - ball.position.getY();

    float sumRadius = getRadius() + ball.getRadius();
    float sqrRadius = sumRadius * sumRadius;

    float distSqr = (xd * xd) + (yd * yd);

    if (distSqr <= sqrRadius)
    {
        return true;
    }

    return false;
}

public void resolveCollision(Ball ball)
{
    // get the mtd
    Vector2d delta = (position.subtract(ball.position));
    float d = delta.getLength();
    // minimum translation distance to push balls apart after intersecting
    Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d); 


    // resolve intersection --
    // inverse mass quantities
    float im1 = 1 / getMass(); 
    float im2 = 1 / ball.getMass();

    // push-pull them apart based off their mass
    position = position.add(mtd.multiply(im1 / (im1 + im2)));
    ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2)));

    // impact speed
    Vector2d v = (this.velocity.subtract(ball.velocity));
    float vn = v.dot(mtd.normalize());

    // sphere intersecting but moving away from each other already
    if (vn > 0.0f) return;

    // collision impulse
    float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2);
    Vector2d impulse = mtd.normalize().multiply(i);

    // change in momentum
    this.velocity = this.velocity.add(impulse.multiply(im1));
    ball.velocity = ball.velocity.subtract(impulse.multiply(im2));

}

源代码:Complete source for ball to ball collider.

如果任何人有如何改进提出了一些建议这一基本物理模拟器让我知道!有一件事我还没有补充的是,角动量,因此球将推出更真实。任何其他的建议?发表评论!

graphics language-agnostic collision-detection physics
11个回答
115
投票

检测是否两球碰撞,只是检查它们的中心之间的距离是否小于两倍的半径。要做到与球之间完全弹性碰撞,你只需要担心的是在发生碰撞的方向上的速度分量。其他组件(切线碰撞)将保持为两个球是相同的。可以通过创建的方向上的单位向量指向从一个球到另一个,然后取点积与所述滚珠的速度矢量得到碰撞部件。然后,您可以堵塞这些组件集成到一维完全弹性碰撞方程。

维基百科有一个很好的summary of the whole process。对于任何质球,新速度可以使用等式(其中V1和V2的碰撞后的速度,和U1,U2是从之前)来计算:

如果球具有相同的质量,则速度是简单地切换。下面是一些代码,我写了有类似的功能:

void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
{
    // Check whether there actually was a collision
    if (a == b)
        return;

    Vector collision = a.position() - b.position();
    double distance = collision.length();
    if (distance == 0.0) {              // hack to avoid div by zero
        collision = Vector(1.0, 0.0);
        distance = 1.0;
    }
    if (distance > 1.0)
        return;

    // Get the components of the velocity vectors which are parallel to the collision.
    // The perpendicular component remains the same for both fish
    collision = collision / distance;
    double aci = a.velocity().dot(collision);
    double bci = b.velocity().dot(collision);

    // Solve for the new velocities using the 1-dimensional elastic collision equations.
    // Turns out it's really simple when the masses are the same.
    double acf = bci;
    double bcf = aci;

    // Replace the collision velocity components with the new ones
    a.velocity() += (acf - aci) * collision;
    b.velocity() += (bcf - bci) * collision;
}

至于效率,瑞安福克斯是正确的,你应该考虑瓜分区域分成多个部分,然后做每一节中的碰撞检测。请记住,球可以在部分边界其他球发生碰撞,所以这可能使你的代码要复杂得多。效率可能不会不要紧,直到你有几百个球,但。对于奖励积分,您可以运行在不同的内核每个部分或每个部分中分裂冲突的处理。


3
投票

KineticModel是Java中的cited方法的实现。


2
投票

我实现这个代码在JavaScript使用HTML Canvas元素,它以每秒60帧产生奇妙的模拟。我开始模拟了与在随机位置和速度十几球的集合。我发现,在更高的速度下,一个小球和一个更大的一个之间的掠碰撞引起的小球出现粘在大球的边缘,并分离之前移动至周围较大的球90度左右。 (我不知道其他人观察到这种行为。)

计算的一些记录显示,最小平移距离在这种情况下是不是足够大,以防止同样的球从非常下一时刻发生碰撞。我做了一些试验,发现我可以通过基于相对速度扩大的MTD解决这个问题:

dot_velocity = ball_1.velocity.dot(ball_2.velocity);
mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
mtd.multplyScalar(mtd_factor);

我验证了此修复程序之前和之后,总动能是保守的每一次碰撞。在mtd_factor 0.5值是约发现总是导致球碰撞后分离的最少值。

尽管此修复程序引入了系统的确切物理错误少量的代价是,现在非常快球可以在浏览器进行模拟,而不降低时间步长。


48
投票

好了,几年前我做节目就像你在这里呈现。 有一个隐藏的问题(或很多,要看观点):

  • 如果球的速度太高,你可能会错过碰撞。

而且,几乎100%的情况下,新的速度将是错误的。哦,不是速度,而是位置。你必须精确地在正确的位置来计算新速度。否则,你只是在一些小的“错误”的金额,这可从以前的离散一步转移球。

解决的办法是显而易见的:你必须拆分时间步长是这样,那你首先转移到正确的地方,然后相撞,然后转向对你有休息的时间。


20
投票

您应该使用空间分割来解决这个问题。

阅读上Binary Space PartitioningQuadtrees


13
投票

作为澄清瑞安福克斯的建议,把屏幕分割成多个区域,只有区域内的碰撞检测...

例如分割播放区域成正方形的网格(其将任意将说是每边1米单位长度的),并检查每个网格正方形中的碰撞。

这是绝对正确的解决方案。与它唯一的问题(如另一个海报指出的)是跨边界冲突是一个问题。

对此的解决方案是在一个0.5单元的垂直和水平偏移到第一个叠加的第二网格。

然后,这将是跨边界在第一栅格(并因此未检测到)任何冲突将在第二网格方格内。只要你保持跟踪你已经处理了冲突(因为有可能会有一些重叠),你不必担心处理边缘情况。所有的碰撞将在网格中的一个方格内。


10
投票

减少碰撞检测的数量的一个好方法是将屏幕分割成不同部分。然后,您只有每个球比较同一节球。


7
投票

有一件事我在这里看到的优化。

虽然我不同意,当距离为半径的一个永远不应该实际计算这个距离之球命中!相反,计算它的平方和使用它的方式。没有理由为昂贵的平方根运算。

另外,一旦你已经找到了碰撞,你必须继续评估碰撞,直到不再有保留。问题是,第一个会叫别人有,你得到一个准确的图片之前得到解决。试想,如果球在边缘击球时会发生什么?第二球击中边,并立即进入反弹的第一球。如果你撞到一堆在角落里的球,你可以有一个必须解决相当多的冲突之前,你可以遍历下一个周期。

至于为O(n ^ 2),所有你能做的就是尽量减少排斥那些错过的费用:

1)不动不能打什么球。如果有趴在地板上的球的合理数量,这可能节省大量的测试。 (请注意,您还必须检查是否有什么东西砸静止球)。

2)的东西,可能是值得做的:把屏幕划分为多个区域,但该行应该是模糊的 - 在区域边缘球被列为所有相关的是(可能是四)区。我会用4x4的方格,区域存储为位。如果两个球区的区域的并返回0,测试结束。

3)正如我所提到的,不这样做的平方根。


6
投票

我发现了一个优秀的网页上的碰撞检测,并在2D响应信息。

http://www.metanetsoftware.com/technique.html

他们试图解释它是如何从学术角度进行。他们先从简单的对象到对象的碰撞检测,并移动到碰撞响应以及如何将其放大。

编辑:更新的链接


3
投票

你有两个简单的方法可以做到这一点。周杰伦已经涵盖了从球的中心检查的准确的方法。

更简单的方法是使用一个矩形边框,设置你的框的大小为80%,球的大小,你会模拟碰撞相当不错。

一个方法添加到你的球类:

public Rectangle getBoundingRect()
{
   int ballHeight = (int)Ball.Height * 0.80f;
   int ballWidth = (int)Ball.Width * 0.80f;
   int x = Ball.X - ballWidth / 2;
   int y = Ball.Y - ballHeight / 2;

   return new Rectangle(x,y,ballHeight,ballWidth);
}

然后,在你的循环:

// Checks every ball against every other ball. 
// For best results, split it into quadrants like Ryan suggested. 
// I didn't do that for simplicity here.
for (int i = 0; i < balls.count; i++)
{
    Rectangle r1 = balls[i].getBoundingRect();

    for (int k = 0; k < balls.count; k++)
    {

        if (balls[i] != balls[k])
        {
            Rectangle r2 = balls[k].getBoundingRect();

            if (r1.Intersects(r2))
            {
                 // balls[i] collided with balls[k]
            }
        }
    }
}

3
投票

我看到它在这里和那里暗示,但你也可以做一个更快的计算第一一样,比较边框的重叠,如果这第一个测试通过,那么做一个半径范围内重叠。

添加/差异的数学是不是所有的TRIG为半径的边框快得多,而且大多数时候,边框测试将解雇碰撞的可能性。但是,如果你再重新测试与三角函数,你得到的是你正在寻找的准确的结果。

是的,这两个测试,但它会更快的整体。

© www.soinside.com 2019 - 2024. All rights reserved.