Làm thế nào để trích xuất các góc euler từ ma trận biến đổi?


11

Tôi có một nhận thức đơn giản về công cụ trò chơi thực thể / thành phần.
Thành phần biến đổi có các phương thức để đặt vị trí cục bộ, xoay cục bộ, vị trí toàn cầu và xoay toàn cầu.

Nếu biến đổi đang được đặt vị trí toàn cầu mới, thì vị trí cục bộ cũng thay đổi, để cập nhật vị trí cục bộ trong trường hợp đó, tôi chỉ áp dụng ma trận cục bộ biến đổi hiện tại cho ma trận thế giới biến đổi của cha mẹ.

Cho đến lúc đó tôi không có vấn đề gì, tôi có thể cập nhật ma trận biến đổi cục bộ.
Nhưng tôi đang đấu tranh về cách cập nhật vị trí cục bộ và giá trị xoay trong biến đổi. Giải pháp duy nhất tôi có trong đầu là trích xuất các giá trị dịch và xoay từ localMatrix của phép biến đổi.

Để dịch nó khá dễ dàng - tôi chỉ lấy giá trị cột thứ 4. Nhưng những gì về luân chuyển?
Làm thế nào để trích xuất các góc euler từ ma trận biến đổi?

Giải pháp như vậy có đúng không?:
Để tìm xoay quanh trục Z, chúng ta có thể tìm thấy sự khác biệt giữa vectơ trục X của localTransform và vectơ trục X của Parent.localTransform và lưu trữ kết quả trong Delta, sau đó: localRotation.z = atan2 (Delta.y, Delta .x);

Tương tự cho xoay quanh X & Y, chỉ cần hoán đổi trục.

Câu trả lời:


9

Thông thường tôi lưu trữ tất cả các đối tượng dưới dạng Ma trận 4 x 4 (bạn có thể thực hiện 3x3 nhưng dễ dàng hơn đối với tôi chỉ có 1 lớp) thay vì dịch qua lại giữa một bộ 4 và 3 vectơ (Dịch, Xoay, Chia tỷ lệ). Các góc của Euler nổi tiếng là khó xử lý trong các tình huống nhất định, vì vậy tôi khuyên bạn nên sử dụng Quancyions nếu bạn thực sự muốn lưu trữ các thành phần thay vì ma trận.

Nhưng đây là một số mã tôi tìm thấy một thời gian trở lại hoạt động. Tôi hy vọng điều này có ích, tiếc là tôi không có nguồn gốc cho nơi tôi tìm thấy thứ này. Tôi không biết nó có thể không hoạt động trong những kịch bản kỳ lạ nào. Tôi hiện đang sử dụng kịch bản này để xoay vòng YawPitchRoll, ma trận 4x4 thuận tay trái.

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

Đây là một chủ đề khác mà tôi tìm thấy trong khi cố gắng trả lời câu hỏi của bạn trông giống như một kết quả tương tự với tôi.

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


Có vẻ như giải pháp đề xuất của tôi gần như đúng, chỉ không biết tại sao không phải là atan2 asin được sử dụng cho sân.

Ngoài ra, nó sẽ giúp tôi như thế nào, nếu tôi sẽ lưu trữ từng thành phần trong mat4x4 riêng biệt? Làm thế nào tôi có thể nhận được và ví dụ góc đầu ra xoay quanh một số trục?

Câu hỏi ban đầu của bạn khiến tôi tin rằng bạn đang lưu trữ các đối tượng của mình dưới dạng 3 vector3: Dịch, Xoay và Tỷ lệ. Sau đó, khi bạn tạo localTransform từ những người đang thực hiện một số công việc và sau đó cố gắng chuyển đổi (localTransform * globalTransform) trở lại thành 3 vector3. Tôi có thể hoàn toàn sai Tôi chỉ nhận được ấn tượng đó.
NtscCobalt

Vâng, tôi không biết toán học đủ tốt cho lý do tại sao sân được thực hiện với ASIN nhưng câu hỏi được liên kết sử dụng cùng một toán học vì vậy tôi tin rằng nó là chính xác. Tôi đã sử dụng chức năng này trong một thời gian mà không có vấn đề gì.
NtscCobalt

Có bất kỳ lý do cụ thể cho việc sử dụng atan2f trong hai trường hợp đầu tiên nếu trường hợp và atan2 trong lần thứ ba, hoặc đó là một lỗi đánh máy?
Mattias F

9

Có một bài viết tuyệt vời về quá trình này của Mike Day: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

Hiện tại nó cũng được triển khai trong glm, kể từ phiên bản 0.9.7.0, 02/08/2015. Kiểm tra việc thực hiện .

