2D物理エンジンにラッピングワイヤ(Worms Ninja Ropeなど)を実装する


34

私は最近、ロープ物理学を試してみましたが、「標準的な」解決策-スプリングまたはジョイントでつながれた一連のオブジェクトからロープを作成すること-が満足できないことがわかりました。特にロープスイングがゲームプレイに関連する場合。ロープが巻き付いたり垂れ下がったりする能力はあまり気にしません(とにかくこれはビジュアルのために偽造されます)。

ゲームプレイにとって重要なのは、ロープが環境包み込み、その後に包み解く能力です。それはロープのように振る舞う必要さえありません-直線のセグメントで構成された「ワイヤ」はそうするでしょう。以下に例を示します。

忍者ロープ、障害物を包む

これは、Wormsゲームの「Ninja Rope」に非常によく似ています。

私は2D物理エンジンを使用しているため、私の環境は2Dの凸ポリゴンで構成されています。(具体的には、FarseerでSATを使用しています。)

だから私の質問はこれです:「ラッピング」効果をどのように実装しますか?

ワイヤーが「分割」および「結合」する一連の線分で構成されていることは明らかです。そして、その線の最後の(アクティブな)セグメント(移動するオブジェクトがアタッチされる)は、固定長ジョイントになります。

しかし、アクティブなラインセグメントをいつ、どこで分割する必要があるかを決定するために必要な数学/アルゴリズムは何ですか また、前のセグメントと結合する必要がある場合はどうなりますか?

(以前、この質問は動的環境でこれを行うことについても尋ねていました-私はそれを他の質問に分割することにしました。)

回答:


18

ロープをいつ分割するかを決定するには、ロープが各フレームを覆う領域を調べる必要があります。あなたがすることは、あなたがカバーされたエリアとあなたのレベル幾何学で衝突チェックをすることです。スイングがカバーする領域は、円弧である必要があります。衝突がある場合は、ロープに新しいセグメントを作成する必要があります。スイングアークと衝突するコーナーを確認します。スイングアークと衝突する角が複数ある場合は、前のフレームでのロープと衝突点の間の角度が最小になる角を選択する必要があります。

Helpful diagram of the ninja rope situation

衝突検出を行う方法は、現在のロープセグメントのルートO、前のフレームのロープの終了位置A、現在のフレームのロープの終了位置B、および多角形の各コーナーポイントPレベルジオメトリでは、(OA x OP)、(OP x OB)、および(OA x OB)を計算します。ここで、「x」は2つのベクトル間の外積のZ座標を取ることを表します。3つの結果すべてに同じ符号(負または正)があり、OPの長さがロープセグメントの長さよりも短い場合、ポイントPはスイングでカバーされる領域内にあるため、ロープを分割する必要があります。衝突するコーナーポイントが複数ある場合は、ロープにぶつかる最初のポイント、つまりOAとOPの間の角度が最小になるポイントを使用する必要があります。内積を使用して角度を決定します。

セグメントの結合に関しては、前のセグメントと現在のセグメントのアークを比較します。現在のセグメントが左側から右側へ、またはその逆にスイングした場合、セグメントを結合する必要があります。

セグメントを結合するための数学では、前のロープセグメントのアタッチメントポイントQと、スプリットケースのアタッチメントポイントを使用します。そこで、ベクトルQO、OA、およびOBを比較します。(QO x OA)の符号が(QO x OB)の符号と異なる場合、ロープは左から右へ、またはその逆に交差しているため、セグメントを結合する必要があります。もちろんこれは、ロープが180度スイングした場合にも発生する可能性があります。そのため、ロープを多角形ではなく空間の1点に巻き付けたい場合は、特別なケースを追加することをお勧めします。

もちろん、この方法は、ロープからぶら下がっているオブジェクトの衝突検出を行っていることを前提としているため、レベルジオメトリ内に収まらないようになっています。


1
このアプローチの問題は、浮動小数点の精度エラーにより、ロープがポイントを「通過」する可能性があることです。
アンドリューラッセル

16

ワームズをプレイしてからしばらく経ちましたが、覚えているのは、ロープが物を包み込んだとき、常に動いているロープの部分が1つだけであるということです。ロープの残りの部分は静的になります

したがって、実際の物理学はほとんど関与していません。アクティブなセクションは、端に質量がある単一の剛性スプリングとしてモデル化できます

興味深いのは、ロープの非アクティブなセクションをアクティブなセクションに分割したり、アクティブなセクションから結合したりするためのロジックです。

主に2つの操作があると思います。

'Split'-ロープが何かと交差しています。交差点で非アクティブなセクションと新しい、より短いアクティブなセクションに分割します

