1

我目前正在使用以下方法来计算两个相互反弹的球体。这是在使用 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));
}
4

2 回答 2

3

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

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

碰撞

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 的拆分如下所示:

碰撞

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;

我们可以通过添加其分量来重建原始速度:

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;
于 2013-10-05T09:07:33.247 回答
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;

然后可以像他的示例代码的其余部分一样重新组合平行和正交分量。

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

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

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

于 2013-12-02T07:33:07.493 回答