使用XNA / MonoGame进行适当的球体碰撞分辨,具有不同的尺寸和质量

时间:2013-10-04 19:32:32

标签: c# xna physics monogame

我目前正在使用以下方法来计算彼此反弹的两个球体。这被用于使用3D物体的2D乒乓球游戏(试图将我的头部包裹在3D周围)。大多数事情都能正常工作,但有时候(通常当X或Y速度朝着同一个方向移动时,只比一个方向快一个时),物理学会做出奇怪的事情。

返回的浮动只是我用来改变球碰撞时发出的声音的质量差异。 任何人都可以在我的计算中看到任何错误:

internal float ResolveCollision(Ball otherBall)
{
    if (otherBall == this) { return 0f; }
    if (this.GetBoundingSphere().Intersects(otherBall.GetBoundingSphere()))
    {
        // Attempt to step the balls back so they are just barely touching
        Vector3 dd = Position - otherBall.Position;
        dd.Normalize();
        Position += dd / 2;
        otherBall.Position -= dd / 2;

        ///http://williamecraver.wix.com/elastic-equations

        Vector3 V1 = Velocity;
        Vector3 P1 = Position;
        float M1 = Mass;
        float A1 = getMovementAngle(V1.X, V1.Y);

        Vector3 V2 = otherBall.Velocity;
        Vector3 P2 = otherBall.Position;
        float M2 = otherBall.Mass;
        float A2 = getMovementAngle(V2.X, V2.Y);

        float CA = getContactAngle(P1, P2);

        // Recalculate x and y components based of a rotated axis, having the x axis parallel to the contact angle.
        Vector3 V1XR = V1 * (float)Math.Cos(A1 - CA);
        Vector3 V1YR = V1 * (float)Math.Sin(A1 - CA);

        Vector3 V2XR = V2 * (float)Math.Cos(A2 - CA);
        Vector3 V2YR = V2 * (float)Math.Sin(A2 - CA);

        //Now solve the x components of the velocity as if they were in one dimension using the equation;
        Vector3 V1f = (V1 * (M1 - M2) + 2 * M2 * V2) / (M1 + M2);
        Vector3 V2f = (V2 * (M2 - M1) + 2 * M1 * V1) / (M1 + M2);

        Vector3 V1fXR = (V1 * (float)Math.Cos(A1 - CA) * (M1 - M2) + 2 * M2 * V2 * (float)Math.Cos(A2 - CA)) / (M1 + M2);
        Vector3 V2fXR = (V2 * (float)Math.Cos(A2 - CA) * (M2 - M1) + 2 * M1 * V1 * (float)Math.Cos(A1 - CA)) / (M1 + M2);

        //Now find the x and y values for the un-rotated axis by equating for the values when the axis are rotated back.
        Vector3 V1fX = V1fXR * (float)Math.Cos(CA) + V1YR * (float)Math.Cos(CA + MathHelper.PiOver2);
        Vector3 V1fY = V1fXR * (float)Math.Sin(CA) + V1YR * (float)Math.Sin(CA + MathHelper.PiOver2);
        Vector3 V2fX = V2fXR * (float)Math.Cos(CA) + V2YR * (float)Math.Cos(CA + MathHelper.PiOver2);
        Vector3 V2fY = V2fXR * (float)Math.Sin(CA) + V2YR * (float)Math.Sin(CA + MathHelper.PiOver2);

        // Add it all up
        Vector3 nV1 = V1fX + V1fY;
        Vector3 nV2 = V2fX + V2fY;

        ///////////////////////////////////////////
        // Correct Velocity & Move apart
        //////////////////////////////////////////
        Velocity = v3check(nV1, MAXSPEED, -MAXSPEED);
        otherBall.Velocity = v3check(nV2, MAXSPEED, -MAXSPEED);

        // Step the balls forward (by there Velocity) just a bit so they are no longer touching
        Position += Velocity * _lastDT * .25f;
        otherBall.Position += otherBall.Velocity * otherBall._lastDT * .25f;

        return BMDMath.toFloat(Mass - otherBall.Mass);
    }

    return 0f;
}

