रूपांतरण मैट्रिक्स से यूलर एंगल कैसे निकालें?


12

मुझे इकाई / घटक गेम इंजन का एक साधारण सा अहसास है।
ट्रांसफ़ॉर्म घटक में स्थानीय स्थिति, स्थानीय रोटेशन, वैश्विक स्थिति और वैश्विक रोटेशन सेट करने की विधियाँ हैं।

यदि परिवर्तन को नई वैश्विक स्थिति निर्धारित की जा रही है, तो स्थानीय स्थिति भी बदल जाती है, स्थानीय स्थिति को अपडेट करने के लिए ऐसे मामले में मैं केवल वर्तमान परिवर्तन स्थानीय मैट्रिक्स को माता-पिता की परिवर्तनशील दुनिया मैट्रिक्स में लागू कर रहा हूं।

तब तक मुझे कोई समस्या नहीं है, मैं अद्यतन स्थानीय परिवर्तन मैट्रिक्स प्राप्त कर सकता हूं।
लेकिन मैं स्थानीय स्थिति और परिवर्तन में रोटेशन मूल्य को अपडेट करने के लिए संघर्ष कर रहा हूं। केवल समाधान जो मेरे मन में है वह है ट्रांसफॉर्मेशन के लोकलमैट्रिक्स से ट्रांसलेशन और रोटेशन वैल्यू निकालना।

अनुवाद के लिए यह काफी आसान है - मैं सिर्फ 4 वां कॉलम मान लेता हूं। लेकिन रोटेशन के बारे में क्या है?
रूपांतरण मैट्रिक्स से यूलर एंगल कैसे निकालें?

क्या ऐसा समाधान सही है ?:
जेड अक्ष के चारों ओर घूमने के लिए, हम स्थानीय अक्ष के एक्स अक्ष वेक्टर और पेरेंट में एक्स अक्ष वेक्टर के बीच अंतर पा सकते हैं। डेल्टा में स्टोर परिणाम, और फिर: स्थानीयकरण = जेड = एटैन 2 (Delta.y, Delta) ।एक्स);

X & Y के चारों ओर घूमने के लिए समान, बस धुरी को स्वैप करने की आवश्यकता है।

जवाबों:


10

आम तौर पर मैं सभी वस्तुओं को 4x4 मैट्रिसेस के रूप में संग्रहीत करता हूं (आप 3x3 कर सकते हैं लेकिन मेरे लिए सिर्फ 1 वर्ग के लिए आसान है) 4x4 और वेक्टर 3 एस (अनुवाद, रोटेशन, स्केल) के 3 सेटों के बीच आगे और पीछे अनुवाद करने के बजाय। यूलर एंगल्स कुछ परिदृश्यों में निपटने के लिए बहुत मुश्किल हैं, इसलिए मैं क्वाटरनियों का उपयोग करने की सलाह दूंगा यदि आप वास्तव में मैट्रिक्स के बजाय घटकों को संग्रहीत करना चाहते हैं।

लेकिन यहाँ कुछ कोड है जो मुझे कुछ समय पहले मिला था जो काम करता है। मुझे आशा है कि यह मदद करता है, दुर्भाग्य से मेरे पास मूल स्रोत नहीं है जहां मुझे यह मिला है। मुझे पता नहीं है कि यह किस विषम परिदृश्य में काम नहीं कर सकता है। मैं फिलहाल 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 asin2 isinead का उपयोग पिच के लिए किया जाता है।

इसके अलावा, यह कैसे मेरी मदद करेगा, अगर मैं प्रत्येक घटक को अलग-अलग mat4x4 में संग्रहीत करूं? फिर मैं कैसे प्राप्त कर सकता हूं और उदाहरण के लिए कुछ अक्ष के चारों ओर रोटेशन का आउटपुट कोण?

आपका मूल प्रश्न मुझे विश्वास दिलाता है कि आप अपनी वस्तुओं को 3 वेक्टर 3s: अनुवाद, रोटेशन और स्केल के रूप में संग्रहीत कर रहे हैं। फिर जब आप कुछ काम कर रहे लोगों में से एक लोकल ट्रांसफॉर्मर का निर्माण करते हैं और बाद में (वेक्टरट्रेनफॉर्म * ग्लोबलट्रेनफॉर्म) को 3 वेक्टर 3 एस में परिवर्तित करने का प्रयास करते हैं। मैं पूरी तरह से गलत हो सकता है मैं सिर्फ उस छाप को प्राप्त कर रहा था।
NtscCobalt

