定義済みの2D(xz)セグメントから一定の「降下率」で3Dベジェスプラインを構築するにはどうすればよいですか?

2020-05-23 typescript three.js geometry bezier

私は、ランダムに生成されたトラックにスプラインを使用する小さな3Dゲームの構築について調査してきました。これを視覚化するのに最も近いアナログは、 Impossible Roadのようものですが、衝突する物理的なボディではなく「パス」である可能性が高いので、その意味では、ゲームプレイはAudiosurfに似ています。

現在、私は1週間ほど経っていますが、賢明なスプラインを生成しようとすることについては、まだかなりのところにいます。私はGodotで始めましたが、最近、GDScriptよりもTypeScriptに少し慣れているため、Three JSに移動しました。これにより、プロジェクトのこの最初の部分について少し簡単に推論できるようになりました(切り替える可能性は十分にあります) Godotに戻ります。これを「ゲーム」のようなものに戻します)。

GodotとThreeにはどちらも便利なベジエスプラインクラスがあり、ベジエについてはかなり簡単に推論できるように思えたので、3次ベジエを使用してスプラインを作成し始めました。私のアイデアは、ランダムに生成されてランダムに生成されるトラックのさまざまな「プレハブセグメント」を定義することです。たとえば、「ハード左ターン」、「右Uターン」、「45度左ターン」などです。これは、インポッシブルロードがしているように見えます(ビデオでは、チェックポイントは常にセグメント間の「接着点」であることに注意してください)。

私が「XZ」平面に住んでいて、高さをまったく扱っていない間、これは大丈夫です。セグメントを「平らな」形状として定義しました:

const prefabs = {
  leftTurn: {
    curve: new CubicBezierCurve3(
      new Vector3(0, 0, 0),
      new Vector3(0, 0, 0),
      new Vector3(0, 0, -1),
      new Vector3(-1, 0, -1)
    )
  }
};

Y軸に沿って平らであるため、これは2Dでちょうど90度回転しています。

これにより、ピースを簡単に接着することができました。パスを[leftTurn, rightTurn, leftTurn]として定義した場合、これらのセグメントからカーブを生成するとき、各出口で接線を追跡し、原点を中心にピースを回転させて「ヨー」に一致させます接線で表されます(つまり、y軸の周りの回転/ xz平面上):

/**
 * Transform a list of prefab names ("leftTurn", "rightTurn") to a series of
 * Bezier curves.
 */
