加速度が制限されているオブジェクトのパスを計算するにはどうすればよいですか?


9

たとえば、車があり、車に特定の最小回転半径があり、その車を点aから点bまで運転したいが、車が点bに向いていないとします。ポイントbへのパスを計算するにはどうすればよいですか?ポイントbで方向を指定できることも良いでしょう(たとえば、私道に車を運転してからガレージに車を引き込みたいとします-芝生の上をドライブして車道に着いたとしても、あまり役に立ちません。横向きです:)

ドキュメントへのポインタ(または名前だけでも)は完全に問題ありません-私は何も見つけることができません。

私の試みでは、それらは単純なケースで機能しますが、ポイントbが最小回転半径よりもaに近い場合などの状況では無残に失敗します。

たとえば、次のようなパス(太字のパス)をどのように決定しますか。

説明のための湾曲した経路

編集:私の実際の問題では、いくつかの単純なパスの制約がありますが、私はすでに機能しているA *アルゴリズムを用意していますが、それは瞬間的な方向変更を可能にするので、車が突然90°回転するのを見るのはばかげているように見えます彼らが転換点に到達したとき、ダイムで。


gamedev.stackexchange.com/questions/86881/…しかし、3Dスペースのセットアップ方法についての答えを理解しているかどうかはわかりません
xaxxon

「理想的には、このアルゴリズムは速度の変化に対応できます」最小回転半径は、変化する速度にまったく関係していますか、それとも1台の車で一定ですか?
DMGregory

その部分を削除します。私がやっていることは、「グランツーリズム」よりも「シムシティ」です。なぜあなたがそれを尋ねているのか理解しており、それが無関係であると理解しているので、それを追加したときに何を考えていたのかわかりません。
xaxxon、

ベジエ曲線図は、この別の答えを少し思い出させてくれました。これは、加速が制限された経路計画にも関係しています。その場合、加速は、回転半径ではなく方向性ロケットスラスタのようにモデル化されましたが、それでもいくつかの有用なアイデアが刺激される可能性があります。
DMGregory

回答:


7

これについてはまだ完全な方程式を検討していませんが、問題を理解するのに役立つビジュアルをいくつか示します。それはいくつかのジオメトリに要約されます:

回転半径を示す円が付いた車。ケニー経由の車のアイコン

任意の開始点と方向から、最小回転半径で2つの円を描くことができます-1つは左側、もう1つは右側です。これらは、私たちのパスへの可能な限りタイトなスタートのポイントを説明しています。

目的の終了位置と方向についても同じことができます。これらの円は、私たちの道の可能な限り厳しい目的を説明しています。

これで問題は、開始円の1つと終了円の1つを結ぶパスを見つけ、接線に沿ってそれぞれにキスすることに減少します。

(これは、質問の中で言及されていない、障害物の間のパスファインドをする必要がないことを前提としています。Stormwindの答えは、これらのタイプの問題のナビゲーショングラフ情報をどのように使用できるかということです。ノードのシーケンスを取得したら通過するには、プランの各セグメントに以下の方法を適用できます。)

簡単にするために直線を使用すると、次のようになります。

車が進むことができるさまざまな経路を示す図。

これは私たちに限定的なケースを与えます。この方法で経路を見つけたら、開始円と終了円の一方または両方を人工的に膨らませて、2つの円が接する点まで、直接ではないが滑らかな経路を取得できます。

これらのパスを計算しています

1つの方向転換のケースを考えてみましょう-右折することから経路を開始するとします。

右回転円の中心は次のとおりです。

startRightCenter = carStart.position + carStart.right * minRadius

パスの直線部分の角度を呼び出します(正のx軸から測定) pathAngle

からrightCenter回転円を離れるポイント(pathAngleに向いている必要があるポイント)までベクトルを描画すると、そのベクトルは...

startOffset = minRadius * (-cos(pathAngle), sin(pathAngle))

つまり、円を離れるポイントは...

departure = startRightCenter + startOffset

ターニングサークルに再び入るポイントは、左折か右折かで異なります。

// To end with a right turn:
reentry = endRightCenter + startOffset

// To end with a left turn: (crossover)
reentry = endLeftCenter - startOffset

さて、私たちの仕事が正しくできていれば、departurereentryつながる線はに垂直でなければなりませんstartOffset

dot(reentry - departure,  startOffset) = 0

そして、この方程式を解くと、これが当てはまる角度が得られます。(技術的にはそのような2つの角度があるため、ここでは複数を使用しますが、そのうちの1つは、通常私たちが望んでいないリバースでの運転を伴います)

例として、右折の場合を右折に置き換えてみましょう。

dot(endRightCenter + startOffset - startRightCenter - startOffset, startOffset) = 0
dot(endRightCenter - startRightCenter, startOffset) = 0
pathAngle = atan2(endRightCenter - startRightCenter)

クロスオーバーのケースはもっと複雑です-それは私がまだすべての数学を理解していないものです。残りの詳細を確認する際に役立つ場合に備えて、今のところ回答を掲載しません。

編集:最小回転半径内の目的地

