変換行列からオイラー角を抽出する方法は?


12

エンティティ/コンポーネントゲームエンジンを簡単に実現できます。
Transformコンポーネントには、ローカル位置、ローカル回転、グローバル位置、グローバル回転を設定するメソッドがあります。

変換が新しいグローバル位置に設定されている場合、ローカル位置も変更され、ローカル位置が更新されます。このような場合は、現在の変換ローカル行列を親の変換ワールド行列に適用するだけです。

それまでは問題ありませんが、更新されたローカル変換行列を取得できます。
しかし、変換でローカル位置と回転値を更新する方法に苦労しています。私が考えている唯一の解決策は、変換のlocalMatrixから並進と回転の値を抽出することです。

翻訳の場合は非常に簡単です。4番目の列の値を取得するだけです。しかし、回転はどうですか?
変換行列からオイラー角を抽出する方法は?

そのような解決策は正しいですか?:
Z軸を中心とした回転を見つけるには、localTransformのX軸ベクトルとparent.localTransformのX軸ベクトルの差を見つけて、結果をDeltaに格納します。次に、localRotation.z = atan2(Delta.y、Delta 。バツ);

XとYを中心とした回転についても同じですが、軸を交換する必要があります。

回答:


10

通常、私はすべてのオブジェクトを4x4と3つのベクトルのセット(移動、回転、スケール)の間で前後に変換する代わりに、4x4の行列(3x3を実行できますが、1つのクラスを用意する方が簡単です)として保存します。オイラー角は特定のシナリオで処理することが難しいことで悪名高いので、マトリックスではなくコンポーネントを本当に保存したい場合は、クォータニオンを使用することをお勧めします。

しかし、ここで私がしばらく前に見つけたいくつかのコードが動作します。これが役に立てば幸いですが、残念ながら、これを見つけた元のソースがありません。私はそれがうまくいかないかもしれない奇妙なシナリオを知りません。私は現在、これを使用して、YawPitchRollの回転、左利きの4x4行列の回転を取得しています。

   union {
        struct 
        {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;
        };
        float m[4][4];
        float m2[16];
    };

    inline void GetRotation(float& Yaw, float& Pitch, float& Roll) const
    {
        if (_11 == 1.0f)
        {
            Yaw = atan2f(_13, _34);
            Pitch = 0;
            Roll = 0;

        }else if (_11 == -1.0f)
        {
            Yaw = atan2f(_13, _34);
            Pitch = 0;
            Roll = 0;
        }else 
        {

            Yaw = atan2(-_31,_11);
            Pitch = asin(_21);
            Roll = atan2(-_23,_22);
        }
    }

これは、私の質問に答えようとしたときに見つけた別のスレッドです。私のスレッドと同様の結果のようです。

/programming/1996957/conversion-euler-to-matrix-and-matrix-to-euler


私の提案したソリューションはほぼ正しいようですが、ピッチにatan2 asinが使用されていない理由がわからないだけです。

また、各コンポーネントを別々のmat4x4に格納する場合、どのように役立ちますか?どのようにしたら、たとえばいくつかの軸の周りの回転角度を取得して出力できますか?

あなたの元の質問は、オブジェクトを3つのvector3として保存していると私に信じさせました:移動、回転、およびスケール。次に、いくつかの作業を行った後でlocalTransformを作成し、後で(localTransform * globalTransform)を3つのvector3に変換しようとします。私はその印象をちょうど得ていた全く間違っているかもしれません。
NtscCobalt 2013年

ええ、私は数学がASINでなぜ行われるのかについて十分な数学を知りませんが、リンクされた質問は同じ数学を使用しているので、それは正しいと思います。私はこの機能をしばらく問題なく使用してきました。
NtscCobalt 2013年

最初の2つのifケースでatan2fを使用し、3番目のケースでatan2を使用する特別な理由はありますか、それともタイプミスですか?
マティアスF

10

Mike Dayによるこのプロセスに関するすばらしい記事がありますhttps ://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

また、バージョン0.9.7.0、2015年2月8日の時点でglmにも実装されています。 実装を確認してください

