有没有办法从 4x4 矩阵计算 X 和 Y 轴上的 3D 旋转
Is there a way to calculate 3D rotation on X and Y axis from a 4x4 matrix
首先,我根本不是数学专家。请容忍我的数学错误并在必要时纠正我,我很乐意学习。
我有一个立方体,它使用 css 变换动画旋转:matrix3d(4x4)。我还可以手动旋转立方体,将用户操作转换为相同的 matrix3d 转换。
我想要的是一个旋转的立方体 css 当用户停止交互时从用户离开的地方开始。这是我通过获取立方体的变换 matrix3d 值并使用乘法动态设置 css 的关键帧而成功完成的事情。
但是,当用户开始与立方体交互时,立方体会跳转到其最后已知的手动旋转点并从那里继续,因为我不知道如何从 4x4 矩阵获取 X 轴和 Y 轴上的旋转.
我目前正在使用以下库 Rematrix,它帮助我从手动轮换到 css 轮换,如上所述。
我一直在研究关于 Euler 的文章,以及如何从 Euler 到矩阵,反之亦然,但正如我之前提到的,我认为这是我缺乏数学知识阻碍我前进的地方。我似乎无法弄清楚。
作为参考,以下是我阅读过的一些文章,试图解决我的问题。
- https://medium.com/@behreajj/3d-rotations-in-processing-vectors-matrices-quaternions-10e2fed5f0a3
- http://www.gregslabaugh.net/publications/euler.pdf
- https://www.learnopencv.com/rotation-matrix-to-euler-angles/
- https://css-tricks.com/get-value-of-css-rotation-through-javascript/
最后一个来源对我来说最有意义,但如果我是正确的,在这种情况下没有用,因为它是关于 2D 转换,而不是 3D。
我通过以下方式获取当前的 matrix3d:
const style = getComputedStyle(this.element).transform
const matrix = Rematrix.parse(style)
对于手动旋转,我使用基于用户鼠标位置(positionY、positionX)的矩阵乘法。
const r1 = Rematrix.rotateX(this.positionY)
const r2 = Rematrix.rotateY(this.positionX)
const transform = [r1, r2].reduce(Rematrix.multiply)
this.element.style[userPrefix.js + 'Transform'] = Rematrix.toString(transform)
从手动到 css 旋转 我使用以下函数:
const setCssAnimationKeyframes = (lastTransform, animationData) => {
const rotationIncrement = 90
let matrixes = []
for (let i = 0; i < 5; i++) {
const rX = Rematrix.rotateX(rotationIncrement * i)
const rY = Rematrix.rotateY(rotationIncrement * i)
const matrix = [lastTransform, rX, rY].reduce(Rematrix.multiply);
matrixes.push(matrix)
}
animationData.innerHTML = `
@keyframes rotateCube {
0% {
transform: ${Rematrix.toString(matrixes[0])};
}
25% {
transform: ${Rematrix.toString(matrixes[1])};
}
50% {
transform: ${Rematrix.toString(matrixes[2])};
}
75% {
transform: ${Rematrix.toString(matrixes[3])}};
}
100% {
transform: ${Rematrix.toString(matrixes[4])};
}
}
`;
}
请提供任何有用信息的答案或评论。尽管非常欢迎,但我不希望您提供完整的代码示例。非常感谢任何形式的有用信息。
第一次阅读:
因为我使用那里的术语。
嗯,我懒得把所有东西都等同于我的环境,但基于此:
对于任何旋转顺序,m
的结果 3D 旋转子矩阵将始终具有这些热量:
(+/-)sin(a)
(+/-)sin(b)cos(a)
(+/-)cos(b)cos(a)
(+/-)sin(c)cos(a)
(+/-)cos(c)cos(a)
只有它们的符号和位置会随着变换顺序和约定而改变。因此,要识别它们,请执行以下操作:
先设置一些非平凡的欧拉角
它们的 |sin|
, |cos|
值必须不同,所以 6 个值中的 none 将相同,否则这将不起作用!!!
我选择了这些:
ex = 10 [deg]
ey = 20 [deg]
ez = 30 [deg]
计算旋转矩阵m
因此按顺序对单位矩阵应用 3 次欧拉旋转。在我的设置中,结果矩阵如下所示:
double m[16] =
{
0.813797652721405, 0.543838143348694,-0.204874128103256, 0, // Xx,Xy,Xz,0.0
-0.469846308231354, 0.823172926902771, 0.318795770406723, 0, // Yx,Yy,Yz,0.0
0.342020153999329,-0.163175910711288, 0.925416529178619, 0, // Zx,Zy,Zz,0.0
0 , 0 , 0 , 1 // Ox,Oy,Oz,1.0
};
请注意,我使用的是 OpenGL 约定,基向量 X,Y,Z
和原点 O
由矩阵的行表示,矩阵是直接的。
识别(+/-)sin(a)
热
a
可以是任何欧拉角,所以打印 sin
个欧拉角:
sin(ex) = 0.17364817766693034885171662676931
sin(ey) = 0.34202014332566873304409961468226
sin(ez) = 0.5
现在看到 m[8] = sin(ey)
所以我们找到了我们的热……现在我们知道了:
ey = a = asin(m[8]);
识别(+/-)???(?)*cos(a)
热量
简单地为尚未使用的角度打印 cos(?)*cos(ey)。所以如果 ey
是 20 度,我打印 10 和 30 度 ...
sin(10 deg)*cos(20 deg) = 0.16317591116653482557414168661534
cos(10 deg)*cos(20 deg) = 0.92541657839832335306523309767123
sin(30 deg)*cos(20 deg) = 0.46984631039295419202705463866237
cos(30 deg)*cos(20 deg) = 0.81379768134937369284469321724839
当我们再次查看 m
时,我们可以交叉匹配:
sin(ex)*cos(ey) = 0.16317591116653482557414168661534 = -m[9]
cos(ex)*cos(ey) = 0.92541657839832335306523309767123 = +m[10]
sin(ez)*cos(ey) = 0.46984631039295419202705463866237 = -m[4]
cos(ez)*cos(ey) = 0.81379768134937369284469321724839 = +m[0]
据此我们可以计算角度...
sin(ex)*cos(ey) = -m[ 9]
cos(ex)*cos(ey) = +m[10]
sin(ez)*cos(ey) = -m[ 4]
cos(ez)*cos(ey) = +m[ 0]
------------------------
sin(ex) = -m[ 9]/cos(ey)
cos(ex) = +m[10]/cos(ey)
sin(ez) = -m[ 4]/cos(ey)
cos(ez) = +m[ 0]/cos(ey)
所以最后:
---------------------------------------------
ey = asin(m[8]);
ex = atan2( -m[ 9]/cos(ey) , +m[10]/cos(ey) )
ez = atan2( -m[ 4]/cos(ey) , +m[ 0]/cos(ey) )
---------------------------------------------
就是这样。如果你有不同的 layout/conventions/transform 顺序,这种方法仍然有效......只有索引和符号会改变。这里是小 C++/VCL OpenGL 示例,我在 (X,Y,Z
order):
上测试了这个
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
bool _redraw=true; // need repaint?
//---------------------------------------------------------------------------
double m[16]= // uniform 4x4 matrix
{
1.0,0.0,0.0,0.0, // Xx,Xy,Xz,0.0
0.0,1.0,0.0,0.0, // Yx,Yy,Yz,0.0
0.0,0.0,1.0,0.0, // Zx,Zy,Zz,0.0
0.0,0.0,0.0,1.0 // Ox,Oy,Oz,1.0
};
double e[3]={0.0,0.0,0.0}; // euler angles x,y,z order
//---------------------------------------------------------------------------
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;
void matrix2euler(double *e,double *m)
{
double c;
e[1]=asin(+m[ 8]);
c=cos(e[1]); if (fabs(c>1e-20)) c=1.0/c; else c=0.0;
e[0]=atan2(-m[ 9]*c,m[10]*c);
e[2]=atan2(-m[ 4]*c,m[ 0]*c);
}
//---------------------------------------------------------------------------
void gl_draw()
{
_redraw=false;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
// glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslated(0.0,0.0,-10.0); // some distance from camera ...
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
int i;
// draw source matrix:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated(-1.0,0.0,0.0); // source matrix on the left
glMultMatrixd(m);
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(1.0,0.0,0.0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,1.0,0.0);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,0.0,1.0);
glEnd();
glPopMatrix();
// draw source matrix:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated(m[12],m[13],m[14]); // source matrix in the middle
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3dv(m+0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3dv(m+4);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3dv(m+8);
glEnd();
glPopMatrix();
// draw euler angles
matrix2euler(e,m);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated(+1.0,0.0,0.0); // euler angles on the right
glRotated(e[0]*rad,1.0,0.0,0.0);
glRotated(e[1]*rad,0.0,1.0,0.0);
glRotated(e[2]*rad,0.0,0.0,1.0);
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(1.0,0.0,0.0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,1.0,0.0);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,0.0,1.0);
glEnd();
glPopMatrix();
// glFlush();
glFinish();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
gl_init(Handle);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotated(10.0,1.0,0.0,0.0);
glRotated(20.0,0.0,1.0,0.0);
glRotated(30.0,0.0,0.0,1.0);
glGetDoublev(GL_MODELVIEW_MATRIX,m);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
if (_redraw) gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
gl_resize(ClientWidth,ClientHeight);
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
// Caption=Key;
const double da=5.0;
if (Key==37){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(+da,0.0,1.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
if (Key==39){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(-da,0.0,1.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
if (Key==38){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(+da,1.0,0.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
if (Key==40){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(-da,1.0,0.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
}
//---------------------------------------------------------------------------
其中唯一重要的东西是 matrix2euler
函数,它按 x,y,z
顺序将矩阵 m
转换为欧拉角。它呈现 3 个坐标系轴。左边是 m
用作模型视图矩阵,中间是 m
的基向量使用身份模型视图,右边是通过计算的欧拉角构建的模型视图 ...
所有 3 个都应该匹配。如果左边和中间不匹配,那么你得到了不同的矩阵或布局约定。
此处 (10,20,30) [deg]
测试用例预览:
即使经过多次旋转(箭头键)也匹配...
gl_simple.h
可以在这里找到:
PS. 根据 platform/environment 计算可能需要一些边缘情况处理,例如 asin
大于 1
,除以零等。atan2
也有其怪癖...
[Edit1] 这里是自动完成所有这些操作的最终 C++ 示例:
//---------------------------------------------------------------------------
enum _euler_cfg_enum
{
_euler_cfg_a=0,
_euler_cfg_b,
_euler_cfg_c,
_euler_cfg__sina,
_euler_cfg_ssina,
_euler_cfg__sinb_cosa,
_euler_cfg_ssinb_cosa,
_euler_cfg__cosb_cosa,
_euler_cfg_scosb_cosa,
_euler_cfg__sinc_cosa,
_euler_cfg_ssinc_cosa,
_euler_cfg__cosc_cosa,
_euler_cfg_scosc_cosa,
_euler_cfgs
};
//---------------------------------------------------------------------------
void matrix2euler_init(double *e,double *m,int *cfg) // cross match euler angles e[3] and resulting m[16] transform matrix into cfg[_euler_cfgs]
{
int i,j;
double a,tab[4];
const double _zero=1e-6;
for (i=0;i<_euler_cfgs;i++) cfg[i]=-1; // clear cfg
// find (+/-)sin(a)
for (i=0;i<3;i++) // test all angles in e[]
{
a=sin(e[i]);
for (j=0;j<16;j++) // test all elements in m[]
if (fabs(fabs(a)-fabs(m[j]))<=_zero) // find match in |m[j]| = |sin(e[i])|
{ // store configuration
cfg[_euler_cfg_a]=i;
cfg[_euler_cfg__sina]=j;
cfg[_euler_cfg_ssina]=(a*m[j]<0.0);
j=-1; break;
}
if (j<0){ i=-1; break; } // stop on match found
}
if (i>=0){ cfg[0]=-1; return; } // no match !!!
// find (+/-)???(?)*cos(a)
a=cos(e[cfg[_euler_cfg_a]]);
i=0; if (i==cfg[_euler_cfg_a]) i++; tab[0]=sin(e[i])*a; tab[1]=cos(e[i])*a; cfg[_euler_cfg_b]=i;
i++; if (i==cfg[_euler_cfg_a]) i++; tab[2]=sin(e[i])*a; tab[3]=cos(e[i])*a; cfg[_euler_cfg_c]=i;
for (i=0;i<4;i++)
{
a=tab[i];
for (j=0;j<16;j++) // test all elements in m[]
if (fabs(fabs(a)-fabs(m[j]))<=_zero) // find match in |m[j]| = |tab[i]|
{ // store configuration
cfg[_euler_cfg__sinb_cosa+i+i]=j;
cfg[_euler_cfg_ssinb_cosa+i+i]=(a*m[j]<0.0);
j=-1; break;
}
if (j>=0){ cfg[0]=-1; return; } // no match !!!
}
}
//---------------------------------------------------------------------------
void matrix2euler(double *e,double *m,int *cfg) // compute euler angles e[3] from transform matrix m[16] using confing cfg[_euler_cfgs]
{
double c;
//-----angle------ --------------sign-------------- ----------index----------
e[cfg[_euler_cfg_a]]=asin ((cfg[_euler_cfg_ssina]?-1.0:+1.0) *m[cfg[_euler_cfg__sina ]]);
c=cos(e[cfg[_euler_cfg_a]]); if (fabs(c>1e-20)) c=1.0/c; else c=0.0;
e[cfg[_euler_cfg_b]]=atan2((cfg[_euler_cfg_ssinb_cosa]?-c:+c)*m[cfg[_euler_cfg__sinb_cosa]],
(cfg[_euler_cfg_scosb_cosa]?-c:+c)*m[cfg[_euler_cfg__cosb_cosa]]);
e[cfg[_euler_cfg_c]]=atan2((cfg[_euler_cfg_ssinc_cosa]?-c:+c)*m[cfg[_euler_cfg__sinc_cosa]],
(cfg[_euler_cfg_scosc_cosa]?-c:+c)*m[cfg[_euler_cfg__cosc_cosa]]);
}
//---------------------------------------------------------------------------
用法:
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;
// variables
double e[3],m[16];
int euler_cfg[_euler_cfgs];
// init angles
e[0]=10.0*deg;
e[1]=20.0*deg;
e[2]=30.0*deg;
// compute coresponding rotation matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotated(e[0]*rad,1.0,0.0,0.0);
glRotated(e[1]*rad,0.0,1.0,0.0);
glRotated(e[2]*rad,0.0,0.0,1.0);
glGetDoublev(GL_MODELVIEW_MATRIX,m);
// cross match e,m -> euler_cfg
matrix2euler_init(e,m,euler_cfg);
// now we can use
matrix2euler(e,m,euler_cfg);
这适用于任何转换顺序和/或 convention/layout。 init 仅被调用一次,然后您可以将转换用于任何变换矩阵...您还可以根据您的环境的 euler_cfg
结果编写自己的优化版本。
首先,我根本不是数学专家。请容忍我的数学错误并在必要时纠正我,我很乐意学习。
我有一个立方体,它使用 css 变换动画旋转:matrix3d(4x4)。我还可以手动旋转立方体,将用户操作转换为相同的 matrix3d 转换。
我想要的是一个旋转的立方体 css 当用户停止交互时从用户离开的地方开始。这是我通过获取立方体的变换 matrix3d 值并使用乘法动态设置 css 的关键帧而成功完成的事情。
但是,当用户开始与立方体交互时,立方体会跳转到其最后已知的手动旋转点并从那里继续,因为我不知道如何从 4x4 矩阵获取 X 轴和 Y 轴上的旋转.
我目前正在使用以下库 Rematrix,它帮助我从手动轮换到 css 轮换,如上所述。
我一直在研究关于 Euler 的文章,以及如何从 Euler 到矩阵,反之亦然,但正如我之前提到的,我认为这是我缺乏数学知识阻碍我前进的地方。我似乎无法弄清楚。
作为参考,以下是我阅读过的一些文章,试图解决我的问题。
- https://medium.com/@behreajj/3d-rotations-in-processing-vectors-matrices-quaternions-10e2fed5f0a3
- http://www.gregslabaugh.net/publications/euler.pdf
- https://www.learnopencv.com/rotation-matrix-to-euler-angles/
- https://css-tricks.com/get-value-of-css-rotation-through-javascript/
最后一个来源对我来说最有意义,但如果我是正确的,在这种情况下没有用,因为它是关于 2D 转换,而不是 3D。
我通过以下方式获取当前的 matrix3d:
const style = getComputedStyle(this.element).transform
const matrix = Rematrix.parse(style)
对于手动旋转,我使用基于用户鼠标位置(positionY、positionX)的矩阵乘法。
const r1 = Rematrix.rotateX(this.positionY)
const r2 = Rematrix.rotateY(this.positionX)
const transform = [r1, r2].reduce(Rematrix.multiply)
this.element.style[userPrefix.js + 'Transform'] = Rematrix.toString(transform)
从手动到 css 旋转 我使用以下函数:
const setCssAnimationKeyframes = (lastTransform, animationData) => {
const rotationIncrement = 90
let matrixes = []
for (let i = 0; i < 5; i++) {
const rX = Rematrix.rotateX(rotationIncrement * i)
const rY = Rematrix.rotateY(rotationIncrement * i)
const matrix = [lastTransform, rX, rY].reduce(Rematrix.multiply);
matrixes.push(matrix)
}
animationData.innerHTML = `
@keyframes rotateCube {
0% {
transform: ${Rematrix.toString(matrixes[0])};
}
25% {
transform: ${Rematrix.toString(matrixes[1])};
}
50% {
transform: ${Rematrix.toString(matrixes[2])};
}
75% {
transform: ${Rematrix.toString(matrixes[3])}};
}
100% {
transform: ${Rematrix.toString(matrixes[4])};
}
}
`;
}
请提供任何有用信息的答案或评论。尽管非常欢迎,但我不希望您提供完整的代码示例。非常感谢任何形式的有用信息。
第一次阅读:
因为我使用那里的术语。
嗯,我懒得把所有东西都等同于我的环境,但基于此:
对于任何旋转顺序,m
的结果 3D 旋转子矩阵将始终具有这些热量:
(+/-)sin(a)
(+/-)sin(b)cos(a)
(+/-)cos(b)cos(a)
(+/-)sin(c)cos(a)
(+/-)cos(c)cos(a)
只有它们的符号和位置会随着变换顺序和约定而改变。因此,要识别它们,请执行以下操作:
先设置一些非平凡的欧拉角
它们的
|sin|
,|cos|
值必须不同,所以 6 个值中的 none 将相同,否则这将不起作用!!!我选择了这些:
ex = 10 [deg] ey = 20 [deg] ez = 30 [deg]
计算旋转矩阵
m
因此按顺序对单位矩阵应用 3 次欧拉旋转。在我的设置中,结果矩阵如下所示:
double m[16] = { 0.813797652721405, 0.543838143348694,-0.204874128103256, 0, // Xx,Xy,Xz,0.0 -0.469846308231354, 0.823172926902771, 0.318795770406723, 0, // Yx,Yy,Yz,0.0 0.342020153999329,-0.163175910711288, 0.925416529178619, 0, // Zx,Zy,Zz,0.0 0 , 0 , 0 , 1 // Ox,Oy,Oz,1.0 };
请注意,我使用的是 OpenGL 约定,基向量
X,Y,Z
和原点O
由矩阵的行表示,矩阵是直接的。识别
(+/-)sin(a)
热a
可以是任何欧拉角,所以打印sin
个欧拉角:sin(ex) = 0.17364817766693034885171662676931 sin(ey) = 0.34202014332566873304409961468226 sin(ez) = 0.5
现在看到
m[8] = sin(ey)
所以我们找到了我们的热……现在我们知道了:ey = a = asin(m[8]);
识别
(+/-)???(?)*cos(a)
热量简单地为尚未使用的角度打印 cos(?)*cos(ey)。所以如果
ey
是 20 度,我打印 10 和 30 度 ...sin(10 deg)*cos(20 deg) = 0.16317591116653482557414168661534 cos(10 deg)*cos(20 deg) = 0.92541657839832335306523309767123 sin(30 deg)*cos(20 deg) = 0.46984631039295419202705463866237 cos(30 deg)*cos(20 deg) = 0.81379768134937369284469321724839
当我们再次查看
m
时,我们可以交叉匹配:sin(ex)*cos(ey) = 0.16317591116653482557414168661534 = -m[9] cos(ex)*cos(ey) = 0.92541657839832335306523309767123 = +m[10] sin(ez)*cos(ey) = 0.46984631039295419202705463866237 = -m[4] cos(ez)*cos(ey) = 0.81379768134937369284469321724839 = +m[0]
据此我们可以计算角度...
sin(ex)*cos(ey) = -m[ 9] cos(ex)*cos(ey) = +m[10] sin(ez)*cos(ey) = -m[ 4] cos(ez)*cos(ey) = +m[ 0] ------------------------ sin(ex) = -m[ 9]/cos(ey) cos(ex) = +m[10]/cos(ey) sin(ez) = -m[ 4]/cos(ey) cos(ez) = +m[ 0]/cos(ey)
所以最后:
--------------------------------------------- ey = asin(m[8]); ex = atan2( -m[ 9]/cos(ey) , +m[10]/cos(ey) ) ez = atan2( -m[ 4]/cos(ey) , +m[ 0]/cos(ey) ) ---------------------------------------------
就是这样。如果你有不同的 layout/conventions/transform 顺序,这种方法仍然有效......只有索引和符号会改变。这里是小 C++/VCL OpenGL 示例,我在 (X,Y,Z
order):
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
bool _redraw=true; // need repaint?
//---------------------------------------------------------------------------
double m[16]= // uniform 4x4 matrix
{
1.0,0.0,0.0,0.0, // Xx,Xy,Xz,0.0
0.0,1.0,0.0,0.0, // Yx,Yy,Yz,0.0
0.0,0.0,1.0,0.0, // Zx,Zy,Zz,0.0
0.0,0.0,0.0,1.0 // Ox,Oy,Oz,1.0
};
double e[3]={0.0,0.0,0.0}; // euler angles x,y,z order
//---------------------------------------------------------------------------
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;
void matrix2euler(double *e,double *m)
{
double c;
e[1]=asin(+m[ 8]);
c=cos(e[1]); if (fabs(c>1e-20)) c=1.0/c; else c=0.0;
e[0]=atan2(-m[ 9]*c,m[10]*c);
e[2]=atan2(-m[ 4]*c,m[ 0]*c);
}
//---------------------------------------------------------------------------
void gl_draw()
{
_redraw=false;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
// glLoadIdentity();
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslated(0.0,0.0,-10.0); // some distance from camera ...
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
int i;
// draw source matrix:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated(-1.0,0.0,0.0); // source matrix on the left
glMultMatrixd(m);
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(1.0,0.0,0.0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,1.0,0.0);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,0.0,1.0);
glEnd();
glPopMatrix();
// draw source matrix:
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated(m[12],m[13],m[14]); // source matrix in the middle
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3dv(m+0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3dv(m+4);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3dv(m+8);
glEnd();
glPopMatrix();
// draw euler angles
matrix2euler(e,m);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glTranslated(+1.0,0.0,0.0); // euler angles on the right
glRotated(e[0]*rad,1.0,0.0,0.0);
glRotated(e[1]*rad,0.0,1.0,0.0);
glRotated(e[2]*rad,0.0,0.0,1.0);
glBegin(GL_LINES);
glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(1.0,0.0,0.0);
glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,1.0,0.0);
glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,0.0,1.0);
glEnd();
glPopMatrix();
// glFlush();
glFinish();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
gl_init(Handle);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotated(10.0,1.0,0.0,0.0);
glRotated(20.0,0.0,1.0,0.0);
glRotated(30.0,0.0,0.0,1.0);
glGetDoublev(GL_MODELVIEW_MATRIX,m);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
if (_redraw) gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
gl_resize(ClientWidth,ClientHeight);
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
// Caption=Key;
const double da=5.0;
if (Key==37){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(+da,0.0,1.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
if (Key==39){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(-da,0.0,1.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
if (Key==38){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(+da,1.0,0.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
if (Key==40){ _redraw=true; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixd(m); glRotated(-da,1.0,0.0,0.0); glGetDoublev(GL_MODELVIEW_MATRIX,m); glPopMatrix(); }
}
//---------------------------------------------------------------------------
其中唯一重要的东西是 matrix2euler
函数,它按 x,y,z
顺序将矩阵 m
转换为欧拉角。它呈现 3 个坐标系轴。左边是 m
用作模型视图矩阵,中间是 m
的基向量使用身份模型视图,右边是通过计算的欧拉角构建的模型视图 ...
所有 3 个都应该匹配。如果左边和中间不匹配,那么你得到了不同的矩阵或布局约定。
此处 (10,20,30) [deg]
测试用例预览:
即使经过多次旋转(箭头键)也匹配...
gl_simple.h
可以在这里找到:
PS. 根据 platform/environment 计算可能需要一些边缘情况处理,例如 asin
大于 1
,除以零等。atan2
也有其怪癖...
[Edit1] 这里是自动完成所有这些操作的最终 C++ 示例:
//---------------------------------------------------------------------------
enum _euler_cfg_enum
{
_euler_cfg_a=0,
_euler_cfg_b,
_euler_cfg_c,
_euler_cfg__sina,
_euler_cfg_ssina,
_euler_cfg__sinb_cosa,
_euler_cfg_ssinb_cosa,
_euler_cfg__cosb_cosa,
_euler_cfg_scosb_cosa,
_euler_cfg__sinc_cosa,
_euler_cfg_ssinc_cosa,
_euler_cfg__cosc_cosa,
_euler_cfg_scosc_cosa,
_euler_cfgs
};
//---------------------------------------------------------------------------
void matrix2euler_init(double *e,double *m,int *cfg) // cross match euler angles e[3] and resulting m[16] transform matrix into cfg[_euler_cfgs]
{
int i,j;
double a,tab[4];
const double _zero=1e-6;
for (i=0;i<_euler_cfgs;i++) cfg[i]=-1; // clear cfg
// find (+/-)sin(a)
for (i=0;i<3;i++) // test all angles in e[]
{
a=sin(e[i]);
for (j=0;j<16;j++) // test all elements in m[]
if (fabs(fabs(a)-fabs(m[j]))<=_zero) // find match in |m[j]| = |sin(e[i])|
{ // store configuration
cfg[_euler_cfg_a]=i;
cfg[_euler_cfg__sina]=j;
cfg[_euler_cfg_ssina]=(a*m[j]<0.0);
j=-1; break;
}
if (j<0){ i=-1; break; } // stop on match found
}
if (i>=0){ cfg[0]=-1; return; } // no match !!!
// find (+/-)???(?)*cos(a)
a=cos(e[cfg[_euler_cfg_a]]);
i=0; if (i==cfg[_euler_cfg_a]) i++; tab[0]=sin(e[i])*a; tab[1]=cos(e[i])*a; cfg[_euler_cfg_b]=i;
i++; if (i==cfg[_euler_cfg_a]) i++; tab[2]=sin(e[i])*a; tab[3]=cos(e[i])*a; cfg[_euler_cfg_c]=i;
for (i=0;i<4;i++)
{
a=tab[i];
for (j=0;j<16;j++) // test all elements in m[]
if (fabs(fabs(a)-fabs(m[j]))<=_zero) // find match in |m[j]| = |tab[i]|
{ // store configuration
cfg[_euler_cfg__sinb_cosa+i+i]=j;
cfg[_euler_cfg_ssinb_cosa+i+i]=(a*m[j]<0.0);
j=-1; break;
}
if (j>=0){ cfg[0]=-1; return; } // no match !!!
}
}
//---------------------------------------------------------------------------
void matrix2euler(double *e,double *m,int *cfg) // compute euler angles e[3] from transform matrix m[16] using confing cfg[_euler_cfgs]
{
double c;
//-----angle------ --------------sign-------------- ----------index----------
e[cfg[_euler_cfg_a]]=asin ((cfg[_euler_cfg_ssina]?-1.0:+1.0) *m[cfg[_euler_cfg__sina ]]);
c=cos(e[cfg[_euler_cfg_a]]); if (fabs(c>1e-20)) c=1.0/c; else c=0.0;
e[cfg[_euler_cfg_b]]=atan2((cfg[_euler_cfg_ssinb_cosa]?-c:+c)*m[cfg[_euler_cfg__sinb_cosa]],
(cfg[_euler_cfg_scosb_cosa]?-c:+c)*m[cfg[_euler_cfg__cosb_cosa]]);
e[cfg[_euler_cfg_c]]=atan2((cfg[_euler_cfg_ssinc_cosa]?-c:+c)*m[cfg[_euler_cfg__sinc_cosa]],
(cfg[_euler_cfg_scosc_cosa]?-c:+c)*m[cfg[_euler_cfg__cosc_cosa]]);
}
//---------------------------------------------------------------------------
用法:
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;
// variables
double e[3],m[16];
int euler_cfg[_euler_cfgs];
// init angles
e[0]=10.0*deg;
e[1]=20.0*deg;
e[2]=30.0*deg;
// compute coresponding rotation matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotated(e[0]*rad,1.0,0.0,0.0);
glRotated(e[1]*rad,0.0,1.0,0.0);
glRotated(e[2]*rad,0.0,0.0,1.0);
glGetDoublev(GL_MODELVIEW_MATRIX,m);
// cross match e,m -> euler_cfg
matrix2euler_init(e,m,euler_cfg);
// now we can use
matrix2euler(e,m,euler_cfg);
这适用于任何转换顺序和/或 convention/layout。 init 仅被调用一次,然后您可以将转换用于任何变换矩阵...您还可以根据您的环境的 euler_cfg
结果编写自己的优化版本。