固定軸を中心にオブジェクトを回転


12

私はアプリのユーザーに、画面上で指をドラッグして、画面の中央に描かれた3Dオブジェクトを回転させようとしています。画面上の水平方向の動きは、固定されたY軸の周りの回転を意味し、垂直方向の動きは、X軸の周りの回転を意味します。私が抱えている問題は、1つの軸の周りの回転を許可するだけでオブジェクトが正常に回転するのに、2番目の回転を導入するとすぐにオブジェクトが期待どおりに回転しないことです。

これが何が起こっているかの写真です:

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

青い軸は固定軸を表します。この固定された青い軸を持つ画面を描きます。これは、オブジェクトに関連して回転させたいものです。何が起こっているのかは赤です。

私が知っていることは次のとおりです。

  1. Y(0、1、0)を中心とした最初の回転により、モデルは青いスペース(このスペースAと呼ばれる)から別のスペース(このスペースBと呼ばれる)に移動します。
  2. ベクトル(1、0、0)を使用して再度回転しようとすると、スペースAではなくスペースBでx軸を中心に回転します。

私が知っていることを考えると、私が試したものがあります(簡潔にするためにW座標を除外します):

  1. 最初に、クォータニオンを使用してY(0、1、0)を中心に回転します。
  2. 回転Yクォータニオンをマトリックスに変換します。
  3. Y回転行列に固定軸x Vector(1、0、0)を掛けて、新しい空間に関連するX軸を取得します。
  4. クォータニオンを使用して、この新しいXベクトルを中心に回転します。

コードは次のとおりです。

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};
    float[] rotationY = Quaternion.fromAxisAngle(yAxis, -angleX).toMatrix();

    // multiply x axis by rotationY to put it in object space
    float[] xAxisObjectSpace = new float[4];
    multiplyMV(xAxisObjectSpace, 0, rotationY, 0, xAxis, 0);

    float[] rotationX = Quaternion.fromAxisAngle(xAxisObjectSpace, -angleY).toMatrix();

    float[] rotationMatrix = new float[16];
    multiplyMM(rotationMatrix, 0, rotationY, 0, rotationX, 0);
    return rotationMatrix;
  }

これは期待どおりに機能しません。回転は機能しているように見えますが、ある時点で水平方向の動きがY軸を中心に回転せず、Z軸を中心に回転しているように見えます。

私の理解が間違っているのか、他の何かが問題を引き起こしているのかはわかりません。回転以外にも、オブジェクトに対して行っている他の変換がいくつかあります。回転を適用する前にオブジェクトを中心に移動します。上記の関数から返されたマトリックスを使用して回転し、Z方向に-2変換して、オブジェクトが見えるようにします。私はこれが私の回転を台無しにしているとは思わないが、とにかくそれのためのコードはここにある:

private float[] getMvpMatrix() {
    // translates the object to where we can see it
    final float[] translationMatrix = new float[16];
    setIdentityM(translationMatrix, 0);
    translateM(translationMatrix, 0, translationMatrix, 0, 0f, 0f, -2);

    float[] rotationMatrix = rotationMatrix();

    // centers the object
    final float[] centeringMatrix = new float[16];
    setIdentityM(centeringMatrix, 0);
    float moveX = (extents.max[0] + extents.min[0]) / 2f;
    float moveY = (extents.max[1] + extents.min[1]) / 2f;
    float moveZ = (extents.max[2] + extents.min[2]) / 2f;
    translateM(centeringMatrix, 0, //
      -moveX, //
      -moveY, //
      -moveZ //
    );

    // apply the translations/rotations
    final float[] modelMatrix = new float[16];
    multiplyMM(modelMatrix, 0, translationMatrix, 0, rotationMatrix, 0);
    multiplyMM(modelMatrix, 0, modelMatrix, 0, centeringMatrix, 0);

    final float[] mvpMatrix = new float[16];
    multiplyMM(mvpMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
    return mvpMatrix;
  }

私はこれに数日間こだわっています。ヘルプは大歓迎です。

================================================= ================

更新:

これをUnityで機能させるのは簡単です。以下は、原点を中心に立方体を回転させるコードです。

public class CubeController : MonoBehaviour {

    Vector3 xAxis = new Vector3 (1f, 0f, 0f);
    Vector3 yAxis = new Vector3 (0f, 1f, 0f);

    // Update is called once per frame
    void FixedUpdate () {
        float horizontal = Input.GetAxis ("Horizontal");
        float vertical = Input.GetAxis ("Vertical");

        transform.Rotate (xAxis, vertical, Space.World);
        transform.Rotate (yAxis, -horizontal, Space.World);
    }
}

回転を期待どおりに動作させる部分は、変換の関数Space.WorldへのパラメーターですRotate

Unityを使用できる場合、残念ながら自分でこの動作をコーディングする必要があります。


1
ここでの私の答えgamedev.stackexchange.com/questions/67199/…はあなたを助けるかもしれません..
concept3d

私はあなたの答えの背後にある概念を理解していますが、実装する方法は私を免れます。
クリストファーペリー

他の回答を確認した場合、syntacの回答は私が説明したアイデアを実装しています。
concept3d

いいえ、ありません。異なる軸を中心に複数の回転を行っています。単一の回転を行うことをお勧めします。
クリストファーペリー

回答:


3

あなたが持っている問題は、ジンブルロックと呼ばれます。あなたがやろうとしていることはアークボール回転と呼ばれていると思いますます。アークボールの計算は少し複雑になる可能性があります。

より簡単な方法は、画面上の2dスワイプに垂直な2dベクトルを見つけることです。

ベクトルを取得し、平面近くのカメラに投影して、ワールド空間で3Dベクトルを取得します。スクリーンスペースからワールドスペース

次に、このベクトルを使用してクォータニオンを作成し、それをゲームオブジェクトに乗算します。おそらくいくつかのずんぐりしたまたはlerp遷移。

編集:

ユニティの例:ユニティの例では、ゲームオブジェクトの回転の内部状態は行列ではなく四元数です。transform.rotationメソッドは、指定されたベクトルと角度に基づいてクォータニオンを生成し、そのクォータニオンにゲームオブジェクトの回転クォータニオンを乗算します。後の時点でレンダリングまたは物理学のための回転行列のみを生成します。四元数は付加的であり、ジンブルロックを回避します。

コードは次のようになります。

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};

    Quaternion qY = Quaternion.fromAxisAngle(yAxis, angleX);
    Quaternion qX = Quaternion.fromAxisAngle(xAxis, -angleY);

    return (qX * qY).getMatrix(); // should probably represent the gameobjects rotation as a quaternion(not a matrix) and multiply all 3 quaternions before generating the matrix. 
  }