'Join'-アクティブなロープは、最も近い交差点が存在しない位置に移動しました(これは単なる角度/ドット積テストですか?)。2つのセクションを再結合し、より長くアクティブな新しいセクションを作成します

2Dポリゴンから構築されたシーンでは、すべての分割ポイントは衝突メッシュの頂点にある必要があります。衝突検出は、「この更新でロープが頂点を通過する場合、その頂点でロープを分割/結合しますか?


2
この男は、実際に、それは、あなただけ...周りにいくつかの直線を回転させる「硬い」春ではないevneです...右の場所にあった
スピーダー

あなたの答えは技術的に正しいです。しかし、線セグメントを持ち、それらを分割して結合することは明らかであると思いました。それを行うための実際のアルゴリズム/数学に興味があります。質問をより具体的にしました。
アンドリューラッセル

3

Gusanosでの忍者ロープの実装方法を確認してください。

  • ロープは、何かに付着するまで粒子のように機能します。
  • 取り付けられると、ロープはワームに力を加えるだけです。
  • 動的オブジェクト(他のワームなど)への添付は、このコードではまだTODOです。
  • オブジェクト/コーナーのラップがサポートされているかどうか思い出せません...

ninjarope.cppからの関連抜粋:


void NinjaRope::think()
{
    if ( m_length > game.options.ninja_rope_maxLength )
        m_length = game.options.ninja_rope_maxLength;

    if (!active)
        return;

    if ( justCreated && m_type->creation )
    {
        m_type->creation->run(this);
        justCreated = false;
    }

    for ( int i = 0; !deleteMe && i < m_type->repeat; ++i)
    {
        pos += spd;

        BaseVec<long> ipos(pos);

        // TODO: Try to attach to worms/objects

        Vec diff(m_worm->pos, pos);
        float curLen = diff.length();
        Vec force(diff * game.options.ninja_rope_pullForce);

        if(!game.level.getMaterial( ipos.x, ipos.y ).particle_pass)
        {
            if(!attached)
            {
                m_length = 450.f / 16.f - 1.0f;
                attached = true;
                spd.zero();
                if ( m_type->groundCollision  )
                    m_type->groundCollision->run(this);
            }
        }
        else
            attached = false;

        if(attached)
        {
            if(curLen > m_length)
            {
                m_worm->addSpeed(force / curLen);
            }
        }
        else
        {
            spd.y += m_type->gravity;

            if(curLen > m_length)
            {
                spd -= force / curLen;
            }
        }
    }
}

1
Uhmn ...これは私の質問にまったく答えていないようです。私の質問の全体的なポイントは、多角形で作られた世界にロープを巻き付けることです。Gusanosにはラッピングがなく、ビットマップの世界があるようです。
アンドリューラッセル

1

頭上から具体的なアルゴリズムを提供することはできませんが、忍者ロープの衝突を検出するために重要なのは、障害物に衝突する可能性のある頂点が2つしかないことです。セグメントの残りの長さに等しい最後の「分割」の半径内。スイングの現在の方向(時計回りまたは反時計回り)。「分割」頂点から近くの各頂点までの角度の一時リストを作成した場合、アルゴリズムは、特定の頂点でセグメントがその角度を超えてスイングしようとしているかどうかを気にするだけで済みます。そうである場合は、分割操作を行う必要があります。これはパイのように簡単です。これは、最後の分割頂点から新しい分割までの単なる線であり、新しい余りが計算されます。

重要なのは頂点だけだと思います。障害物の頂点間のセグメントにぶつかる危険がある場合は、ロープの端にぶら下がっている男の通常の衝突検出を開始する必要があります。とにかくコーナーなので、間のセグメントは重要ではありません。

申し訳ありませんが、具体的なことは何もありませんが、うまくいけば、これを実現するために概念的に必要な場所に移動できます。:)


1

同様のタイプのシミュレーションに関する論文へのリンクがある投稿は次のとおりです(ゲームではなくエンジニアリング/アカデミックコンテキスト):https : //gamedev.stackexchange.com/a/10350/6398

この種の「ワイヤ」シミュレーション(ゲームの河原海原で見られるように)の衝突検出+応答に、少なくとも2つの異なるアプローチを試しました。少なくとも、これはあなたが望んでいることだと思います-この種のシミュレーションには特定の用語はないようです。私はそれを「ロープ」ではなく「ワイヤー」と呼ぶ傾向があります。 「ロープ」は「粒子の鎖」と同義であると考えてください。そして、忍者のロープのような棒のような動作が必要な場合(つまり、押したり引いたりすることができます)、これはロープというよりも硬いワイヤーのようなものです。とにかく..

Pekujaの答えは良いです。3点の符号付き領域が0である時間を解くことにより、連続衝突検出を実装できます。