Để hiểu toán, bạn nên nhìn vào các giá trị trong ma trận xoay vòng của bạn. Ngoài ra, bạn phải biết thứ tự các phép quay được áp dụng để tạo ma trận của bạn để trích xuất đúng các giá trị.

Một ma trận xoay từ các góc Euler được hình thành bằng cách kết hợp các phép quay quanh các trục x, y- và z. Ví dụ, xoay θ độ quanh Z có thể được thực hiện với ma trận

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

Các ma trận tương tự tồn tại để quay quanh trục X và Y:

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

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

Chúng ta có thể nhân các ma trận này với nhau để tạo ra một ma trận là kết quả của cả ba phép quay. Điều quan trọng cần lưu ý là thứ tự các ma trận này được nhân với nhau là rất quan trọng, vì phép nhân ma trận không giao hoán . Điều này có nghĩa là Rx*Ry*Rz ≠ Rz*Ry*Rx. Hãy xem xét một thứ tự xoay có thể, zyx. Khi ba ma trận được kết hợp, nó sẽ tạo ra một ma trận giống như sau:

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

trong đó Cxcosin của xgóc quay, Sxlà sin của xgóc quay, v.v.

Bây giờ, thách thức là để trích xuất các gốc x, yzgiá trị mà đã đi vào ma trận.

Trước tiên hãy lấy xgóc nhìn ra. Nếu chúng ta biết sin(x)cos(x), chúng ta có thể sử dụng hàm tiếp tuyến nghịch đảo atan2để đưa chúng ta trở lại góc của chúng ta. Thật không may, những giá trị đó không tự xuất hiện trong ma trận của chúng tôi. Nhưng, nếu chúng ta xem xét kỹ hơn các yếu tố M[1][2]M[2][2], chúng ta có thể thấy chúng ta cũng biết -sin(x)*cos(y)như vậy cos(x)*cos(y). Vì hàm tiếp tuyến là tỷ lệ của các cạnh đối diện và liền kề của một tam giác, nên chia tỷ lệ cả hai giá trị theo cùng một lượng (trong trường hợp này cos(y)) sẽ mang lại kết quả như nhau. Như vậy

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

Bây giờ chúng ta hãy cố gắng để có được y. Chúng tôi biết sin(y)từ M[0][2]. Nếu chúng ta có cos (y), chúng ta có thể sử dụng atan2lại, nhưng chúng ta không có giá trị đó trong ma trận của mình. Tuy nhiên, do bản sắc Pythagore , chúng tôi biết rằng:

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

Vì vậy, chúng ta có thể tính toán y:

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

Cuối cùng, chúng ta cần tính toán z. Đây là cách tiếp cận của Mike Day khác với câu trả lời trước đó. Vì tại thời điểm này, chúng ta biết lượng xyxoay, chúng ta có thể xây dựng ma trận xoay XY và tìm lượng zxoay cần thiết để khớp với ma trận đích. Các RxRyma trận trông như thế này:

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

Vì chúng tôi biết rằng RxRy* Rzbằng với ma trận đầu vào của chúng tôi M, chúng tôi có thể sử dụng ma trận này để quay lại Rz:

M = RxRy * Rz

inverse(RxRy) * M = Rz

Các nghịch đảo của một ma trận xoay là transpose của nó , vì vậy chúng tôi có thể mở rộng này để:

 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 

Bây giờ chúng ta có thể giải quyết sinZcosZbằng cách thực hiện phép nhân ma trận. Chúng ta chỉ cần tính toán các yếu tố [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)

Đây là một triển khai đầy đủ để tham khảo:

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

Tuy nhiên, lưu ý rằng vấn đề khi y = pi / 2 và do đó cos (y) == 0. Sau đó, không phải là trường hợp M [1] [3] và M [2] [3] có thể được sử dụng để thu được x bởi vì tỷ lệ này không được xác định và không thể lấy giá trị atan2 . Tôi tin rằng điều này tương đương với vấn đề khóa gimbal .
Pieter Geerkens

@PieterGeerkens, bạn nói đúng, đó là khóa gimbal. BTW, bình luận của bạn tiết lộ rằng tôi có một lỗi đánh máy trong phần đó. Tôi tham khảo các chỉ số ma trận với cái đầu tiên ở mức 0, và vì chúng là 3x3 ma trận, chỉ số cuối cùng là 2, không 3. Tôi đã sửa chữa M[1][3]với M[1][2], và M[2][3]với M[2][2].
Chris

Tôi khá chắc chắn cột thứ hai hàng đầu tiên của ma trận kết hợp ví dụ là SxSyCz + CxSz, không phải SxSySz + CxSz!
Hồ

@Lake, bạn đã đúng. Đã chỉnh sửa.
Chris
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.