हाँ, मैं गणित को अच्छी तरह से नहीं जानता कि एएसआईएन के साथ पिच क्यों की जाती है लेकिन जुड़ा हुआ प्रश्न उसी गणित का उपयोग करता है इसलिए मेरा मानना ​​है कि यह सही है। मैं बिना किसी समस्या के कुछ समय के लिए इस फ़ंक्शन का उपयोग कर रहा हूं।
NtscCobalt

क्या पहले दो में एटैन 2 एफ का उपयोग करने का कोई विशेष कारण है अगर मामलों और तीसरे में एटैन 2, या यह टाइपो है?
एफ

10

माइक डे द्वारा इस प्रक्रिया पर एक शानदार राइटअप है: https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf

यह अब glm में भी लागू हो गया है, संस्करण 0.9.7.0, 02/08/2015 के रूप में। कार्यान्वयन की जाँच करें

गणित को समझने के लिए, आपको उन मूल्यों को देखना चाहिए जो आपके रोटेशन मैट्रिक्स में हैं। इसके अलावा, आपको उस क्रम को जानना होगा जिसमें मूल्यों को ठीक से निकालने के लिए आपके मैट्रिक्स को बनाने के लिए घुमाव लागू किए गए थे।

यूलर एंगल्स से एक रोटेशन मैट्रिक्स x-, y- और z- अक्षों के चारों ओर घुमावों को मिलाकर बनता है। उदाहरण के लिए, Z के चारों ओर around डिग्री का घूमना मैट्रिक्स के साथ किया जा सकता है

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

एक्स और वाई कुल्हाड़ियों के बारे में घूमने के लिए समान मेट्रिसेस मौजूद हैं:

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

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

हम इन मैट्रिक्स को एक साथ एक मैट्रिक्स बनाने के लिए गुणा कर सकते हैं जो तीनों रोटेशन का परिणाम है। यह ध्यान रखना महत्वपूर्ण है कि इन मैट्रिक्स को एक साथ गुणा करने का क्रम महत्वपूर्ण है, क्योंकि मैट्रिक्स गुणन सराहनीय नहीं है । इसका मतलब है कि Rx*Ry*Rz ≠ Rz*Ry*Rx। आइए एक संभावित रोटेशन ऑर्डर पर विचार करें, zyx। जब तीन मैट्रिक्स संयुक्त होते हैं, तो यह इस तरह दिखने वाले मैट्रिक्स में परिणाम करता है:

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

जहां रोटेशन Cxके xकोण का कोज्या है , रोटेशन के कोण की Sxसाइन है x, आदि।

अब, चुनौती मूल को निकालने के लिए है x, yऔर zमूल्यों है कि मैट्रिक्स में चला गया।

आइए सबसे पहले 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])

अब पाने की कोशिश करते हैं y। हम से जानते sin(y)हैं M[0][2]। यदि हमारे पास cos (y) होता, तो हम atan2फिर से उपयोग कर सकते थे , लेकिन हमारे मैट्रिक्स में वह मूल्य नहीं है। हालाँकि, पायथागॉरियन पहचान के कारण , हम जानते हैं कि:

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

तो, हम गणना कर सकते हैं y:

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

अंतिम, हमें गणना करने की आवश्यकता है z। यह वह जगह है जहां माइक डे का दृष्टिकोण पिछले उत्तर से भिन्न होता है। चूंकि इस बिंदु पर हम xऔर yरोटेशन की मात्रा जानते हैं , हम 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 मूल्य प्राप्त नहीं किया जा सकता है। मेरा मानना ​​है कि यह जिम्बल लॉक समस्या के बराबर है।
पीटर जार्जेंस

@PieterGeerkens, आप सही कह रहे हैं, यह जिम्बल लॉक है। BTW, आपकी टिप्पणी से पता चला कि मेरे पास उस अनुभाग में एक टाइपो था। मैं 0 पर पहले एक के साथ मैट्रिक्स सूचकांकों का उल्लेख करता हूं, और चूंकि वे 3x3 मैट्रिसेस हैं, अंतिम इंडेक्स 2 है, न कि 3. मैंने M[1][3]साथ M[1][2]और M[2][3]साथ सही किया है M[2][2]
क्रिस

मुझे पूरा यकीन है कि उदाहरण के लिए संयुक्त पंक्ति की पहली पंक्ति संयुक्त मैट्रिक्स SxSyCz + CxSz है, SxSySz + CxSz नहीं!
लेक

@ ठीक है, तुम सही हो। संपादित।
क्रिस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.