この単純な衝突ソルバーでオブジェクトが貫通するのはなぜですか?


7

以下のコードは、ここの Microsoft XNAサンプルからのものです。これは非常に単純なリジッドボディシミュレーションであり、多くの物理的効果(角運動量など)を無視しますが、オブジェクト(球)を押しのけて、互いに貫通しないようにします。

ただし、シミュレーションでは球体が貫通するだけでなく、多数の球体が相互に積み重ねられている場合、小さな球体が大きな球体の内側にほぼ完全に収まります。すべての球が同じ半径と質量を持つようにすると、シミュレーションは適度に実行されます(最小の相互浸透で)。

誰かが相互浸透がある理由を説明できますか?球の位置を移動させるので、相互侵入は不可能のようです。

シミュレーションの各球について、このメソッドは他のすべての球で呼び出されます。

/// <summary>
// Given 2 spheres with velocity, mass and size, evaluate whether
// a collision occured, and if so, excatly where, and move sphere 2
// at the contact point with sphere 1, and generate new velocities.
/// </summary>
private void SphereCollisionImplicit(Sphere sphere1, Sphere sphere2)
{
    const float K_ELASTIC = 0.75f;

    Vector3 relativepos = sphere2.Position - sphere1.Position;
    float distance = relativepos.Length();
    float radii = sphere1.Radius + sphere2.Radius;
    if (distance >= radii)
    {
        return; // No collision
    }

    // Add epsilon to avoid NaN.
    distance += 0.000001f;

    Vector3 relativeUnit = relativepos * (1.0f / distance);
    Vector3 penetration = relativeUnit * (radii - distance);

    // Adjust the spheres' relative positions
    float mass1 = sphere1.Mass;
    float mass2 = sphere2.Mass;

    float m_inv = 1.0f / (mass1 + mass2);
    float weight1 = mass1 * m_inv; // relative weight of sphere 1
    float weight2 = mass2 * m_inv; // relative weight of sphere 2. w1+w2==1.0

    sphere1.Position -= weight2 * penetration;
    sphere2.Position += weight1 * penetration;

    // Adjust the objects’ relative velocities, if they are
    // moving toward each other.
    //
    // Note that we're assuming no friction, or equivalently, no angular momentum.
    //
    // velocityTotal = velocity of v2 in v1 stationary ref. frame
    // get reference frame of common center of mass
    Vector3 velocity1 = sphere1.Velocity;
    Vector3 velocity2 = sphere2.Velocity;

    Vector3 velocityTotal = velocity1 * weight1 + velocity2 * weight2;
    Vector3 i2 = (velocity2 - velocityTotal) * mass2;
    if (Vector3.Dot(i2, relativeUnit) < 0)
    {
        // i1+i2 == 0, approx
        Vector3 di = Vector3.Dot(i2, relativeUnit) * relativeUnit;
        i2 -= di * (K_ELASTIC + 1);
        sphere1.Velocity = (-i2) / mass1 + velocityTotal;
        sphere2.Velocity = i2 / mass2 + velocityTotal;
    }
}

特に、これは次のとおりです。

sphere1.Position -= weight2 * penetration;
sphere2.Position += weight1 * penetration;

相互侵入を完全に禁止する必要がありますが、それはなぜですか?

回答:


5

あなたの問題は複数の衝突が原因だと思います。

3つの球A、B、Cについて考えます。

Aがそこに座っています。

BがAをヒット。

CがBにヒットします。BがAにナッジされます。


はい、でも今はCがAとBの両方に対して位置を修正します。私はあなたの意味を理解していますが、3つの球で作成した例はうまくいくようです-それが本当に問題なのかどうかはわかりません。また、この反復法は、ほとんどのゲーム物理シミュレーションが衝突を解決する方法であると私は信じています。繰り返しますが、私は間違っている可能性があります。たぶん、この方法で一度に1つずつ衝突を解決すると、連続衝突検出を使用する場合にのみ相互侵入が防止されます。
Olhovsky、

これが実際の理由かもしれません。ブランドンは同様のことを提案しました。+1は、正確な理由であるかどうかにかかわらず、有用な貢献を示します。
Olhovsky、

5

このコードは、2つの球の間の衝突を取り、それらの質量と速度に基づいて球を移動しています。

これは、青いボールと緑の壁の間に貫通がある理由を示す非常に基本的な図です。

ここに画像の説明を入力してください

この例では、ボールの中心は壁の端から5フィートです。ボールは毎秒1フィートで動き、毎秒1フレームのフレームレートで走っています。フレーム4では貫通はありませんが、フレーム5には貫通があります。フレーム5が衝突することを知る方法はないので、壁に到達したときに停止とは言えません。代わりに、ボールが部分的に壁の内側にあるかどうか、すべてのフレームをチェックします。ある場合は、ペネトレーションの量だけ戻し、反射速度を適用します。これは、ほとんどの衝突検出アルゴリズムが機能する方法です。別の方法として、特定の方向に衝突するまでの距離を各フレームで確認することもできます。これの問題は、チェックする方向を知る必要があることです。これはあなたの状況では機能しないので、あなたが取っているルートは正しいものです。

このアプローチの欠点は、おそらく経験しているジッターです。このスクリプトが、最後の衝突に基づいて移動するようにボールに指示する場合、別のボールに侵入するように移動している可能性があり、その後、最初のボールと衝突するようにボールを送り返します。前後にジャンプさせる。これに役立つようにオブジェクトの質量を変更できます。これにより、ジャンプが大幅に減少するか、最初に衝突する速度が低下します。


