Dönüşüm matrisinden euler açıları nasıl çıkarılır?


12

Varlık / bileşen oyun motorunu basit bir şekilde gerçekleştiriyorum.
Dönüştürme bileşeni yerel konumu, yerel döndürmeyi, genel konumu ve genel döndürmeyi ayarlama yöntemlerine sahiptir.

Eğer dönüşüm yeni küresel konum olarak ayarlanıyorsa, o zaman yerel konum da değişir, bu durumda yerel konumu güncellemek için sadece ana dönüşüm dünya matrisine geçerli dönüşüm yerel matrisini uygularım.

O zamana kadar hiçbir sorunum yok, güncellenmiş yerel dönüşüm matrisi alabilirim.
Ancak dönüşümdeki yerel konum ve dönüş değerinin nasıl güncelleneceği konusunda mücadele ediyorum. Aklımdaki tek çözüm, localMatrix dönüşümünden çeviri ve döndürme değerleri elde etmektir.

Çeviri için oldukça kolay - sadece 4. sütun değerlerini alıyorum. Peki rotasyon ne olacak?
Dönüşüm matrisinden euler açıları nasıl çıkarılır?

Böyle bir çözüm doğru mu ?
: Z ekseni etrafında dönüş bulmak için, localTransform'un X ekseni vektörü ile parent.localTransform'un X ekseni vektörü arasındaki farkı bulabilir ve sonucu Delta'da depolayabiliriz, ardından: localRotation.z = atan2 (Delta.y, Delta .x);

X & Y etrafında dönme için aynıdır, sadece ekseni değiştirmeniz gerekir.

Yanıtlar:


10

Normalde tüm nesneleri 4x4 ve 3 set vector3s (Çeviri, Döndürme, Ölçek) arasında ileri geri çevirmek yerine 4x4 Matrisler olarak saklıyorum (3x3 yapabilirsin ama benim için sadece 1 sınıfa sahip olmak daha kolay). Belirli senaryolarda euler açıları ile uğraşmak çok zordur, bu yüzden bileşenleri bir matris yerine depolamak istiyorsanız Quaternions'ı kullanmanızı tavsiye ederim.

Ama işte bir süre geri buldum bazı kod İşte. Umarım bu yardımcı olur, maalesef bunu bulduğum yer için orijinal kaynağım yok. Hangi garip senaryolarda işe yaramayabilir hiçbir fikrim yok. Şu anda YawPitchRoll döndürülmüş, sol elle 4x4 matrisler rotasyon almak için kullanıyorum.

   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);
        }
    }

İşte benimkine benzer bir sonuca benzeyen sorunuzu cevaplamaya çalışırken bulduğum başka bir konu.

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


Önerilen çözümüm neredeyse doğru görünüyor, sadece atan2'nin neden saha için kullanılmadığını bilmiyorum.

Ayrıca, her bir bileşeni ayrı mat4x4'de saklarsam bana nasıl yardımcı olur? Daha sonra bir eksen etrafında nasıl dönme açısını elde edebilirim ve örneğin?

Asıl sorunuz beni nesnelerinizi 3 vector3s olarak sakladığınıza inandırıyor: Çeviri, Döndürme ve Ölçek. Sonra bir iş yapan ve daha sonra (localTransform * globalTransform) 3 vector3s dönüştürmek için bir localTransform oluştururken. Tamamen yanlış olabilirdim, sadece bu izlenimi alıyordum.
NtscCobalt

Evet, perdenin neden ASIN ile yapıldığı için yeterince iyi bilmiyorum ama bağlantılı soru aynı matematiği kullanıyor, bu yüzden doğru olduğuna inanıyorum. Bu işlevi bir süredir sorunsuz bir şekilde kullanıyorum.
NtscCobalt

İlk iki durumda atan2f ve üçüncü sırada atan2 kullanmanın özel bir nedeni var mı, yoksa bir yazım hatası mı?
Mattias F

10

Mike Day tarafından bu süreçte harika bir yazı var: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

Ayrıca şimdi 0.9.7.0, 02/08/2015 sürümünden itibaren glm'de uygulanmaktadır. Uygulamayı kontrol edin .

