Майк День чудово написав цей процес:
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
. Саме тут підхід Майка Дей відрізняється від попередньої відповіді. Оскільки в цей момент ми знаємо величину 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;
}