ArcBall Rotation Openglチュートリアル


私はジンバルロックを取得していません。最初の回転で軸が移動するため、2番目の回転は移動した軸に基づきます。私が提供した写真をもう一度見てください。
クリストファーペリー

あなたがそれを理解したことを願っています。要するに。四元数を一緒に乗算して、回転を適用できます。すべての回転計算の最後にのみマトリックスを生成する必要があります。また、xQ * yQはyQ * xQと等しくありません。クリストファー・ペリーが言ったように、四元数は非可換です。
アンソニーライモンド

ここにすべてのコードを入れます。私はすべてを試したように感じます。他の誰かがこれに目を向けると、私の間違いが見つかるかもしれません。
クリストファーペリー

私はそれを受け入れませんでした、スタック交換アルゴリズムはあなたにポイントを自動的に割り当てました。:/
クリストファーペリー

この不公平についてすみません。
アンソニーライモンド

3

蓄積された回転行列を回転させることで、期待通りの回転を得ることができました。

setIdentityM(currentRotation, 0);
rotateM(currentRotation, 0, angleY, 0, 1, 0);
rotateM(currentRotation, 0, angleX, 1, 0, 0);

// Multiply the current rotation by the accumulated rotation,
// and then set the accumulated rotation to the result.
multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);

1

画像はrotationMatrixコードに対応します。x軸を以前のy回転で回転させると、ローカルx軸が得られます。その後、オブジェクトを回転させると、画像に表示される結果が得られます。ユーザーの視点から論理的に回転させるには、代わりにワールド座標軸を使用してオブジェクトを回転させます。

ユーザーがオブジェクトを何度もスピンできるようにしたい場合は、マトリックスではなく四元数に回転を保存するのが理にかなっています。時間の経過とともに複数のスピン(および浮動小数点の不正確さ)により、マトリックスは次第に見えなくなります回転行列の場合、同じことはもちろん四元数でも起こりますが、四元数を正規化するだけで適切な回転に戻ります。

アイデンティティクォータニオンを初期値として使用し、ユーザーが画面をスワイプするたびに、Quaternion.fromAxisAngle(yAxis, -angleX)コードでクォータニオンを回転させます。x回転には常に(1,0,0,1)を使用し、y回転には(0,1,0,1)を使用します。

static final float[] xAxis = {1f, 0f, 0f, 1f};
static final float[] yAxis = {0f, 1f, 0f, 1f};

private void rotateObject(float angleX, float angleY) {
  Quaternion rotationY = Quaternion.fromAxisAngle(yAxis, -angleX);
  Quaternion rotationX = Quaternion.fromAxisAngle(xAxis, -angleY);

  myRotation = myRotation.rotate(rotationY).rotate(rotationX).normalize();
}
private float[] rotationMatrix() {
  return myRotation.toMatrix();
}

言語や特定のフレームワークについては言及しなかったので、Quaternionのメソッドはもちろん別の方法で呼び出される場合があり、それを頻繁に呼び出すためにノーマライズは必要ありませんが、回転はユーザーが画面をスワイプすることで行われるため、物事が非常に遅くなり、そのようにすると、クォータニオンがユニットクォータニオンから外れることはありません。


これを行うとまったく同じ動作になります。
クリストファーペリー

1
@ChristopherPerryローテーションの順序を逆にしてみてください。myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()これらは交換可能ではないので、それらを行う順序は重要です。うまくいかなかった場合は、フレームワーク/言語に別のコメントを追加し、もう少し詳しく調べます。
ダニエルカールソン

それも機能しません、私は同じ動作を取得します。
クリストファーペリー

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