(OTOHを完全に思い出すことはできませんが、次のようにアプローチすることができます:ポイントaがb、cを通過する行に含まれる時間tを見つけます(dot(ab、cb)= t)の値を見つけるために0で、有効な時間0 <= t <1が与えられると、セグメントbc上のaのパラメトリック位置sを見つけます。つまり、a =(1-s)b + s cであり、aがbおよびc(つまり、0 <= s <= 1の場合)は有効な衝突です。

AFAICRは、他の方法でアプローチすることもできます(つまり、sを解いてからプラグインしてtを見つけます)が、直感的ではありません。(これが意味をなさない場合は申し訳ありません。メモを掘り下げる時間がなく、数年前です!)

したがって、イベントが発生するすべての時間を計算できるようになりました(つまり、ロープノードを挿入または削除する必要があります)。最も早いイベントを処理(ノードを挿入または削除)し、t = 0とt = 1の間にイベントがなくなるまで繰り返し/繰り返します。

このアプローチに関する1つの警告:ロープがラップできるオブジェクトが動的である場合(特に、それらをシミュレートする場合、およびロープへの影響、およびその逆)、それらのオブジェクトがそれぞれをクリップ/通過する場合、問題が発生する可能性がありますその他-ワイヤが絡まる可能性があります。また、box2dスタイルの物理シミュレーションでは、この種の相互作用/動き(オブジェクトの角が互いにすり抜ける)を防ぐことは間違いなく困難です。そのコンテキストでは、オブジェクト間のわずかな貫通は正常な動作です。

(少なくとも..これは、「wire」の実装の1つに問題がありました。)

はるかに安定しているが、特定の条件で一部の衝突を見逃す別のソリューションは、静的テストを使用することです(つまり、時間による順序付けを気にせず、衝突の各セグメントを見つけたときに再帰的に細分化するだけです)。はるかに堅牢です-ワイヤが角で絡まることはなく、少量の貫通は問題ありません。

Pekujaのアプローチはこれにも有効だと思いますが、別のアプローチもあります。私が使用したアプローチの1つは、補助衝突データを追加することです。世界の各凸頂点v(つまり、ロープが巻き付けることができる形状の角)で、有向線セグメントuvを形成する点uを追加します。ポイント「角の内側」(すなわち、世界の「背後」v; uを計算するために、補間された法線に沿ってvから内側に光線を投じ、vの後または光線が世界のエッジと交差する前に距離を止め、または、視覚ツール/レベルエディタを使用して、手動でセグメントをワールドにペイントすることもできます)。

とにかく、これで一連の「コーナーラインセグメント」UVができました。各UV、およびワイヤ内の各セグメントabについて、abとuvが交差するかどうかを確認します(静的、ブールのlineseg-lineseg交差点クエリ)。その場合、再帰(ラインセグメントabをavとvbに分割、つまりvを挿入)、ロープがvで曲がる方向を記録します。次に、ワイヤの隣接するラインセグメントab、bcの各ペアについて、bの現在の曲げ方向をテストしますbが生成されたときと同じです(これらの「曲げ方向」テストはすべて、署名された領域のテストです)。そうでない場合は、2つのセグメントをacにマージします(つまり、bを削除します)。

または、私は忘れてしまったかもしれませんが、マージしてから分割しますが、2つの可能な順序の少なくとも1つで確実に機能します!:)

現在のフレームに対して計算されたすべてのワイヤセグメントが与えられると、2つのワイヤエンドポイント間の距離の制約をシミュレートできます(内部ポイント、つまりワイヤとワールド間の接触ポイントを含めることもできますが、それはもう少し複雑です) )。

とにかく、うまくいけば、これが何らかの役に立つことになるでしょう...私がリンクした投稿の論文もあなたにいくつかのアイデアを与えるべきです。


0

これへの1つのアプローチは、スプリングで接続された衝突可能な粒子としてロープをモデル化することです。(かなり硬いもの、場合によっては単に骨としてでも)。粒子は環境と衝突し、ロープがアイテムを確実に包み込みます。

ソース付きのデモは次のとおりです。http//www.ewjordan.com/rgbDemo/

(最初のレベルで右に移動すると、やり取りできる赤いロープがあります)


2
ええと-これは特に私が望んでいないものです(質問を参照)。
アンドリューラッセル

あ。元の質問からは明らかではありませんでした。時間をかけて明確にしてくれてありがとう。(素晴らしい図です!)動的な分割を行うのではなく、一連の非常に小さな固定ジョイントを使用します。環境で大きなパフォーマンスの問題でない限り、コーディングははるかに簡単です。
レイチェルブルーム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.