Game Development Community

Convert transform rotations to Euler

by Brendan Fletcher · in Technical Issues · 12/13/2007 (8:06 am) · 16 replies

I need a routine to convert a transform rotation to an Euler rotation. (Never would have guessed, huh?)

It just needs to output an Euler rotation that, when plugged in to MatrixCreateFromEuler, will bring back the original transform rotation. I found a routine at euclideanspace.com/maths/geometry/rotations/conversions/angleToEuler/index.htm, but it doesn't match up with Torque's definitions for Euler angles.

Thanks in advance,
calc84maniac

About the author

Recent Threads


#1
12/13/2007 (1:05 pm)
Construct a matrix from the axis-angle, then ask the matrix for its Torque-ish Euler angle sequence.
(This answer could be more precise, if asked in the registered forums. If you have the code, check the matrix, axis-angle headers for the appropriate methods.)

(If you have to do it in script only, ask again.)


Be aware that anytime you do the "factoring a rotation matrix for its Euler angles" you are risking a failure. It won't work in all cases:
axis angle -> Matrix ->(this is the suspect factoring)-> Euler -> original Matrix back again ????

When factoring a matrix to an Euler rotation sequence, you have to guess whether one of the angles was greater than or less than 90 degrees. Usually, the factorings assume that the critical angle is less than 90 degrees. (In Torque's case, the "pitch" angle.) If your airplane just did a half loop, you are screwed.

To recreate a matrix reliably, you need to have stored it as at least 4 parameters (axis angle or quaternion) rather than just three Euler angles. I wouldn't be surprised if the API is designed consciously to make shooting your self in the foot this way more difficult by restricting the interface.

Per your reference, I believe the axes in his "NASA Standard Aeroplane" aren't close enough to Torque. The Z axis in Torque is Up, wth Y forward, X out the right wing. You need a Z X Y factoring to match Torque, rather than what he presumably did (Z Y X).
#2
12/13/2007 (1:16 pm)
@Brendan, why don't you just use the MatrixF class toEuler() function?

It works thusly:
EulerF rot = transform.toEuler();
where transform is your MatrixF.
#3
12/13/2007 (1:35 pm)
I think Brendan will need something in script,
since according to his profile he doesn't have the SDK.
#4
12/13/2007 (2:57 pm)
I think this is a serious oversight!

Upon reviewing the situation, by default the torque scripts provides absolutely NO WAY of doing this other than handwriting a ton of formulas. There is no support for Eulers or Quats and very little for Matrix (which is actually an Angle Axis when in script! if it was a real matrix we could easily code the toEuler method in script but its not).

GG needs to seriously add some more console functions for math related things.

In order to get from a "Script Matrix" (aka Angle Axis) to an Euler Torque internally even converts it first to a Quaternion.



However there is probably a better way to do what you are looking for.



Why do you need to get the euler notation of matrix in the first place? Not that I can't think of reasons but I think you can solve your problem another way.

Instead of getting the eulers, transforming them and setting a new matrix. Why not just keep track of the change. Create a matrix from the change euler and multiply the object's current transform by the change matrix.

I can't really help more unless I know the circumstances in which you are trying to use this method.
#5
12/13/2007 (3:24 pm)
@Brian, er...you do realize it would be seriously trivial to expose it to script, provided one actually owns the engine, right? I honestly don't see what the problem is. I mean, most people that have it would probably be doing this sort of operation in C++ anyway, since it's likely it's something performance intensive.
#6
12/13/2007 (4:40 pm)
Well, this is for a mod of a game (which means script-only). I'm using the transforms to rotate objects (static shapes, to be exact) around a global axis, but I have to convert back to euler for compatibility. Whoever made the original mod probably didn't know about multiplying transforms. I can't really change the whole thing because this is a small addon mod. So that's the situation.

Here's a routine that ALMOST works:
function rotUnConv(%mat) {
  %z = getWord(%mat,0);
  %x = getWord(%mat,1);
  %y = getWord(%mat,2);
  %ang = getWord(%mat,3);
  %s = mSin(%ang);
  %t = 1-mCos(%ang);
  %test = %x*%y*%t + %z*%s;
  if (%test > 0.998) { // north pole singularity detected
    %heading = 2*mATan(%x*mSin(%ang/2),mCos(%ang/2));
    %attitude = $piover2;
    %bank = 0;
    return vectorScale(%bank SPC -%attitude SPC %heading,180/$pi);
  }
  if (%test < -0.998) { // south pole singularity detected
    %heading = -2*mATan(%x*mSin(%ang/2),mCos(%ang/2));
    %attitude = -$piover2;
    %bank = 0;
    return vectorScale(%bank SPC -%attitude SPC %heading,180/$pi);
  }
  %heading = mATan(%y*%s - %x*%z*%t,1 - (%y*%y+%z*%z)*%t);
  %attitude = mASin(%test);
  %bank = mATan(%x*%s - %y*%z*%t,1 - (%x*%x+%z*%z)*%t);
  return vectorScale(%bank SPC -%attitude SPC %heading,180/$pi);
}
It works in some cases, but not in others. I think a routine like this (that works) could be made, so it's possible to do this without "tons of formulae".
#7
12/13/2007 (5:15 pm)
@Brendan, that makes sense then. It's something that the authors of the game should probably have thought about when making their game compatible with modes in general. In that case though, not much for it but to just do the calculations.
#8
12/14/2007 (8:22 am)
This ordering seems odd if %mat is really a Torque axis angle (words 3..6 of a 7 element transform string). Shouldn't it be "x y z angle" ?

function rotUnConv(%mat) {
%z = getWord(%mat,0);
%x = getWord(%mat,1);
%y = getWord(%mat,2);

Perhaps if you gave your test case (inputs and expected outputs)?
#9
12/14/2007 (8:54 am)
Matthew is right;
if %mat is a transformation returned by say %someShape.getTransform(),
then the code should be
%axisX = getWord(%mat,3);
%axisY = getWord(%mat,4);
%axisZ = getWord(%mat,5);
%theta = getWord(%mat,6);
#10
12/14/2007 (1:03 pm)
I happen to have my own quaternion library available so I was able to create a TorqueScript solution to the original question far above without just pasting parts of the Torque math library here.
The question this answers is: how to convert from a TorqueScript "transform" (axis angle representation of attitude) to Euler angles such that the Euler angles when input to the MatrixCreateFromEuler console function returns the original transform's Axis Angle attitude representation.

// Returns Euler angles "X SPC Y SPC Z" of the Torque ZXY sequence in radians 
// that results in the same rotation matrix as the input axis angle from a "transform"
function eulerFromAxisAngle(%axisAngle) // words 3..6 of a transform
{
   // axis angle to quaternion (quaternion in Kuipers convention, see below)
   //
   %angleOver2 = getWord(%axisAngle,3) * 0.5;
   
   // Invert sign of angle per Torque conventions before factoring
   // Needed because MatrixF(EulerF).transpose == AngAxisF(EulerF).setMatrix(&MatrixF)
   %angleOver2 = -%angleOver2;
   
   %sinThetaOver2 = mSin(%angleOver2);
   %cosThetaOver2 = mCos(%angleOver2);
   %q0 = %cosThetaOver2;
   %q1 = getWord(%axisAngle,0) * %sinThetaOver2;
   %q2 = getWord(%axisAngle,1) * %sinThetaOver2;
   %q3 = getWord(%axisAngle,2) * %sinThetaOver2;

   // quaternion to matrix
   //
   // (elements not required for this function are commented out)
   
   // Ref: "Quaternions and Rotation Sequences,"
   // Jack B. Kuipers,  p.169
   
   // To convert from this quaternion system to Torque's: 
   // x y z w = q1 q2 q3 q0
   
    %q0q0 = %q0*%q0;
//    %q1q1 = %q1*%q1;
    %q1q2 = %q1*%q2;
    %q0q3 = %q0*%q3;
    %q1q3 = %q1*%q3;
    %q0q2 = %q0*%q2;
    %q2q2 = %q2*%q2;
    %q2q3 = %q2*%q3;
    %q0q1 = %q0*%q1;
    %q3q3 = %q3*%q3;

   // mRC where R is the row (1..3) and C is the column (1..3)
   // e.g.: m12 = row 1, column 2
   
//   %m11 = 2.0*%q0q0 - 1.0 + 2.0*%q1q1;
//      %m12 = 2.0*(%q1q2 + %q0q3);
         %m13 = 2.0*(%q1q3 - %q0q2);

   %m21 = 2.0*(%q1q2 - %q0q3);
      %m22 = 2.0*%q0q0 - 1.0 + 2.0*%q2q2;
         %m23 = 2.0*(%q2q3 + %q0q1);

//   %m31 = 2.0*(%q1q3 + %q0q2);
//      %m32 = 2.0*(%q2q3 - %q0q1);
         %m33 = 2.0*%q0q0 - 1.0 + 2.0*%q3q3;

   // Factor matrix to Euler angles, Torque's Z X Y sequence
   //
   
   // Torque Matrix from Euler angle Z then X then Y sequence:
   //   cy cz - sx sy sz            cz sx sy + cy sz            -(cx sy)
   //   -(cx sz)                    cx cz                       sx
   //   cz sy + cy sx sz            -(cy cz sx) + sy sz         cx cy
   
   %eZ = mAtan(-%m21, %m22);  // heading
   %eX = mAsin(%m23);         // pitch
   %eY = mAtan(-%m13, %m33);  // roll
   
   return %eX SPC %eY SPC %eZ; // radians
}

Notice this transpose operation early in the script above:
// Invert sign of angle per Torque conventions before factoring
   // Needed because MatrixF(EulerF).transpose == AngAxisF(EulerF).setMatrix(&MatrixF)
   %angleOver2 = -%angleOver2;

This has to do with exactly how Torque treats Euler angles and Axis Angle attitude representations.
This is discussed in more detail in the (SDK required) thread:
www.garagegames.com/mg/forums/result.thread.php?qt=70308

Test script:
// usage: testEulerFromAxisAngle("60 70 80"); 
function testEulerFromAxisAngle(%eulerVec) // input Euler angles in X,Y,Z order in degrees.
{
   %eX = mDegToRad(getWord(%eulerVec,0));
   %eY = mDegToRad(getWord(%eulerVec,1));
   %eZ = mDegToRad(getWord(%eulerVec,2));
   
   %transform = MatrixCreateFromEuler(%eX SPC %eY SPC %eZ);
   echo("MatrixCreateFromEuler transform= " SPC %transform);
   
   %eulerOut = eulerFromAxisAngle(getWords(%transform,3,6));
   
   // convert to degrees for output comparison
   %exOut = mRadToDeg(getWord(%eulerOut,0));
   %eyOut = mRadToDeg(getWord(%eulerOut,1));
   %ezOut = mRadToDeg(getWord(%eulerOut,2));
   echo("eulerFromAxisAngle result = " SPC %exOut SPC %eyOut SPC %ezOut SPC "(degrees)");
   
   MatrixCreateFromEuler(%eulerOut);
   echo("MatrixCreateFromEuler(" @ %eulerOut @ ") transform = " SPC %transform);
}

Results:
==>testEulerFromAxisAngle("60 70 80");
MatrixCreateFromEuler transform=  0 0 0 0.00593316 -0.689798 -0.723978 2.407
eulerFromAxisAngle result =  60.0001 70 79.9998 (degrees)
MatrixCreateFromEuler(1.0472 1.22173 1.39626) transform =  0 0 0 0.00593316 -0.689798 -0.723978 2.407
----------------
==>testEulerFromAxisAngle("120 70 80");
MatrixCreateFromEuler transform=  0 0 0 -0.359101 -0.6757 -0.643798 3.15267
eulerFromAxisAngle result =  60.0001 -110 -100 (degrees)
MatrixCreateFromEuler(1.0472 -1.91987 -1.74533) transform =  0 0 0 -0.359101 -0.6757 -0.643798 3.15267

Note that Euler angles can have problems for certain cases as shown in the second test case. Why factoring matrices (or axis angles) for Euler angles is not really a good idea is discussed in previous posts in this thread.

edited: Realized I had originally posted the test of a C++ version of the method, rather than the TorqueScript version - fixed.
#11
12/14/2007 (6:29 pm)
Now what was that I was saying about 'loads of formula'?

Just because you shouldn't use something ALL the time for performance issues doesn't mean it shouldn't be used.

It would be MUCH easier to expose ONE function to script to make this work.

However, Good Job Matthew! That is an excellent piece of work and exactly what I was talking about.
#12
12/14/2007 (7:20 pm)
@Brian, yeah, this is pretty much a weird case of not having the source... If this wasn't for a mod or whatnot, it would be a 2-3 minute change to expose toEuler to script and recompile, bam done.
#13
12/14/2007 (8:07 pm)
We certainly have to remember our modders. We have an engine that allows us to easily give some control to our players that can allow them to shape and craft our worlds or even mod our entire game. We need to provide them with the basic tools they need. If a function could cause problems (like using toEuler) we should inform our modders of the situation. There are many situations in which toEuler will work fine, especially for orienting players that tend to keep themselves straight up anyways.

We should implement a better set of extended math functions for script use.
#14
12/14/2007 (8:33 pm)
Yeah, like I said this is something that the developers should have thought of when implementing things for modders to use.
#15
12/15/2007 (12:44 am)
Nice matthew !
#16
01/06/2008 (4:53 pm)
Thanks a lot, my routine works now. I made a more concise version of your code (that actually does return it exactly with degrees and everything):

function rotUnConv(%axisAngle)
{
   %angleOver2 = -getWord(%axisAngle,3)/2;
   %sinThetaOver2 = mSin(%angleOver2);

   %q0 = mCos(%angleOver2);
   %q1 = getWord(%axisAngle,0) * %sinThetaOver2;
   %q2 = getWord(%axisAngle,1) * %sinThetaOver2;
   %q3 = getWord(%axisAngle,2) * %sinThetaOver2;
   %q4 = %q0*%q0;

   return vectorScale(mAsin(2*(%q2*%q3 + %q0*%q1))
                  SPC mAtan(%q0*%q2 - %q1*%q3, (%q4+%q3*%q3)-0.5)
                  SPC mAtan(%q0*%q3 - %q1*%q2, (%q4+%q2*%q2)-0.5) ,-180/$pi);
}

Edit: Changed code, realized that I could divide both arguments of mATan by 2.