Ada proses penulisan bagus untuk proses ini oleh Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Itu juga sekarang diimplementasikan di glm, pada versi 0.9.7.0, 02/08/2015. Lihat implementasinya .
Untuk memahami matematika, Anda harus melihat nilai-nilai yang ada dalam matriks rotasi Anda. Selain itu, Anda harus mengetahui urutan rotasi diterapkan untuk membuat matriks Anda agar dapat mengekstrak nilai dengan benar.
Matriks rotasi dari sudut Euler dibentuk dengan menggabungkan rotasi di sekitar sumbu x, y, dan z. Misalnya, memutar θ derajat di sekitar Z dapat dilakukan dengan matriks
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Matriks serupa ada untuk memutar sumbu X dan Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Kita dapat mengalikan matriks-matriks ini bersama-sama untuk membuat satu matriks yang merupakan hasil dari ketiga rotasi. Penting untuk dicatat bahwa urutan matriks ini dikalikan bersama adalah penting, karena perkalian matriks tidak komutatif . Ini artinya Rx*Ry*Rz ≠ Rz*Ry*Rx
. Mari kita pertimbangkan satu kemungkinan urutan rotasi, zyx. Ketika tiga matriks digabungkan, ini menghasilkan matriks yang terlihat seperti ini:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
dimana Cx
cosinus dari x
sudut rotasi, Sx
adalah sinus darix
sudut rotasi, dll.
Sekarang, tantangannya adalah untuk mengekstrak asli x
, y
danz
nilai-nilai yang masuk ke matriks.
Pertama mari kita x
keluar sudut. Jika kita tahu sin(x)
dan cos(x)
, kita bisa menggunakan fungsi tangen terbalik atan2
untuk mengembalikan sudut kita. Sayangnya, nilai-nilai itu tidak muncul sendiri dalam matriks kami. Tapi, jika kita melihat lebih dekat pada unsur-unsur M[1][2]
dan M[2][2]
, kita dapat melihat kita tahu -sin(x)*cos(y)
serta cos(x)*cos(y)
. Karena fungsi tangen adalah rasio sisi yang berlawanan dan berdekatan dari sebuah segitiga, penskalaan kedua nilai dengan jumlah yang sama (dalam hal ini cos(y)
) akan menghasilkan hasil yang sama. Jadi,
x = atan2(-M[1][2], M[2][2])
Sekarang mari kita coba y
. Kami tahu sin(y)
dari M[0][2]
. Jika kita memiliki cos (y), kita bisa menggunakan atan2
lagi, tetapi kita tidak memiliki nilai itu dalam matriks kita. Namun, karena identitas Pythagoras , kita tahu bahwa:
cosY = sqrt(1 - M[0][2])
Jadi, kita dapat menghitung y
:
y = atan2(M[0][2], cosY)
Terakhir, kita perlu menghitung z
. Di sinilah pendekatan Mike Day berbeda dari jawaban sebelumnya. Karena pada titik ini kita mengetahui jumlah x
dan y
rotasi, kita dapat membangun matriks rotasi XY, dan menemukan jumlah z
rotasi yang diperlukan untuk mencocokkan matriks target. The RxRy
matriks terlihat seperti ini:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Karena kita tahu bahwa RxRy
* Rz
sama dengan matriks input M
kita, kita dapat menggunakan matriks ini untuk kembali ke Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
The inverse dari matriks rotasi transposnya , sehingga kita dapat memperluas ini untuk:
┌ 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 ┘
Kita sekarang dapat memecahkan untuk sinZ
dan cosZ
dengan melakukan perkalian matriks. Kami hanya perlu menghitung elemen [1][0]
dan [1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Berikut ini adalah implementasi lengkap untuk referensi:
#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;
}