球面上のルート-測地線を見つけますか?


8

私は、2Dマップ上を移動できるブラウザベースのゲームで友人と作業しています。ほぼ7年になりますが、まだこのゲームをプレイしているので、何か新しいものを提供する方法を考えています。それ以来、ゲームマップは制限された平面であり、人々は(0、0)から(MAX_X、MAX_Y)に量子化されたXとYの増分で移動できます(大きなチェス盤と想像してください)。
私たちはそれを別の次元にする時が来たと信じているので、ほんの数週間前に、ゲームが他のマッピングでどのように見えるか疑問に思い始めました:

  • 継続的な動きのある無制限の飛行機:これは一歩前進かもしれませんが、それでも私には確信がありません。
  • トロイダルワールド(継続的または量子化された動き):以前はトーラスを使用して作業してきましたが、今回はもっと何かを求めています...
  • 連続的な動きのある球形の世界:これは素晴らしいことです!

我々が望むものを ユーザーのブラウザは球面マップ上の各オブジェクトの(緯度、経度)のような座標のリストを与えられています。ブラウザは、これをユーザーの画面に表示して、Web要素内にレンダリングする必要があります(キャンバスでしょうか?これは問題ではありません)。人々が飛行機をクリックすると、(mouseX、mouseY)を(lat、lng)に変換し、現在のユーザーの位置からクリックされたポイントまでのルートを計算する必要があるサーバーに送信します。

私たちは 、回転行列、四元数、オイラー角、平行移動などを操作するための多くの有用な数学を備えたJavaライブラリの作成を開始しました。それらをすべてまとめて、球点を生成し、それらをレンダリングしてユーザーに表示するプログラムを作成しましたJPanel内。クリックをキャッチして球面座標に変換し、ビューの回転、スケール、変換などの他の便利な機能を提供することができました。現在、クライアントとサーバーの相互作用をシミュレートする小さな(非常に小さな)エンジンのようになっています。クライアント側は画面上のポイントを表示し、他の相互作用をキャッチし、サーバー側はビューをレンダリングし、現在の位置とクリックされたポイントの間のルートを補間するような他の計算を行います。

問題はどこだ? 明らかに、2つのルートポイント間補間する最短パスが必要です。クォータニオンを使用して、球の表面上の2点間を補間します。これは、球の表面上で最短経路が得られないことに気付くまで、問題なく機能するように見えました。

間違った円

問題は、ルートがX軸とY軸を中心とした2つの回転の合計として計算されることでした。そこで、目的地のクォータニオンの計算方法を変更しました。方向と呼ばれる3番目の角度(1番目は緯度、2番目は経度、3番目は現在の位置を指すベクトルを中心とした回転)を取得します。これで「向き」の角度ができたので、Z軸を回転し、結果のベクトルを宛先クォータニオンの回転軸として使用します(回転軸は灰色で表示されます)。

0、0から始まる正しいルート

取得したのは正しいルートです(大きな円上にあることがわかります)。ただし、これはルートの始点が緯度、経度(0、0)にある場合にのみ取得されます。つまり、開始ベクトルは(sphereRadius、0)です。 、0)。以前のバージョン(画像1)では、開始点が0、0であっても良い結果が得られないため、解決策に向かっていると思いますが、このルートを取得するために実行する手順は少し「奇妙です」 " 多分?

次の画像では、開始点が(0、0)ではない場合に発生する問題のビューが表示されます。これは、開始点が(sphereRadius、0、0)のベクトルではなく、目的点を確認できるためです。 (正しく描かれています!)はルート上にありません。

ルートが間違っています!

マゼンタポイント(ルート上にあるもの)は、(-startLatitude、0、-startLongitude)の球の中心を中心に回転したルートの終点です。つまり、回転行列を計算してルート上のすべてのポイントに適用すると、実際のルートが得られる可能性がありますが、これを行うにはもっと良い方法があると思い始めます。

たぶん私は球の中心とルートポイントを通る平面を取得し、それを球と交差させて測地線を取得しようとする必要がありますか?しかし、どうやって?

