มีการเขียนที่ยอดเยี่ยมเกี่ยวกับกระบวนการนี้โดย Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
ตอนนี้มันถูกนำไปใช้งานใน glm ด้วยเช่นรุ่น 0.9.7.0, 02/08/2015 ตรวจสอบการดำเนินการ
เพื่อให้เข้าใจคณิตศาสตร์คุณควรดูค่าที่อยู่ในเมทริกซ์การหมุนของคุณ นอกจากนี้คุณต้องทราบลำดับที่การหมุนถูกนำไปใช้เพื่อสร้างเมทริกซ์ของคุณเพื่อดึงค่าอย่างถูกต้อง
เมทริกซ์การหมุนจากมุมออยเลอร์เกิดขึ้นจากการรวมการหมุนรอบแกน 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θ ┘
เราสามารถคูณเมทริกซ์เหล่านี้เข้าด้วยกันเพื่อสร้างเมทริกซ์เดียวที่เป็นผลลัพธ์ของการหมุนทั้งสาม สิ่งสำคัญคือให้สังเกตว่าลำดับที่เมทริกซ์เหล่านี้ถูกคูณเข้าด้วยกันนั้นมีความสำคัญเนื่องจากการคูณเมทริกซ์ไม่ได้สลับกัน 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
แล้วเราต้องคำนวณ นี่คือที่แนวทางของ Mike Day แตกต่างจากคำตอบก่อนหน้า เนื่องจาก ณ จุดนี้เราทราบจำนวน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
ผกผันของเมทริกซ์หมุน transpose ของตนเพื่อให้เราสามารถขยายนี้ไปที่:
┌ 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;
}