数学を理解するには、回転行列にある値を確認する必要があります。さらに、値を適切に抽出するために、マトリックスを作成するために回転が適用された順序を知る必要があります。

オイラー角からの回転行列は、x、y、およびz軸の周りの回転を組み合わせることによって形成されます。たとえば、Zを中心としたθ度の回転は、行列

      cosθ  -sinθ   0 
Rz =  sinθ   cosθ   0 
        0      0    1 

X軸とY軸を中心に回転するための同様の行列が存在します。

       1    0     0   
Rx =   0  cosθ  -sinθ 
       0  sinθ   cosθ 

       cosθ  0   sinθ 
Ry =    0    1    0   
      -sinθ  0   cosθ 

これらの行列を乗算して、3つの回転すべての結果である1つの行列を作成できます。行列の乗算は可換ではないので、これらの行列が一緒に乗算される順序が重要であることに注意することが重要です。つまりRx*Ry*Rz ≠ Rz*Ry*Rx。可能な回転順序の1つであるzyxについて考えてみましょう。3つの行列を組み合わせると、次のような行列になります。

               CyCz              -CySz        Sy  
RxRyRz =   SxSyCz + CxSz   -SxSySz + CxCz   -SxCy 
          -CxSyCz + SxSz    CxSySz + SxCz    CxCy 

どこCxの余弦れるx回転の角度は、Sxのサインであるx等、回転の角度

さて、課題はオリジナルを抽出することであるxyz行列に入った値。

まずはx角度を出しましょう。私たちが知っている場合sin(x)cos(x)、我々は、逆正接関数を使用することができatan2、私たちは私たちの角度をバック与えます。残念ながら、これらの値はそれ自体ではマトリックスに表示されません。しかし、要素M[1][2]とを詳しく見てみるとM[2][2]-sin(x)*cos(y)だけでなくも知っていることがわかりcos(x)*cos(y)ます。正接関数は三角形の反対側と隣接する側の比率であるため、両方の値を同じ量(この場合はcos(y))スケーリングすると、同じ結果になります。したがって、

x = atan2(-M[1][2], M[2][2])

では、取得してみましょうysin(y)から知っていM[0][2]ます。cos(y)があれば、atan2再び使用できますが、マトリックスにはその値がありません。ただし、ピタゴラスのアイデンティティにより、次のことがわかります。

cosY = sqrt(1 - M[0][2])

したがって、次のように計算できますy

y = atan2(M[0][2], cosY)

最後に、を計算する必要がありますz。これが、Mike Dayのアプローチが前の回答と異なるところです。この時点xy回転量と回転量がわかっているので、XY回転行列を作成し、zターゲット行列と一致させるために必要な回転量を見つけることができます。RxRy行列は次のようになります。

          Cy     0     Sy  
RxRy =   SxSy   Cx   -SxCy 
        -CxSy   Sx    CxCy 

RxRy* Rzが入力行列と等しいことがわかっているのでM、この行列を使用して次のように戻ることができますRz

M = RxRy * Rz

inverse(RxRy) * M = Rz

回転行列逆はその転置なので、これを次のように拡張できます。

 Cy   SxSy  -CxSy ┐┌M00  M01  M02    cosZ  -sinZ  0 
  0    Cx     Sx  ││M10  M11  M12 =  sinZ   cosZ  0 
 Sy  -SxCy   CxCy ┘└M20  M21  M22      0      0   1 

私たちは、今のところ解決することができますsinZし、cosZ行列の乗算を行うことで。要素[1][0]とを計算するだけです[1][1]

sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)

参照用の完全な実装は次のとおりです。

#include <iostream>
#include <cmath>

class Vec4 {
public:
    Vec4(float x, float y, float z, float w) :
        x(x), y(y), z(z), w(w) {}

    float dot(const Vec4& other) const {
        return x * other.x +
            y * other.y +
            z * other.z +
            w * other.w;
    };

    float x, y, z, w;
};

class Mat4x4 {
public:
    Mat4x4() {}