我有以下帮助方法来转换某些角度(这可能是问题所在:

private static float getMovementAngle(double vx, double vy)
{
    return MathHelper.ToDegrees((float)Math.Atan2(vy, vx));
}


private static float getContactAngle(Vector3 o1, Vector3 o2)
{
    Vector3 d = o1 - o2;
    return MathHelper.ToDegrees((float)Math.Atan2(d.Y, d.X));
}

2 个答案:

答案 0 :(得分:3)

应尽可能避免使用角度。实际上,计算与角度的碰撞是非常糟糕的。有太多精彩的矢量数学可以帮助您进行计算。

让我们从计算碰撞平面开始吧。实际上,我们不需要整架飞机,只需要它的正常。在两个球体的情况下,这只是连接两个中心的矢量:

collision

var collisionNormal = Position - otherBall.Position;
collisionNormal.Normalize();
//The direction of the collision plane, perpendicular to the normal
var collisionDirection = new Vector3(-collisionNormal.Y, collisionNormal.X, 0);

现在将速度分成两部分。一部分与法线平行,另一部分垂直。那是因为垂直部分不受碰撞的影响。 V2的拆分如下:

collision

var v1Parallel = Vector3.Dot(collisionNormal, V1) * collisionNormal;
var v1Ortho    = Vector3.Dot(collisionDirection, V1) * collisionDirection;
var v2Parallel = Vector3.Dot(collisionNormal, V2) * collisionNormal;
var v2Ortho    = Vector3.Dot(collisionDirection, V2) * collisionDirection;

我们可以通过添加其组件重建原始的veclocities:

v1 = v1Parallel + v1Ortho;
v2 = v2Parallel + v2Ortho;

如前所述,正交分​​量不受碰撞的影响。现在我们可以将一些物理学应用于并行组件:

var v1Length = v1Parallel.Length;
var v2Length = v2Parallel.Length;
var commonVelocity = 2 * (this.Mass * v1Length + otherBall.Mass * v2Length) / (this.Mass + otherBall.Mass);
var v1LengthAfterCollision = commonVelocity - v1Length;
var v2LengthAfterCollision = commonVelocity - v2Length;
v1Parallel = v1Parallel * (v1LengthAfterCollision / v1Length);
v2Parallel = v2Parallel * (v2LengthAfterCollision / v2Length);

现在我们可以重新组合这些组件:

this.Velocity = v1Parallel + v1Ortho;
otherBall.Velocity = v2Parallel + v2Ortho;

答案 1 :(得分:1)

Nico Shertler的答案很好地用向量来设置问题,并且有很好的图表。但是,如果任一球的速度为零,则示例代码的物理部分不起作用 - 它会产生一个被零除错误,因为当V1或V2分别为零时,v1Length或v2Length将为0。

相反,物理部分可以用维基百科关于弹性碰撞(http://en.wikipedia.org/wiki/Elastic_collision#One-dimensional_Newtonian)的文章中的公式替换,因此:

var totalMass = this.Mass + otherBall.Mass;
var v1ParallelNew = (v1Parallel * (this.Mass - otherBall.Mass) + 2*otherBall.Mass * v2Parallel) / totalMass;
var v2ParallelNew = (v2Parallel * (otherBall.Mass - this.Mass) + 2*this.Mass * v1Parallel) / totalMass;
v1Parallel = v1ParallelNew;
v2Parallel = v2ParallelNew;

然后,可以像在其示例代码的其余部分中那样重新组合并行和正交分量。

还有一个重要的考虑因素。如果你只改变速度并且在检测到碰撞时球没有移出碰撞,那么它们可能仍然在下一帧中重叠(碰撞),即使它们的速度正在使它们彼此远离。这导致它们粘在一起,因为代码反转了彼此的速度。为了避免这种情况,只有当球至少在“平行”方向上朝向另一个球移动时才改变速度。 E.g:

if (Vector.Dot(collisionNormal, V1) > 0 || Vector.Dot(collisionNormal, V2) < 0)
{
    // do the physics code here
}

这种条件检查将允许球在初始碰撞解决方案应用于其速度后仍然重叠时继续彼此远离。它还允许以重叠状态生成的球分离。