Game Development Community

dev|Pro Game Development Curriculum

Torque3D Script Rotations Explained and Converted

by Matthew Genge · 02/12/2012 (6:04 am) · 4 comments

The rotation of an object in Torque3D returned by getTransform() is rather confusing. It consists of four numbers, the first three between -1 and 1 and the last between -120 and 240 e.g. [0.707 0.707 0 40.0]. Simply rotating an object in the world editor you'll see when you rotate around the z axis you get rotations like [0 0 1 36.0] which can make you think these numbers are three fractions of rotation around an axis (fractional euler angles) and the maximum angle. But not so! The first three numbers are the x, y and z components of a unit vector defining a rotation axis around which the object is rotated by an angle given by the final number. It is a notation called axis-angle representation. You can prove it to yourself in the world editor, just set the rotation of a shape to [1 0 0 0] then chose an axis and imagine how the model would look if rotated by a certain angle around that axis, then enter the numbers for that axis and the angle. Have paracetemol ready.

Vectors for Noobs
For those not familiar with vectors....you will need to learn about them if you ever want to make a game. Think of a vector as an arrow pointing at something saying "it is there". If the arrow goes all the way from you to the object, and your position is 0, 0, 0....then it defines the position of the object and is known as a position vector. It has a direction and a distance. If you were to describe to someone the direction and size of the arrow you might say "it goes from me two feet left, one foot down and five feet forwards". These distances are the components of the vector in the x, y and z directions. The arrow also has a length, known as its magnitude. The length is the square root of the sum of its components squared (remember pythagoras). A unit vector is simply a vector that has a length of one (it has been divided by its magnitude) and defines only a direction. They are useful for describing orientations.

Easier Rotations
Axis angle rotations are not easy to picture in our heads. When most people think about rotating objects, for example a sofa being carried down the stairs, we don't say "let's rotate it 30 degrees around an axis off to the right a bit", we think in terms of "turn it to the right", "lean it down a bit", "roll it to the right". These are body fixed rotations. A useful way of describing them is with the terms roll, pitch and yaw, where pitch means to lean forwards or backwards, yaw means to turn right or left, and roll is pretty obvious. It would be useful to convert between roll, pitch and yaw and axis angle.

Converting
We can convert roll, pitch, roll to axis angle and vice a versa by converting them to quaternions. These are complex numbers that can describe rotations...they can be thought of as an arcane maths magic...all we need to know is they have four component numbers qw, qx, qy and qz. I am sure experts will tell us that all we need is the quaternion, but we are interested in something that makes sense that we can visualise.

The function below converts from roll, pitch, yaw into axis angle, the rotation input for setTransform():

function RPYtoTransform(%roll, %pitch, %yaw){
   
   //Convert roll, yaw and pitch in radians to axis angle from  setTransform

   %cr = mCos(%roll/2.0);
   %sr = mSin(%roll/2.0);
   %cy = mCos(%yaw/2.0);
   %sy = mSin(%yaw/2.0);
   %cp = mCos(%pitch/2.0);
   %sp = mSin(%pitch/2.0);
   
   // Convert to quaternion
   %qw = %cr*%cy*%cp + %sr*%sy*%sp;
   %qx = %sr*%sy*%cp + %cr*%cy*%sp;
   %qy = %sr*%cy*%cp + %cr*%sy*%sp;
   %qz = %cr*%sy*%cp - %cr*%cy*%sp;
   
   %denom = mSqrt(1-%qw*%qw);
   if(%denom == 0){
      %out = "1 0 0 0";
   } else {
      %theta = 2.0*mAcos(%qw);
      %x = %qx/%denom;
      %y = %qy/%denom;
      %z = %qz/%denom;
      %out = %x SPC %y SPC %z SPC %theta;
   }
   
   return %out;
   
}

The next function converts back again:

function TransformtoRPY(%transform){
   
   //Input is the rotational part of the transform
   
   %x = getWord(%transform,0);
   %y = getWord(%transform,1);     
   %z = getWord(%transform,2);   
   %theta = getWord(%transform,3);
   
   //Convert to quaternion
   %qw = mCos(%theta/2.0);
   %st = mSin(%theta/2.0);
   %qx = %x*%st;
   %qy = %y*%st;
   %qz = %z*%st;
   
   //Convert to roll, yaw and pitch
   
   %yaw = mAsin(2.0*%qx*%qy + 2.0*%qz*%qw);
   %roll = mAtan(2.0*%qy*%qw - 2*%qx*%qz, 1.0 - 2.0*%qy*%qy - 2.0*%qz*%qz);
   %pitch = mAtan(2.0*%qx*%qw - 2.0*%qy*%qz, 1.0 - 2.0*%qx*%qx - 2.0*%qz*%qz);
   
   //Correct pitch to -pi/2 to pi/2
   if(%pitch<=-mPi()/2.0){
      %pitch = -(mPi() + %pitch);
   }
   if(%pitch>=mPi()/2.0){
      %pitch = (mPi() - %pitch);
   }
   
   
   %out = %roll SPC %pitch SPC %yaw;
   
   return %out;
      
}

Of course it would be much better and faster to write conversion functions in C++ and make changes to the engine. This is quite clunky in comparison. There are also much more clever ways of converting with matrices, but this is a simple way of doing it in script. The functions may crash and burn, since I haven't constrained angle to between -110 and 240, but so far they seem to work for me.

#1
02/13/2012 (9:46 pm)
Nice post. I just posted my own function to convert from axis-angle to Euler rotations here as a supplement to your guide.
#2
02/15/2012 (1:56 am)
A few years back I was given the task to take data from a Midge device. My task was to take data that was being streamed at 115K bps constantly and use it in control code for a flying vehicle. I read up on the device and found it spat out quaternions. So I learned how to convert quaternions to euler angles so I could understand what I was even looking at! I completely understand why you wrote this code. The angles make practically no sense without some "interpretation". Good job!
#3
02/15/2012 (1:23 pm)
Thanks for the explanations.
Why is the range -120 to +240 and not -180 to +180?
#4
02/16/2012 (3:12 pm)
No idea, in fact learnt that from someone elses post.

Btw I have edited my post the transformtoRPY() was not entirely correct (i.e. wrong). It is right now.