あまりにも冗長で、多分間違った英語で申し訳ありませんが、これは私の心を吹き飛ばしています!

編集: 以下のコードは問題なく動作します!みんなに感謝:

public void setRouteStart(double srcLat, double srcLng, double destLat, destLng) {
    //all angles are in radians
    u = Choords.sphericalToNormalized3D(srcLat, srcLng);
    v = Choords.sphericalToNormalized3D(destLat, destLng);
    double cos = u.dotProduct(v);
    angle = Math.acos(cos);
    if (Math.abs(cos) >= 0.999999) {
        u = new V3D(Math.cos(srcLat), -Math.sin(srcLng), 0);
    } else {
        v.subtract(u.scale(cos));
        v.normalize();
    }
}

public static V3D sphericalToNormalized3D( double radLat, double radLng) {
    //angles in radians
    V3D p = new V3D();
    double cosLat = Math.cos(radLat);
    p.x = cosLat*Math.cos(radLng);
    p.y = cosLat*Math.sin(radLng);
    p.z = Math.sin(radLat);
    return p;
}

public void setRouteDest(double lat, double lng) {
    EulerAngles tmp = new AngoliEulero(
       Math.toRadians(lat), 0, -Math.toRadians(lng));
    qtEnd.setInertialToObject(tmp);
    //do other stuff like drawing dest point...
}

public V3D interpolate(double totalTime, double t) {
    double _t = angle * t/totalTime;
    double cosA = Math.cos(_t);
    double sinA = Math.sin(_t);
    V3D pR = u.scale(cosA);
    pR.sum(
       v.scale(sinA)
    );
    return pR;
}

1
クォータニオンslerp /補間コードを表示してください。
Maik Semder

1
余談ですが、本でさえ観察されていないが悲惨なエラーを含む可能性があります。何かをコピーする前に、自分自身でそれを理解していることを確認してください。そうして初めて、それが有効であると数えることができます(まあ、自分で誤解していない限り-これははるかに可能性が低いです)。
teodron

@MaikSemderは、Quaternionクラスから開始してRotationMatrixをコンパイルする関数を追加しました
CaNNaDaRk

たぶん、問題はsetRouteDest(double lat、double lng)とsetRouteStart(double lat、double lng)の内部にあります。次のようにEulerAnglesオブジェクトを作成すると、角度が不足していると思います:EulerAngles tmp = new EulerAngles(Math.toRadians(lat)、???、
Math.toRadians

「RotationMatrix」を使用して「interpolate」から返された回転ポイント「p」を作成する場所がわかりません。補間されたクォータニオンから「RotationMatrix」を設定しただけで、それを使用していない
Maik Semder

回答:


5

あなたの問題は、球体の中心とソースポイントと宛先ポイントによって形成される平面で、純粋に2次元です。クォータニオンは3D球上の位置に加えて、方向をエンコードするため、クォータニオンを使用すると実際には事態がより複雑になります。

あなたはすでに円を補間する何かを持っているかもしれませんが、念のために、これはうまくいくはずのいくつかのコードです。

V3D u, v;
double angle;

public V3D geographicTo3D(double lat, double long)
{
    return V3D(sin(Math.toRadians(long)) * cos(Math.toRadians(lat)),
               cos(Math.toRadians(long)) * cos(Math.toRadians(lat)),
               sin(Math.toRadians(lat)));
}

public V3D setSourceAndDest(double srcLat, double srcLong,
                            double dstLat, double dstLong)
{
    u = geographicTo3D(srcLat, srcLong);
    V3D tmp = geographicTo3D(dstLat, dstLong);
    angle = acos(dot(u, tmp));
    /* If there are an infinite number of routes, choose
     * one arbitrarily. */
    if (abs(dot(u, tmp)) >= 0.999999)
        v = V3D(cos(srcLong), -sin(srcLong), 0);
    else
        v = normalize(tmp - dot(u, tmp) * u);
}

public V3D interpolate(double totalTime, double t)
{
    double a = t / totalTime * angle;
    return cos(a) * u + sin(a) * v;
}

はい!それが私が探しているものです、これは私の本当の問題です、C、目的地、およびソースと交差する平面で作業しなければなりません!唯一の問題は、それでも機能しないことです...コードと、コードから取得した結果を貼り付けます!
CaNNaDaRk 2012年

質問を編集しました。問題があります...多分私はコードを誤って翻訳しました???
CaNNaDaRk 2012年

@CaNNaDaRk何が悪いのかわかりません。副作用に関して、normalize()、subtract()、scale()などの使用は正しいですか?そしてt到達しtotalTimeますか?また、完全な円を取得したい場合は、単にの代わりにt最大値を作成します。2 * pi / angle * totalTimetotalTime
sam hocevar

パーフェクト!そこで、正規化関数に愚かなエラーを追加したため、「v」ベクトルの大きさが間違っていました。今、すべてがうまくいきます!再度ありがとう;)
CaNNaDaRk