結局のところ、この方法は目的地が最小旋回距離よりも近い場合でも、すぐに使用できることがよくあります。再突入円の1つの少なくとも一部が回転半径の外側に到達するため、少しプレッツェルのようになってもかまわない限り、実行可能なパスを見つけることができます...

近い目的地への経路計画を立てるときのオプションのデモ。

私たちがその道をたどる経路が気に入らない場合(または実行可能でない場合-私はすべてのケースを徹底的にチェックしていません-多分不可能なケースがあるかもしれません)、適切な状態になるまで常に直進または後退できます上図のように、開始円と終了円の間の接触をキスします。


それはそれについて考えるのに素晴らしく単純な方法であり、円の接線は扱いが非常に簡単です。これまであなたの答えをざっと読みましたが、私が取ったすべてのアプローチの1つの問題は、ゴールがスタートポイントの回転する円の内側にある場合です。
xaxxon、

私がそれに対処するために知っている最も単純なポリシーは、目標があなたの転換円の1つにあるまで逆転し、それからそれを有効にすることです。目的地の方向では、開始と終了のターンサークルがどこかにキスするまで反転します。その場合を視覚化するために図を追加します。
DMGregory

2
1か月後(およびいくつかの注意散漫)に、私はこれを機能させました。4つの接線、つまり「外側」と「内側」(または「交差」)の接線を計算します。つまり、start.left_circleをgoal.left_circleに、start.left_circleを「交差」してgoal.right_circleに(そして他の2つは円を入れ替えるだけ)。ここでは「外側」のパスです:youtube.com/watch?v=99e5Wm8OKb0こことは、「交差点」のパスです: youtube.com/watch?v=iEMt8mBheZU
xaxxon

1

これは、ナビゲーションの残りのデータモデルに大きく依存します。つまり。手元にあるデータ、データを簡単に追加できるデータ、データの利用方法。

水の交通システムから同様のシナリオをとり、その前提で

  • あなたはゲームループにいます
  • あなたはノードパスシステムを持っています
  • あなたの車は、自分自身の力とステアリングを使って「内側から」自分自身を制御する自律オブジェクトのように振る舞います
  • あなたの車はレールのように動きません

あなたは以下のようなものを持つことができます(写真の子供っぽい外観のために私を許してください)

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

(赤い四角はノード、赤い線はノードの相互接続です。ノード1〜9をドライブするためにパスファインディングソルバーを使用したと想定します。図に表示されているノード4〜9で、緑の線で示されたノードを通過したいとします。 、ノード#9のガレージに移動します。ただし、緑の線に正確に行きたくないので、右側の車線に自然にとどまり、スムーズな操作を行います)。

各ノードには、さまざまな目的のために、たとえば半径または複数のものを保持するメタデータがあります。そのうちの1つは青い円で、車の照準を示します。

あらゆる機会、車両のニーズがする次の二つのノード点に注意して P(次)とP(1 +次)、及びそれらの位置。もちろん、車にもポジションがあります。車は、P(next)の青いメタデータサークルの右側の接線を目指します。したがって、車は反対方向に進むので、衝突することはありません。接線を狙うということは、車がどの方向からでも円に近づき、常に正しい方向を保つことができることを意味します。これは大まかな基本原則であり、さまざまな方法で改善できます。

距離を決定するにはP(next + 1)が必要です。車がP(next)に到達するか、メタデータの半径内に入ると、P(next + 1)の距離に応じてステアリング角度を調整できます。つまり。近い場合は大きく回し、遠い場合は少し回します。明らかに、他のルールとエッジ条件も必要です。たとえば、P(next)とP(next + 1)の右側の接線に基づいて車とヘルプラインの間の距離を計算し、それによって修正します。 -破線(写真の上)と点線(写真の下)に留まる意志。

いずれの場合でも、自動車は1つのノードを通過すると、それを忘れて次の2 つのノードを調べ始めます

あなたの質問に。どうやら、ノード7(上の写真では、下の図ではノード2として表示)に到達すると、十分に回せません

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

1つの可能な解決策は、いくつかのヘルプラインを作成し、常に目標維持し、その後、独自の物理設定で車を動かします(指定した速度で加速し、速度を遅くし、ノードメタデータの速度制限を考慮に入れ、指定または計算された速度でブレーキをかけます) Gなど)。前述のように、このシナリオでは、自動車は自律的で自己記述的な自己運搬オブジェクトです。

緑のヘルプライン1,2,3を用意します。車がマゼンタの円に達すると、右折し始めます。この時点で、成功しないことを既に計算できます(最大回転率がわかっており、曲線を計算でき、ヘルプライン2と3の両方を横切ることがわかります)。ステアリングを完全に右に回し、(物理的な増分によって)前進させ、ヘルプライン3に到達するまで減速します(近づく-しきい値を使用して、f(ヘルプラインへの距離)など)。それがあるときヘルプライン3、入るにステアリングを回し、モードフル反対ヘルプライン4到達するまで反転させます(ノード1と2の間の接続線-「ポイントオブサイドアルゴリズム」のグーグル)。速度が落ちたら、到達したら、再びドライブアヘッドモードにして、ホイールを回します。道路がきれいになるまで繰り返します。今回は1つの追加の操作で十分でした。