    Mat4x4(float v00, float v01, float v02, float v03,
            float v10, float v11, float v12, float v13,
            float v20, float v21, float v22, float v23,
            float v30, float v31, float v32, float v33) {
        values[0] =  v00;
        values[1] =  v01;
        values[2] =  v02;
        values[3] =  v03;
        values[4] =  v10;
        values[5] =  v11;
        values[6] =  v12;
        values[7] =  v13;
        values[8] =  v20;
        values[9] =  v21;
        values[10] = v22;
        values[11] = v23;
        values[12] = v30;
        values[13] = v31;
        values[14] = v32;
        values[15] = v33;
    }

    Vec4 row(const int row) const {
        return Vec4(
            values[row*4],
            values[row*4+1],
            values[row*4+2],
            values[row*4+3]
        );
    }

    Vec4 column(const int column) const {
        return Vec4(
            values[column],
            values[column + 4],
            values[column + 8],
            values[column + 12]
        );
    }

    Mat4x4 multiply(const Mat4x4& other) const {
        Mat4x4 result;
        for (int row = 0; row < 4; ++row) {
            for (int column = 0; column < 4; ++column) {
                result.values[row*4+column] = this->row(row).dot(other.column(column));
            }
        }
        return result;
    }

    void extractEulerAngleXYZ(float& rotXangle, float& rotYangle, float& rotZangle) const {
        rotXangle = atan2(-row(1).z, row(2).z);
        float cosYangle = sqrt(pow(row(0).x, 2) + pow(row(0).y, 2));
        rotYangle = atan2(row(0).z, cosYangle);
        float sinXangle = sin(rotXangle);
        float cosXangle = cos(rotXangle);
        rotZangle = atan2(cosXangle * row(1).x + sinXangle * row(2).x, cosXangle * row(1).y + sinXangle * row(2).y);
    }

    float values[16];
};

float toRadians(float degrees) {
    return degrees * (M_PI / 180);
}

float toDegrees(float radians) {
    return radians * (180 / M_PI);
}

int main() {
    float rotXangle = toRadians(15);
    float rotYangle = toRadians(30);
    float rotZangle = toRadians(60);

    Mat4x4 rotX(
        1, 0,               0,              0,
        0, cos(rotXangle), -sin(rotXangle), 0,
        0, sin(rotXangle),  cos(rotXangle), 0,
        0, 0,               0,              1
    );
    Mat4x4 rotY(
         cos(rotYangle), 0, sin(rotYangle), 0,
         0,              1, 0,              0,
        -sin(rotYangle), 0, cos(rotYangle), 0,
        0,               0, 0,              1
    );
    Mat4x4 rotZ(
        cos(rotZangle), -sin(rotZangle), 0, 0,
        sin(rotZangle),  cos(rotZangle), 0, 0,
        0,               0,              1, 0,
        0,               0,              0, 1
    );

    Mat4x4 concatenatedRotationMatrix =
        rotX.multiply(rotY.multiply(rotZ));

    float extractedXangle = 0, extractedYangle = 0, extractedZangle = 0;
    concatenatedRotationMatrix.extractEulerAngleXYZ(
        extractedXangle, extractedYangle, extractedZangle
    );

    std::cout << toDegrees(extractedXangle) << ' ' <<
        toDegrees(extractedYangle) << ' ' <<
        toDegrees(extractedZangle) << std::endl;

    return 0;
}

ただし、y = pi / 2、つまりcos(y)== 0の場合の問題に注意してください。M[1] [3]およびM [2] [3]を使用してxを取得できるとは限りません。比率が定義されておらず、atan2値が取得できないためです。これはジンバルロックの問題と同じだと思います。
Pieter Geerkens、2015

@PieterGeerkens、あなたは正しい、それはジンバルロックです。ところで、あなたのコメントは私がそのセクションにタイプミスがあったことを明らかにしました。最初のインデックスが0のマトリックスインデックスを参照します。これらは3x3マトリックスであるため、最後のインデックスは3ではなく2 です。とで修正M[1][3]しました。M[1][2]M[2][3]M[2][2]
Chris

結合マトリックスの例の2行1列目がSxSySz + CxSzではなく、SxSyCz + CxSzであると確信しています!

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