3

両方の四元数が超球上の同じ半球上にあることを確認します。それらの内積が0より小さい場合、そうではありません。その場合は、それらの1つを否定する(その数をそれぞれ否定する)ため、それらは同じ半球上にあり、最短の経路を提供します。疑似コード:

quaternion from, to;

// is "from" and "to" on the same hemisphere=
if(dot(from, to) < 0.0)
{
    // put "from" to the other hemisphere, so its on the same as "to"
    from.x = -from.x;
    from.y = -from.y;
    from.z = -from.z;
    from.w = -from.w;
}

// now simply slerp them

ここでの私の答えは、四元数の各項を否定することで何が行われるのか、なぜそれが超球の反対側にあるのに同じ方向であるのかを詳しく説明しています。


補間関数を編集すると、次のようになります。

public V3D interpolate(double totalTime, double t) {
    double _t = t/totalTime;    
    Quaternion tmp;
    if(dot(qtStart, qtEnd) < 0.0)
    {
        tmp.x = -qtEnd.x;
        tmp.y = -qtEnd.y;
        tmp.z = -qtEnd.z;
        tmp.w = -qtEnd.w;
    }
    else
    {
        tmp = qtEnd;
    }
    Quaternion q = Quaternion.Slerp(qtStart, tmp, _t);
    RotationMatrix.inertialQuatToIObject(q);
    V3D p = matInt.inertialToObject(V3D.Xaxis.scale(sphereRadius));
    //other stuff, like drawing point ...
    return p;
}

私はあなたのコードを試しましたが、同じ結果を得ています。デバッグモードでは、ドット積を監視し、2つの四元数が超球上の同じ半球上にあることを確認します。申し訳ありませんが、多分私の質問は十分に明確ではありませんか?
CaNNaDaRk

「彼らが同じ半球にいると確信している」とはどういう意味ですか?ドット> = 0の場合はそうであり、そうでない場合はそうではありません。
Maik Semder

それは通常の球の半球ではなく、超球、四元数の4D空間での半球です。時間をかけてリンクを読んでください。コメントボックスで説明するのは難しいです。
Maik Semder

それが私が言っていることです、あなたのコードを入れてドットを計算しました。それが> = 0であっても(したがって、それらが同じ半球上にあると確信しています)、問題は常に同じです。私が得るルートは、大圏ではありません。問題はどこかにあると思います。多分私は質問にいくつかのコードを追加する必要があります...?編集:私はあなたのリンクを読みます、私はまだ問題がそこにあるとは思いません。私もあなたがくれたコードを試しましたが、それでもマイナーサークルのルートを取得しています。
CaNNaDaRk

補間コードを表示してください
Maik Semder

0

V3Dインターポレーターから戻る必要があるため、最も簡単な方法は、四元数を完全にスキップすることです。始点と終点を変換し、V3Dそれらの間をslerpします。

クォータニオンの使用を主張する場合、からPへの回転を表すクォータニオンには、Q方向P x QとがwありP . Qます。

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