在Stack Overflow社区的帮助下,我编写了一个非常基本但有趣的物理模拟器。

你点击并拖动鼠标来发射一个球。它会弹来弹去,最终停在“地板”上。

我想添加的下一个重要功能是球与球的碰撞。球的运动被分解成x和y速度向量。有重力(每一步y向量都有小幅度的减小),有摩擦力(每一次与墙碰撞两个向量都有小幅度的减小)。这些球以一种令人惊讶的真实方式移动。

我想我的问题有两部分:

检测球与球碰撞的最佳方法是什么? 我只是有一个O(n²)循环,遍历每个球,并检查每个球的半径是否重叠? 我用什么方程来处理球与球的碰撞?101年物理 它如何影响两个球的速度x/y向量?这两个球最终的方向是什么?我怎么把它应用到每个球上呢?

处理“墙壁”的碰撞检测和由此产生的矢量变化很容易,但我发现球-球碰撞更复杂。对于墙,我只需要取适当的x或y向量的负数,它就会朝着正确的方向移动。我可不这么认为。

一些快速的澄清:为了简单起见,我现在对完美弹性碰撞没有问题,而且我所有的球现在都有相同的质量,但未来我可能会改变这一点。


编辑:我发现有用的资源

带矢量的二维球物理:不含三角函数的二维碰撞。pdf 2d球碰撞检测示例:添加碰撞检测


成功!

我的球碰撞检测和响应工作得很好!

相关代码:

碰撞检测:

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碰撞。此外,它跳过检查与自身的碰撞)。

然后,在我的球类中,我有我的collision()和resolveccollision()方法:

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));

}

源代码:完整的源球到球对撞机。

如果有人对如何改进这个基本物理模拟器有一些建议,请告诉我!我还需要补充的一件事是角动量,这样球就会滚动得更真实。还有其他建议吗?请留下评论!


当前回答

有两种简单的方法。杰伊已经介绍了从球的中心起跳的准确方法。

更简单的方法是使用矩形包围框,将框的大小设置为球的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]
            }
        }
    }
}

其他回答

这是一个支持质量的简单例子。

private void CollideBalls(Transform ball1, Transform ball2, ref Vector3 vel1, ref Vector3 vel2, float radius1, float radius2)
{
    var vec = ball1.position - ball2.position;
    float dis = vec.magnitude;
    if (dis < radius1 + radius2)
    {
        var n = vec.normalized;
        ReflectVelocity(ref vel1, ref vel2, ballMass1, ballMass2, n);

        var c = Vector3.Lerp(ball1.position, ball2.position, radius1 / (radius1 + radius2));
        ball1.position = c + (n * radius1);
        ball2.position = c - (n * radius2);
    }
}

public static void ReflectVelocity(ref Vector3 vel1, ref Vector3 vel2, float mass1, float mass2, Vector3 intersectionNormal)
{
    float velImpact1 = Vector3.Dot(vel1, intersectionNormal);
    float velImpact2 = Vector3.Dot(vel2, intersectionNormal);

    float totalMass = mass1 + mass2;
    float massTransfure1 = mass1 / totalMass;
    float massTransfure2 = mass2 / totalMass;

    vel1 += ((velImpact2 * massTransfure2) - (velImpact1 * massTransfure2)) * intersectionNormal;
    vel2 += ((velImpact1 * massTransfure1) - (velImpact2 * massTransfure1)) * intersectionNormal;
}

经过一些试验和错误,我使用了本文的2D碰撞方法:https://www.vobarian.com/collisions/2dcollisions2.pdf (那个OP链接到)

我在一个使用p5js的JavaScript程序中应用了这一点,它工作得很完美。我之前曾尝试使用三角方程,虽然它们确实适用于特定的碰撞,但我无法找到一个适用于每一次碰撞的方程,无论它发生的角度是多少。

本文档中解释的方法没有使用任何三角函数,它只是简单的向量操作,我将此推荐给任何试图实现球与球碰撞的人,以我的经验,三角函数很难推广。我请我大学里的一位物理学家教我怎么做,他告诉我不要用三角函数来麻烦,并向我展示了一个类似于文档中链接的方法。

注:我的质量都是相等的,但这可以推广到不同的质量使用在文件中提出的方程。

下面是我计算碰撞后速度矢量的代码:

    //you just need a ball object with a speed and position vector.
    class TBall {
        constructor(x, y, vx, vy) {
            this.r = [x, y];
            this.v = [0, 0];
        }
    }

    //throw two balls into this function and it'll update their speed vectors
    //if they collide, you need to call this in your main loop for every pair of 
    //balls.
    function collision(ball1, ball2) {
        n = [ (ball1.r)[0] - (ball2.r)[0], (ball1.r)[1] - (ball2.r)[1] ];
        un = [n[0] /  vecNorm(n), n[1] / vecNorm(n) ] ;
        ut = [ -un[1], un[0] ];   
        v1n = dotProd(un, (ball1.v));
        v1t = dotProd(ut, (ball1.v) );
        v2n = dotProd(un, (ball2.v) );
        v2t = dotProd(ut, (ball2.v) );
        v1t_p = v1t; v2t_p = v2t;
        v1n_p = v2n; v2n_p = v1n;
        v1n_pvec = [v1n_p * un[0], v1n_p * un[1] ]; 
        v1t_pvec = [v1t_p * ut[0], v1t_p * ut[1] ]; 
        v2n_pvec = [v2n_p * un[0], v2n_p * un[1] ]; 
        v2t_pvec = [v2t_p * ut[0], v2t_p * ut[1] ];
        ball1.v = vecSum(v1n_pvec, v1t_pvec); ball2.v = vecSum(v2n_pvec, v2t_pvec);
    }

这个KineticModel是引用的方法在Java中的实现。

改进问题中给出的圆碰撞检测检测圆的解决方案:

float dx = circle1.x - circle2.x,
      dy = circle1.y - circle2.y,
       r = circle1.r + circle2.r;
return (dx * dx + dy * dy <= r * r);

它避免了不必要的“如果有两个返回”和使用不必要的变量。

为了澄清Ryan Fox的建议,将屏幕分割成区域,并且只检查区域内的冲突……

例如,将游戏区域分割成一个方格网格(即每边有一个单位长度),并检查每个方格网格之间的冲突。

这绝对是正确的解决方案。唯一的问题(正如另一个帖子指出的那样)是跨边界的碰撞是一个问题。

解决方案是在第一个网格上以0.5单位的垂直和水平偏移覆盖第二个网格。

然后,任何跨越第一个网格边界的碰撞(因此不会被检测到)将在第二个网格的方格内。只要跟踪已经处理过的碰撞(因为可能会有一些重叠),就不必担心处理边缘情况。所有的碰撞都将发生在其中一个网格上的方格内。