3

ユーザーがマウス/指を球の上に動かしたときに、3D 球を回転させようとしています。

問題なく回転させることができますが、Surface SDK の Affine2DInertiaProcessor を使用して球体に慣性を追加しようとすると、球体をすばやくフリックするとジャンプの問題が発生し、その理由がわかりません...

ここに私の初期化コードがあります:

    private void InitializeManipulationProcessor()
    {
        manipulationProcessor = new Affine2DManipulationProcessor(
            Affine2DManipulations.Rotate | 
            Affine2DManipulations.TranslateX | 
            Affine2DManipulations.TranslateY,
            _eventSource);


        inertiaProcessor = new Affine2DInertiaProcessor();
        inertiaProcessor.Affine2DInertiaDelta += Inertia_OnManipulationDelta;
        inertiaProcessor.Affine2DInertiaCompleted += InertiaProcessor_Affine2DInertiaCompleted;

        manipulationProcessor.Affine2DManipulationStarted += OnManipulationStarted;
        manipulationProcessor.Affine2DManipulationDelta += Manipulation_OnManipulationDelta;
        manipulationProcessor.Affine2DManipulationCompleted += OnManipulationCompleted;
}

ユーザーが指を動かしたときに、球を回転させるコードは次のとおりです。

private void Manipulation_OnManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
    {
        Point currentPosition = e.ManipulationOrigin;
        // avoid any zero axis conditions
        if (currentPosition == _previousPosition2D)
            return;

        Track(currentPosition);

        _previousPosition2D = currentPosition;
    }

これは、ユーザーが指の動きを止めると、慣性を開始します。

private void OnManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
{
    inertiaProcessor.InitialOrigin = e.ManipulationOrigin;
    inertiaProcessor.InitialVelocity = e.Velocity;
    inertiaProcessor.DesiredDeceleration = 0.0001;
    inertiaProcessor.Begin();
}

回転の魔法は、以下のTrackメソッドで発生します。

    private void Track(Point currentPosition)
    {
        Vector3D currentPosition3D = ProjectToTrackball(currentPosition);

        Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
        double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);

        // quaterion will throw if this happens - sometimes we can get 3D positions that
        // are very similar, so we avoid the throw by doing this check and just ignoring
        // the event 
        if (axis.Length == 0)
            return;

        Quaternion delta = new Quaternion(axis, -angle);

        // Get the current orientantion from the RotateTransform3D
        Quaternion q = new Quaternion(_rotation.Axis, _rotation.Angle);

        // Compose the delta with the previous orientation
        q *= delta;

        // Write the new orientation back to the Rotation3D
        _rotation.Axis = q.Axis;
        _rotation.Angle = q.Angle;

        _previousPosition3D = currentPosition3D;
    }

_rotation変数は、3D メッシュのRotateTransform3Dに使用されるAxisAngleRotation3Dクラスです。

これが特殊なケースであることはわかっていますが、これは計算上の問題であると感じており、これをデバッグする方法が本当にわかりません。

もう 1 つ、非常に興味深い点として、地球儀をゆっくりとフリックすると、ジャンプすることはなく、非常にスムーズです。したがって、それは大規模な計算、または単なるバグと関係があるに違いありません...

あなたが 3D 回転が得意で、あなたが助けてくれると本当に信じているなら、このプロジェクトを ZIP にパッケージ化して、より良いフォーマットが必要な場合はお送りします。

あなたが与えることができるどんな助けにも感謝します、私は本当に助けに感謝します!

マーク

4

2 に答える 2

1

明確な答えはありませんが、あなたのコードビットを見ると、多くのことが奇妙に思えます。

まず、ProjectToTrackball は正確には何をするのでしょうか? 2D 慣性を使用しているので、2D ポイント (スクリーン スペース内) を球体に投影し、3D ポイントを返すと思いますよね? では、2D ポイントが画面上の球の外側にある場合、正確にはどうなるでしょうか? 還元されるポイントは?球に指を置いて動き始め、慣性 2D によって動きが球の外に出るとどうなりますか?

次に、Track メソッドで回転を処理する方法について説明します。クォータニオンについてはよくわかりませんが、3D 回転をモデル化するには、3 つの軸と 3 つの角度 (オイラー角) が必要であることは確かです。各ステップで、1 つの軸と 1 つの角度だけでクォータニオンを上書きしています。これは、指を一方向にのみ動かした場合に機能します。移動中に方向を変えると、これは機能しません。

余談ですが、デルタ四元数で直接「角度」ではなく「-角度」を使用する理由がわかりませんが、そこにバグがあった場合はすぐに気付くと思います;)

編集: .rar をダウンロードしました。

ProjectToTrackball がどのように機能するか (SurfaceTrackballDecorator.cs 内) を調べたところ、何が起こっているのかがよくわかりました。

