I have some code from Graphics Gems IV. It extracts Euler angles from a transformation matrix. I want to understand how it works, as it is much more generic than some other examples of similar code, and some of the variable names were opaque. (I have re-named those that I could)
The function is broken into two parts. The first un-packs an "Order" integer into several flags and some offsets into the matrix:
int EulGetOrd(const int order,
int& i,
int& j,
int& k,
int& eulParity,
int& eulRepeat,
int& eulFrame)
{
const unsigned char EulSafe[] = "\000\001\002\000";
const unsigned char EulNext[] = "\001\002\000\001";
unsigned o = static_cast<unsigned int>(order);
// Extract the first bit flag
// Either EulFrameS (0) or EulFrameR (1)
eulFrame = o & 1;
o >>= 1;
// Extract the second bit flag
// Either EulRepeatNo (0) or EulRepeatYes (1)
eulRepeat = o & 1;
o >>= 1;
// Extract the third bit flag
// Either EulParityEven (0) or EulParityOdd (1)
eulParity = o & 1;
o >>= 1;
// Not sure exactly what's going on here
i = EulSafe[o & 3];
j = EulNext[i + eulParity];
k = EulNext[i + 1 - eulParity];
if(eulRepeat)
{
return k;
}
else
{
return i;
}
}
The second is the function that does the common atan2() operations to actually extract the Euler angles:
Quat Eul_FromHMatrix(double M[4][4], int order)
{
EulerAngles ea;
// Not sure what ijk are, exactly
int i, j, k, parity, eulerRepeat, frame;
// Un-pack the Order integer into each flag and the offsets(?)
EulGetOrd(order, i, j, k, parity, eulerRepeat, frame);
if (eulerRepeat == EulRepeatYes)
{
double sy = sqrt(M[i][j] * M[i][j] + M[i][k] * M[i][k]);
if (sy > 16 * static_cast<double>(FLT_EPSILON))
{
ea.x = atan2(M[i][j], M[i][k]);
ea.y = atan2(sy, M[i][i]);
ea.z = atan2(M[j][i], -M[k][i]);
}
else
{
ea.x = atan2(-M[j][k], M[j][j]);
ea.y = atan2(sy, M[i][i]);
ea.z = 0;
}
}
else
{
double cy = sqrt(M[i][i] * M[i][i] + M[j][i] * M[j][i]);
if (cy > 16 * static_cast<double>(FLT_EPSILON))
{
ea.x = atan2(M[k][j], M[k][k]);
ea.y = atan2(-M[k][i], cy);
ea.z = atan2(M[j][i], M[i][i]);
}
else
{
ea.x = atan2(-M[j][k], M[j][j]);
ea.y = atan2(-M[k][i], cy);
ea.z = 0;
}
}
if (parity == EulParityOdd)
{
ea.x = -ea.x;
ea.y = -ea.y;
ea.z = -ea.z;
}
// "R" for "Reverse"? Best guess.
if (frame == EulFrameR)
{
double t = ea.x;
ea.x = ea.z;
ea.z = t;
}
ea.w = order;
return (ea);
}
My questions:
1: What do "parity", "frame" and "repeat" conceptually mean in terms of Euler angles/transformation matrices? (I tried searching this online, but found little referencing these terms)
2: What do i, j and k represent? I noticed the following output if I set the non-flag portion of order to various values, I could get the following output:
Even:
i, j, k
0, 1, 2
1, 2, 0
2, 0, 1
0, 1, 2
0, 1, 2
1, 2, 0
2, 0, 1
0, 1, 2
Odd:
i, j, k
0, 2, 1
1, 0, 2
2, 1, 0
0, 2, 1
0, 2, 1
1, 0, 2
2, 1, 0
0, 2, 1
To me, it looks like the integer portion of order is just a simple offset into the order of 0,1,2. Shift that by one to the left and you get 1,2,0, as seen on the second line of the "even" data. When parity is "odd" these are shifted to the right instead and the order seems reversed. But even if that is the intended use, I still don't see why it's needed. It also would break down past about 3 (odd).
With the default arguments I see used elsewhere, X would either be:
ea.x = atan2(M[2][1], M[2][2]);
or:
ea.x = atan2(-M[1][2], M[1][1]);
Neither of which line up with the calculation of X I see in Mike Day's implementation:
x = atan2(-M[1][2], M[2][2])
Maybe this has to do something with row/column ordering? There's a lot of moving parts here.
Update:
While the code is not documented, I did find discussion in Graphics Gems itself on this.
Parity: Even if the inner axis X is followed by the middle axis Y, or Y is followed by Z, or Z is followed by X. Otherwise, parity is odd.
Repetition: Whether the first and last axes are the same or different
Frame: Choice of either static (S) or rotating (R) frame, applies to all 3 axis. With static, the inner axis is the first axis, with rotating it is last.
I don't really understand it, but it's better than nothing.