Matematiği anlamak için, döndürme matrisinizdeki değerlere bakmalısınız. Ayrıca, değerleri düzgün bir şekilde çıkarmak için matrisinizi oluşturmak için rotasyonların uygulanma sırasını bilmeniz gerekir.

X-, y- ve z eksenleri etrafındaki rotasyonların birleştirilmesiyle Euler açılarından bir rotasyon matrisi oluşturulur. Örneğin, Z'nin etrafında θ derece döndürmek matrisle yapılabilir

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

X ve Y eksenleri etrafında döndürmek için benzer matrisler vardır:

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

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

Üç rotasyonun sonucu olan bir matris oluşturmak için bu matrisleri birlikte çoğaltabiliriz. Matris çarpımı değişmeli olmadığından , bu matrislerin birlikte çarpılma sırasının önemli olduğunu belirtmek önemlidir . Bu demektir Rx*Ry*Rz ≠ Rz*Ry*Rx. Bir olası dönüş sırasını düşünelim, zyx. Üç matris birleştirildiğinde, şöyle görünen bir matrise neden olur:

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

dönme açısının Cxkosinüsü nerede , xdönme açısının Sxsinüsü xvb.

Şimdi, meydan orijinali çıkarmaktır x, yve zmatrisin girdi değerleri.

Önce xaçıyı çıkaralım . Bildiğimiz ise sin(x)ve cos(x)biz ters tanjant fonksiyonunu kullanabilirsiniz atan2bize açısını geri vermek. Ne yazık ki, bu değerler matrisimizde kendiliğinden görünmüyor. Ama biz unsurları daha yakından bakacak olursak M[1][2]ve M[2][2]biz biliyoruz görebilirsiniz -sin(x)*cos(y)yanı sıra cos(x)*cos(y). Teğet fonksiyonu, bir üçgenin zıt ve bitişik kenarlarının oranı olduğundan, her iki değeri de aynı miktarda ölçeklendirmek (bu durumda cos(y)) aynı sonucu verir. Böylece,

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

Şimdi almaya çalışalım y. Biz biliyoruz sin(y)dan M[0][2]. Eğer cos (y) varsa, atan2tekrar kullanabiliriz , ama matrisimizde bu değere sahip değiliz. Ancak, Pisagor kimliğinden dolayı şunu biliyoruz:

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

Böylece, hesaplayabiliriz y:

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

Son olarak, hesaplamamız gerekiyor z. Mike Day'in yaklaşımı önceki yanıttan farklıdır. Bu noktada xve ydönüş miktarını bildiğimizden, bir XY dönüş matrisi oluşturabilir ve zhedef matrisle eşleştirmek için gerekli dönüş miktarını bulabiliriz . RxRyMatris şöyle görünür:

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

RxRy* Rz'Nin giriş matrisimize eşit olduğunu bildiğimizden, Mbu matrisi kullanarak geri dönebiliriz Rz:

M = RxRy * Rz

inverse(RxRy) * M = Rz

Rotasyon matrisi ters onun devrik olduğunu biz bu genişletmek, böylece:

 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 

Şimdilik çözebilir sinZve cosZmatris çarpma gerçekleştirerek. Biz sadece unsurları hesaplamak gerekir [1][0]ve [1][1].

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

İşte referans için tam bir uygulama:

#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;
}

Bununla birlikte, y = pi / 2 ve dolayısıyla cos (y) == 0 olduğunda soruna dikkat edin. O zaman M [1] [3] ve M [2] [3] 'ün x elde etmek için kullanılabileceği durum söz konusu değildir. çünkü oran tanımsızdır ve atan2 değeri alınamaz. Bunun gimbal kilit problemine eşdeğer olduğuna inanıyorum .
Pieter Geerkens

@PieterGeerkens, haklısın, bu gimbal kilit. BTW, yorumunuz bu bölümde bir yazım hatası olduğunu ortaya koydu. Ben 0'dan ilkinden matris endeksleri bakın ve onlar 3x3 matrisler olduğundan, son endeks, ben düzelttim değil 3. 2 M[1][3]ile M[1][2]ve M[2][3]ile M[2][2].
Chris

Eminim örnek birleşik matris ikinci satır ilk sütun SxSySz + CxSz değil, SxSyCz + CxSz olduğunu!
Gölü

@ Doğru, haklısın. Düzenlenen.
Chris
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.