まず、球体が画面全体に一致する必要があります (つまり、画面が正方形でなくても画面の 4 辺に接触する必要があります)。そうしないと、動きが正常に動作しません。そうでない場合は、球と画面の端の間のスペースで球を回転できるはずですが、これは私が推測する望ましい効果ではありません。

次に、慣性が機能するときに何が起こるかというと、あたかも指が動き続けているかのように (ゆっくりと減速します)、動きは 3D ではなく2 次元で続きます。

動きが球の端に当たると、2D ポイントを 3D 球に投影する魔法により、球が非常に速く回転します。そのため、指が球の端に達していなくても、inertia2D を使用するとこれを実現できます。

ここで面白いのは、2D 点が球に投影されなくなると (球のエッジを横切るとき)、動きが激しく停止することです。すべての 2D 点が (z=0) 平面に投影されるためです。

これが「ジャンプ球」の意味かどうかはわかりません:)

これを解決するには、2D ポイントの移動ではなく、3D 回転を遅くするある種の慣性 3D が必要です。

私の以前のポイントはまだ有効です。お役に立てれば。

編集:眠れません:)

このコードを試していただけますか?

private Vector3D ProjectToTrackball(Point point)
{
  double x = point.X / (ActualWidth / 2);    // Scale so bounds map to [0,0] - [2,2]
  double y = point.Y / (ActualHeight / 2);

  x = x - 1;                           // Translate 0,0 to the center
  y = 1 - y;                           // Flip so +Y is up instead of down
  double alpha = 1.0 / Math.Sqrt(x*x + y*y + 1);
  return new Vector3D(x*alpha, y*alpha, alpha);
}

私は何も保証しませんが、これははるかに滑らかになるはずであり(おそらく過度に)、球の端に不連続性がなくなります...

唯一の角度の問題はまだ気になりますが...

編集:新しいもの:

  private Vector3D ProjectToTrackball(Point point)

  {
    // IMPORTANT NOTE: result should always be normalized

    double x = point.X / (ActualWidth / 2);    // Scale so bounds map to [0,0] - [2,2]

    double y = point.Y / (ActualHeight / 2);



    x = x - 1;                           // Translate 0,0 to the center

    y = 1 - y;                           // Flip so +Y is up instead of down



    double z2 = 1 - x * x - y * y;       // z^2 = 1 - x^2 - y^2
    double z =  0;

    if(z2 > 0)
      z2 = Math.Sqrt(z2); // Ok no need to normalize.
    else
    {
      // I will get rid of the discontinuity with a little trick:
      // I construct an imaginary point below the sphere.
      double length = Math.Sqrt(x * x + y * y);
      x = x / length;
      y = y / length;
      z = 1 - length;
      // Now I normalize:
      length = Math.Sqrt(x * x + y * y + z * z);
      x = x / length;
      y = y / length;
      z = z / length;
    }

    return new Vector3D(x, y, z);

  }

これで、指が球の内側にある場合の動きについては、以前と同じように動作するはずです。球の端を横切るときの不連続性がなくなりました。動きは急速に遅くなるはずです。

2 番目の編集で間違いを犯しました。ジャンプはおそらく、2D ポイントが球体に投影されなくなったときに、ProjectToTrackball が正規化されていないベクトルを返すという事実から生じたものです。その後、すべてが狂った。

注: OpenGL の本を手に取り、3D を学ぶ必要があります。

2009 年 11 月 18 日の新しい編集:

唯一の角度の問題については、「Z 軸の周りにしか回転しない」問題の原因であると思います。

最初に _rotation をクォータニオンに変更します。メッシュに _rotation を掛ける方法でいくつかのコードを変更する必要がありますが、それほど難しくはありません。

次に、この新しい Track メソッドを試すことができます。

private void Track(Point currentPosition)
{
    Vector3D currentPosition3D = ProjectToTrackball(currentPosition);

    Vector3D axis = Vector3D.CrossProduct(_previousPosition3D, currentPosition3D);
    double angle = Vector3D.AngleBetween(_previousPosition3D, currentPosition3D);

    // quaterion will throw if this happens - sometimes we can get 3D positions that
    // are very similar, so we avoid the throw by doing this check and just ignoring
    // the event 
    if (axis.Length == 0)
        return;

    Quaternion delta = new Quaternion(axis, -angle);

    // Compose the delta with the previous orientation
    _rotation *= delta;

    _previousPosition3D = currentPosition3D;
}

慣性についてはあきらめました... ある種の 3D 回転慣性が必要です。

于 2009-11-17T12:01:09.290 に答える
0

私はここで暗闇の中で巨大な刺し傷を取っていますが、あなたが立てたコードから判断すると、ManipulationOriginプロパティがManipulation_OnManipulationDeltaイベントとOnManipulationCompletedイベントの間で劇的に異なっていたらどうなるでしょうか?

ユーザーが指をすばやくフリックすると、イベント間で値が大幅に異なる可能性があり、これが発生している「ジャンプ」症状の原因となる可能性があるように思われます。

于 2009-11-12T18:57:03.037 に答える