THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 20/07/2007 at 15:22, xxxxxxxx wrote:
User Information:
Cinema 4D Version: R8.2-R10
Platform: Windows ; Mac ; Mac OSX ;
Language(s) : C++ ;
---------
It took literally years (!) but I have found a stable and consistent method for converting Cinema 4D HPB or matrix/quaternion equivalent into exact ordered Euler angles - not equivalents (like Eberly, Shoemake, et al).
First, I'm going to pay homage to Greg Abbas for the only viable solution that I've ever encountered ever anywhere. The article that led to the solution is at this link:
http://www.allyourpixel.com/post/well-behaved-euler-angles/
Now, this is not a direct solution. It is a heuristic solution. That is, it expects smooth transitions (deltas/changes) in rotation to allow the conversion to Euler angles by way of the previous Euler angles as comparative equivalents. So, this won't work if you just type in any rotation and then get the matrix and convert - it requires that there is a previous state (x-dx, y-dx, z-dx) where dx,dy,dz are relatively small angle deltas. This makes it very good for user interaction with the mouse - but not good for random values entered into the HPB coords (etc.).
This is exactly the idea that I had - but without the mathematical rigor needed to develop the heuristic algorithm.
The code considers rotation order (XYZ, XZY, YXZ, YZX, ZXY, ZYX) and is taking a previous representation in a left-hand (-z) and applying it to results in a right-hand systems (note the z negation at the end). The equivalent angles are extracted from the matrix using David Eberly's routines first. Then they are made 'well-behaved' using Greg Abbas' algorithm. This is not a general solution for rotations greater than 360d. I leave it up to the user to generalize for that case. Note that the general solution must be considered both for +/-360d integrals and for the 180d identity similarly.
static CHAR* RotOrdStr[7] = { "", "XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX" };
// IPPDial.HPBToPoser - Convert HPB to Poser ordered-Euler angles
//*---------------------------------------------------------------------------*
void IPPDial::HPBToPoser(LONG rotorder, Matrix mat, Vector prot)
//*---------------------------------------------------------------------------*
{
Vector v1;
Vector v2;
Real sx = 1.0f, sy = 1.0f, sz = 1.0f;
Real x, y, z;
Real ox = prot.x, oy = prot.y, oz = prot.z;
// Take into account Rotation Order and extract Euler angles
switch (rotorder)
{
case ROTORDER_XYZ:
v1 = mat.v1;
v2 = mat.v2;
y = asin(v1.z);
if (y < pi05)
{
if (y > -pi05)
{
x = Angle(-v2.z, mat.v3.z);
z = Angle(-v1.y, v1.x);
}
else
{
x = -Angle(v2.x, v2.y);
z = 0.0f;
}
}
else
{
x = Angle(v2.x, v2.y);
z = 0.0f;
}
sy = -sy;
break;
case ROTORDER_XZY:
v1 = mat.v1;
v2 = mat.v3;
z = asin(-v1.y);
if (z < pi05)
{
if (z > -pi05)
{
x = Angle(v2.y, mat.v2.y);
y = Angle(v1.z, v1.x);
}
else
{
x = -Angle(-v2.x, v2.z);
y = 0.0f;
}
}
else
{
x = Angle(-v2.x, v2.z);
y = 0.0f;
}
sz = -sz;
break;
case ROTORDER_YXZ:
v1 = mat.v1;
v2 = mat.v2;
x = asin(-v2.z);
if (x < pi05)
{
if (x > -pi05)
{
y = Angle(v1.z, mat.v3.z);
z = Angle(v2.x, v2.y);
}
else
{
y = -Angle(-v1.y, v1.x);
z = 0.0f;
}
}
else
{
y = Angle(-v1.y, v1.x);
z = 0.0f;
}
sx = -sx;
break;
case ROTORDER_YZX:
v1 = mat.v3;
v2 = mat.v2;
z = asin(v2.x);
if (z < pi05)
{
if (z > -pi05)
{
y = Angle(-v1.x, mat.v1.x);
x = Angle(-v2.z, v2.y);
}
else
{
y = -Angle(v1.y, v1.z);
x = 0.0f;
}
}
else
{
y = Angle(v1.y, v1.z);
x = 0.0f;
}
sz = -sz;
break;
case ROTORDER_ZXY:
v1 = mat.v1;
v2 = mat.v3;
x = asin(v2.y);
if (x < pi05)
{
if (x > -pi05)
{
z = Angle(-v1.y, mat.v2.y);
y = Angle(-v2.x, v2.z);
}
else
{
z = -Angle(v1.z, v1.x);
y = 0.0f;
}
}
else
{
z = Angle(v1.z, v1.x);
y = 0.0f;
}
sx = -sx;
break;
case ROTORDER_ZYX:
v1 = mat.v1;
v2 = mat.v3;
y = asin(-v2.x);
if (y < pi05)
{
if (y > -pi05)
{
z = Angle(mat.v2.x, v1.x);
x = Angle(v2.y, v2.z);
}
else
{
z = -Angle(-v1.y, v1.z);
x = 0.0f;
}
}
else
{
z = Angle(-v1.y, v1.z);
x = 0.0f;
}
sy = -sy;
break;
}
Vector d1, d2;
Real dc, dp, dn;
Real adc, adp, adn;
// Find minimum relation in full revolutions (factors of 2*pi or 360d)
// r10 = r +/- (n*pi2)
// r11 = r +/- (n*pi2)
// r12 = r +/- (n*pi2)
// - X
dc = x;
dp = x + pi2;
dn = x - pi2;
adc = Abs(x - ox);
adp = Abs(dp - ox);
adn = Abs(dn - ox);
d1.x = (adc < adp) ? ((adc < adn) ? dc : dn) : ((adp < adn) ? dp : dn);
// - Y
dc = y;
dp = y + pi2;
dn = y - pi2;
adc = Abs(y - oy);
adp = Abs(dp - oy);
adn = Abs(dn - oy);
d1.y = (adc < adp) ? ((adc < adn) ? dc : dn) : ((adp < adn) ? dp : dn);
// - Z
dc = z;
dp = z + pi2;
dn = z - pi2;
adc = Abs(z - oz);
adp = Abs(dp - oz);
adn = Abs(dn - oz);
d1.z = (adc < adp) ? ((adc < adn) ? dc : dn) : ((adp < adn) ? dp : dn);
// Make relative to hemi revolution (factor of pi or 180d)
// r20 = pi + r10 +/- (n*pi2)
// r21 = pi - r11 +/- (n*pi2)
// r22 = pi + r12 +/- (n*pi2)
// sx,sy,sz handle subtraction on second rotation (pi - r + (n*pi2))
// - X
dc = pi + (sx*x);
dp = pi + (sx*x) + pi2;
dn = pi + (sx*x) - pi2;
adc = Abs(dc - ox);
adp = Abs(dp - ox);
adn = Abs(dn - ox);
d2.x = (adc < adp) ? ((adc < adn) ? dc : dn) : ((adp < adn) ? dp : dn);
// - Y
dc = pi + (sy*y);
dp = pi + (sy*y) + pi2;
dn = pi + (sy*y) - pi2;
adc = Abs(dc - oy);
adp = Abs(dp - oy);
adn = Abs(dn - oy);
d2.y = (adc < adp) ? ((adc < adn) ? dc : dn) : ((adp < adn) ? dp : dn);
// - Z
dc = pi + (sz*z);
dp = pi + (sz*z) + pi2;
dn = pi + (sz*z) - pi2;
adc = Abs(dc - oz);
adp = Abs(dp - oz);
adn = Abs(dn - oz);
d2.z = (adc < adp) ? ((adc < adn) ? dc : dn) : ((adp < adn) ? dp : dn);
// Find closest to reference triplet (prot)
Real d1a, d2a;
d1a = Abs(d1.x - ox);
d2a = Abs(d2.x - ox);
x = (d1a < d2a) ? d1.x : d2.x;
d1a = Abs(d1.y - oy);
d2a = Abs(d2.y - oy);
y = (d1a < d2a) ? d1.y : d2.y;
d1a = Abs(d1.z - oz);
d2a = Abs(d2.z - oz);
z = -((d1a < d2a) ? d1.z : d2.z);
GePrint("Rotation ("+String(RotOrdStr[rotorder])+") = "+VectorToString(Vector(ToDeg(x),ToDeg(y),ToDeg(z)))+" (xyz)");
}
Notes: Angle() is a 'smart' atan2() wrapper:
// Return Real angle (in radians) whose tangent = y/x
//*---------------------------------------------------------------------------*
Real Angle(Real y, Real x)
//*---------------------------------------------------------------------------*
{
if (Abs(x) < 0.000001f)
{
if (Abs(y) < 0.000001f) return 0.0f;
if (y > 0.0f) return (RAD_90);
return (RAD_270);
}
return atan2(y,x);
}
And ToDeg() should be obvious. It is an inline function to convert radians to degrees:
inline Real ToDeg(Real r) { return ((r)*57.295779513082320876798154814105f); }