実際には、問題は「ジッター」(不安定さ)ではありません。不安定さは大丈夫ですが、正確さを犠牲にして距離補正しきい値で後で修正できます。私が抱えている問題(まあ、Microsoftはコードなので)は、相互侵入が解決されていないということです。つまり、コードはペネトレーションを解決しているように見えますが、ジッターであるだけでなく、相互ペネトレーションもたくさんあります。それはなぜですか?私はあなたの答えに何かを見逃しましたか?入力btwをありがとう!
Olhovsky、

ああ、私はあなたがローレンの答えと同様に、複数のボールが互いに押し出されるために複数の衝突が正しく解決されないと言っていると思います-それは正しいですか?
Olhovsky、

そう、それはジッターであり、これもまた彼らが正しく衝突しない原因になります
ブランドン

まあ、そうでもない。不安定性!=相互侵入。オブジェクトは、相互侵入せずに(侵入解決のために)ジッターする可能性があります。とにかく+1 :)
Olhovsky、


1

SphereCollisionImplicit()は、球が移動した後に呼び出されます。その動きは衝突と相互侵入で終わった可能性があり、この方法はそれを解決します。

開始フレーム:

  1. コードの他の場所では、位置は速度によって更新されます。

  2. 次に、このメソッドが呼び出されて、衝突/侵入が発生したかどうかが確認されます。

  3. もしそうなら、それはそれらを分離し、それらの速度を反映/変更します。

  4. 次のフレームでは、位置は新しい/変更された速度で更新されます... goto 1。


私はあなたが何を言おうとしているのか理解できません。現在、そのXNAサンプルでは、​​箇条書きが正確に言っていることを実行し、ボールの描画はステップ3の直後に発生します。それでも、なぜ相互貫通があるのですか?おっしゃったように、ステップ3ではそれらを分離する必要があります。
Olhovsky、

ステップ1と4はペネトレーションを引き起こす可能性があり、ステップ3はペネトレーションが発生した場合にそれを削除します。
スティーブH

この質問の要点は、浸透が完全に取り除かれているわけではないということです。その理由については、他の回答を参照してください。
Olhovsky、

1

ボールの位置と速度が変更されるたびに、ループの初期にチェックされた他のボールと衝突したかどうかを確認する必要があるため、相互侵入があります。たとえば、3つのボールがあるとします。

スタート1.ボール#Aがボール#Bに対してチェックされ、衝突はない

  1. ボール#Aはボール#Cに対してチェックされ、衝突はありません

  2. ボール#Bはボール#Aに対してチェックされ、衝突はありません

  3. ボール#Bがボール#Cに対してチェックされ、衝突が見つかりました!! ボール#Bと#Cが移動します。

  4. ボール#Cはボール#Aに対してチェックされ、衝突はありません

  5. ボール#Cはボール#Bに対してチェックされ、衝突はありません

できた

したがって、ステップ2でボール#Bがボール#Aに移動し、相互侵入が発生した可能性があります。

修正するには、ボールが移動するたびに、移動するたびに他のすべてのボールに対して再チェックする必要があると思います。このシナリオでは、ボール#Bがボール#Cにぶつかり、ボール#Aをリッチチェッティングし、ボール#Aをボール#Cにリッチチェーし、ボール#Bに再び当たります...

1つのフレームでいくつのリチェットを処理しますか?また、各リチェットの後にすべての壁に対して再チェックする必要がありますか?

スタート1.ボール#Aがボール#Bに対してチェックされ、衝突はない

  1. ボール#Aはボール#Cに対してチェックされ、衝突はありません

  2. ボール#Bはボール#Aに対してチェックされ、衝突はありません

  3. ボール#Bがボール#Cに対してチェックされ、衝突が見つかりました!! ボール#Bと#Cが移動します。

  4. #Bおよび#Cループを再起動します...

  5. ボール#Bがボール#Aに対してチェックされ、衝突が見つかりました!! ボール#Bと#Aが移動する

  6. #Bおよび#Aループを再起動します...

  7. ボール#Aはボール#Bに対してチェックされ、衝突はありません

  8. ボール#Aがボール#Cに対してチェックされ、衝突が見つかりました!! ボール#Aと#Cが移動

  9. #Aおよび#Cループを再始動します。

  10. ボール#Aはボール#Bに対してチェックされ、衝突はありません

  11. ボール#Aはボール#Cに対してチェックされ、衝突はありません

  12. ボール#Bはボール#Aに対してチェックされ、衝突はありません

  13. ボール#Bがボール#Cに対してチェックされ、衝突が見つかりました!! ボール#Bと#Cが移動

  14. #Bおよび#Cループを再始動します

  15. ボール#Bはボール#Aに対してチェックされ、衝突はありません

  16. ボール#Bはボール#Cに対してチェックされ、衝突はありません

  17. ボール#Cはボール#Aに対してチェックされ、衝突はありません

  18. ボール#Cはボール#Bに対してチェックされ、衝突なし


実際にはそれではありません。衝突は他のすべてのボールに対してチェックされますが、問題はチェックの順序にあります(緑色のチェックマークが付いた回答で示唆されているように:)。他のすべてのボールをチェックするだけでは十分ではありません。
Olhovsky
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.