これは一般的な考え方です。ゲームループ中、またはゲームのタスクキューシステムをチェックするとき:

  • 車の位置、速度、角度などを現在のエッジの制限と目標に対してチェックします
  • まだ到達していない場合続けてください(物理学に動かさせてください。車にはrpmとギアがあります)。queシステムに新しいチェックを挿入します。たとえば0.1秒で発生します。
  • 到達した場合、新しい条件を計算し、データを設定して開始します。queシステムで発生する新しいチェックを、たとえば0.1秒で挿入します。
  • ループサイクルを完了します-続行して繰り返します。

ノードと車に十分なデータを与えることにより、動きと継続があります。

編集:そして追加:これは当然微調整が必​​要です。シミュレーションの動作には、さまざまなヘルプライン、メタデータ、サークルなどが必要になる場合があります。これは、1つの可能な解決策のアイデアを提供します。


あなたの答えを読むのに少し時間がかかります。汎用的なパスファインディングがすでに設定されて機能していますが、これにより、オブジェクトは任意の時点で無限の加速を行うことができます。
xaxxon 2016

ランダムに、私は実際にあなたの説明にかなり近いものを持っています。紫色の「動く」線は、youtube.com / watch?v = EyhBhrkmRiYの 2つの直線から完全に手続き的に生成されます が、「タイト」な状況では機能せず、実際のパスファインディングには使用されません。
xaxxon 2016

0

私はDMGregoryが提案したことをやってしまい、それはうまくいきました。2つの接線のスタイルを計算するために使用できるいくつかの関連コード(スタンドアロンではありません)を次に示します。このコードは効率的ではなく、すべての状況でおそらく正しくないかもしれませんが、今のところ私にとってはうまくいきます:

bool Circle::outer_tangent_to(const Circle & c2, LineSegment & shared_tangent) const {
    if (this->direction != c2.direction) {
        return false;
    }
    if (this->radius != c2.radius) {
        // how to add it: http://mathworld.wolfram.com/Circle-CircleTangents.html
        // just subtract smaller circle radius from larger circle radius and find path to center
        //  then add back in the rest of the larger circle radius
        throw ApbException("circles with different length radius not supported");
    }

    auto vector_to_c2 = c2.center - this->center;
    glm::vec2 normal_to_c2;
    if (this->direction == Circle::CW) {
        normal_to_c2 = glm::normalize(glm::vec2(-vector_to_c2.y, vector_to_c2.x));
    } else {
        normal_to_c2 = glm::normalize(glm::vec2(vector_to_c2.y, -vector_to_c2.x));
    }

    shared_tangent = LineSegment(this->center + (normal_to_c2 * this->radius),
                                 c2.center + (normal_to_c2 * this->radius));
    return true;
}


bool Circle::inner_tangent_to(const Circle & c2, LineSegment & tangent) const {

    if (this->radius != c2.radius) {
        // http://mathworld.wolfram.com/Circle-CircleTangents.html
        // adding this is non-trivial
        throw ApbException("inner_tangents doesn't support circles with different radiuses");
    }

    if (this->direction == c2.direction) {
        // inner tangents require opposing direction circles
        return false;
    }

    auto vector_to_c2 = c2.center - this->center;
    auto distance_between_circles = glm::length(vector_to_c2);

    if ( distance_between_circles < 2 * this->radius) {
//      throw ApbException("Circles are too close and don't have inner tangents");
        return false;
    } else {
        auto normalized_to_c2 = glm::normalize(vector_to_c2);
        auto distance_to_midpoint = glm::length(vector_to_c2) / 2;
        auto midpoint = this->center + (vector_to_c2 / 2.0f);

        // if hypotenuse is oo then cos_angle = 0 and angle = 90˚
        // if hypotenuse is radius then cos_angle = r/r = 1 and angle = 0
        auto cos_angle = radius / distance_to_midpoint;
        auto angle = acosf(cos_angle);

        // now find the angle between the circles
        auto midpoint_angle = glm::orientedAngle(glm::vec2(1, 0), normalized_to_c2);

        glm::vec2 p1;
        if (this->direction == Circle::CW) {
            p1 = this->center + (glm::vec2{cos(midpoint_angle + angle), sin(midpoint_angle + angle)} * this->radius);
        } else {
            p1 = this->center + (glm::vec2{cos(midpoint_angle - angle), sin(midpoint_angle - angle)} * this->radius);
        }

        auto tangent_to_midpoint = midpoint - p1;
        auto p2 = p1 + (2.0f * tangent_to_midpoint);
        tangent = {p1, p2};

        return true;
    }
};

上記のコードの2つの動画を以下に示します。

ここに「外部」パスがあります:http : //youtube.com/watch?v=99e5Wm8OKb0そしてここに「交差」パスがあります:http : //youtube.com/watch?v=iEMt8mBheZU

このコードは役立つが、ここに表示されていない部分について質問がある場合は、コメントを投稿してください。1日か2日で表示されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.