export function convertPiecesToSplineSegments(
  pieces: string[]
): SplineSegment[] {
  let enterHeading = new Vector3(0, 0, -1).normalize();
  let enterPoint = new Vector3(0, 0, 0);

  return pieces.map((piece) => {
    const prefab = prefabs[piece];

    // get the angle between (0, 0, -1) and the current heading to figure out
    // how much to rotate the piece by.
    //
    // via https://stackoverflow.com/a/33920320
    const yaw = Math.atan2(
      enterHeading
        .clone()
        .cross(new Vector3(0, 0, -1))
        // a lil weirded out i had to use the negative y axis here, not sure what's
        // going on w that...
        .dot(new Vector3(0, -1, 0)),
      new Vector3(0, 0, -1).dot(enterHeading)
    );

    const transform = (v: Vector3): Vector3 => {
      return v
        .clone()
        .applyAxisAngle(new Vector3(0, 1, 0), yaw)
        .add(enterPoint);
    };

    const a = transform(prefab.curve.v0);
    const b = transform(prefab.curve.v1);
    const c = transform(prefab.curve.v2);
    const d = transform(prefab.curve.v3);
    const curve = new CubicBezierCurve3(a, b, c, d);

    enterHeading = d.clone().sub(c).normalize();
    enterPoint = d;

    return {
      curve,
    }
  }
}

これは本当にうまくいきました!プレハブのカーブに沿って「ロール」を定義できるようにするためのロジックを追加して、角度を設定したり、法線を生成するためにいくつかのものを使用して、角度に基づいてターンを「バンク」したりすることができました(これを「接線のローカル空間のZ軸を中心に回転」と呼びますか?)。

私がここにたどり着いた場所のデモを見ることができます:

https://disco.zone/splines/1

WASDとマウスルックを使って見回すことができます。私の目では、大丈夫なようです!

次に、高さを追加してみました。そして、すべてが非常に悪かった。

私の唯一の目標は、スプラインが常に同じ速度で下降することです。つまり、xz平面上の距離あたりのy軸上の同じ変位です。最終的に各曲線の下降量をランダムに変化させる方法を理解するのは良いことかもしれませんが、今のところ、物事を一定に保つ方が簡単だと思います。それでも、これに必要な数学に問題があります。

私は最初に単純に、現在のヨーの向きで各ピースを回転させるのと同じように、「ピッチ」を使用して同じように、各ポイントをカーブの原点に対して15度下に回転させることができると考えました。その問題は、Uターンですぐに明らかになります。

平らな曲線を取り、任意の1つの軸で「回転」させると、 曲線全体が1つの単位として回転します。これは実際には90度カーブの世界ではうまく機能しますが、180度カーブの世界ではそれほどうまくいきません。

したがって、明らかに、回転は私が望むものではありません。下降曲線のポイントにyを追加する必要があります。そして、これは物事がトリッキーになる場所です。

私が理解しているように、ベジェスプラインの問題は、それらに連続性を持たせたい場合、つまり鋭い点を持たせない場合、曲線nのt = 0での接線がでの接線と同じでなければならないということです。曲線n-1のt = 1(これは、ほとんど理解していない数学の説明では「C1の連続性」と呼ばれています)。これは私には理にかなっており、「2D」の世界でも簡単に実行できました。文字通り、新しいセグメントを回転させて、前のものと正確に接線を合わせるだけでした。フラットなので、心配する必要があるだけです」そうすることで「ヨー」角度。

正確に言えば、私はこの行動を高さからどのように得るのかについて、少し無知です。直感的には、「ああ、たぶんすべてが線形の下降率を持つだけかもしれない」と思ったのですが、これをどのように計算するのかわかりません。これが点で定義された一連の線分である場合:

a=(0, 0, 0)
b=(0, 0, -1/3)
c=(0, 0, -2/3)
d=(0, 0, -1)

次に、一定の下降率を適用するのは簡単です。Y値を-1 / -1/3-2/3 、および-1bc 、およびd追加するだけです。 badcはどちらも(0, 0, -1/3)になるので、接線はずっと下で等しくなります。

実際には、これらはまあ、 曲線なので、それほど単純ではありません。私はあなたのXZ距離を計算する必要があると思う bcから、スケールa y適切に、これは実際にはどのような方法でrasonableアプローチであれば、私はよく分かりません。私はランダムな「壁にコードを投げる」ことを試みて、私が望んだものに似たものを思いつくかどうかを確認しましたが、これまでのところ何も機能していないようです。

私は確かに限られた数学の知識でできる限り最善の方法でこれをグーグルで試しましたが、足りませんでした。スプラインの作成とレンダリングについては多くの資料がありますが、このようなものをカバーするであろうと思われる曲線のスムーズな生成に関する資料はあまり見ていません。

さらに、これにベジェスプラインを使用しようとすると、間違ったツリーを鳴らしているのではないかと思います。BスプラインまたはCatmull-Romスプラインを使用すると、連続パスを簡単に作成できますか?私はそれらが最も文字通りの意味であると知っていますが、それらのスプラインが使用できるという意味で私の「セグメント」を定義するかどうかはよくわかりません。

これまでのところ私のコード全体がここにあります。問題を理解するためにそれを読む必要がないことを願っていますが、解決策を提供するのに役立つかもしれません: https : //github.com/thomasboyt/rascal

Answers

私は@Spektreが示唆したものに近い方法でこれを解決することになりました:

ベジエスプライン上の一定の勾配の完璧な制御点を見つけるのではなく、XZ平面上に「2D」スプラインとしてスプラインを生成しました。次に、スプ​​ラインが実際にレンダリング/計算されたときに、 生成されたポイントに直線的に高さを追加しました。

これは、振り返ってみると明らかでしたが、ベジェコントロールポイントを使用してこれを「正しい方法」で生成するという考えに行き詰まりすぎました。これはベジェ曲線で可能であるように見えます-数学は私を超えていますが、友人が私にこれをカバーしていると私が信じているらせんの描画に関するこの記事をリンクしました。

非線形の高さ変位(つまり、各セグメントに対してランダムに生成された高さ)を追加することも、この方法ではそれほど問題ではありませんでした。最初にランダムな高さの束を生成し、次に各点でx = tとy = heightの2D Catmull-Romスプラインでそれらを補間しました。これにより、不連続性の問題が解決されるようです。

結果はここにあり、私の目にはかなり喜んで見えますhttps : //disco.zone/splines/3/

Related