Game Development Community

Rotation constraints

by Radoslaw Marcin Kurczewski · in Torque Game Engine · 03/12/2003 (4:05 am) · 7 replies

Hi,

I've written new class inherited from Trigger and now I'd like to limit rotation of trigger box displayed in level editor to one, vertical (Z) axis.
I tried to overload 'setTransform' method but how can I get rotation angles from transformation matrix? All I need is to zero X and Y rotation...

Greetings

Ania

#1
03/12/2003 (6:22 am)
MatrixF mat = getTransform();
VectorF rot;
mat.getColumn(3, &rot);
F32 angle = mAtan(rot.x, -rot.y);

That SHOULD get you your rotation angle. I'm not quite sure because I don't have an example right here with me, but the only difference would be the - sign in the mAtan might be on the x var, or the 3 might be a 1 in getColumn.

That should be rotation about the Z axis, which is the most common type of rotation that you'd be looking for. Just change the vars in mAtan to get the other rotations.
#2
03/12/2003 (1:49 pm)
If you want just the Z rotation to be preserved from an arbitrary rotation, the usual Torque method is to factor the rotation matrix into an Euler angle, and set the Euler angle's x and y to zero. Torque adds an extra twist in that it uses negative angles in the Euler angle compared to most texts (or at least the texts I own). Instead of doing the factoring, I just use identities to translate between Torque and text. Fortunately for this case there is some code buried away in worldEditor.cc. I just adapted that code and commented profusely :).

Also, Torque converts between Euler angles and transformation matrices using Roll->Pitch->>Yaw ordering, implying RzRxRy. You can usually just look up RzRxRy factoring in a text or on the web, except remember that Torque is using negative angles. For that reason you can look up RyRxRz instead, and take into account the negative angles.

// Return the rotation about the Z axis (Yaw).  The maximum Yaw is
// always factored out (i.e.  in case of gimbal lock, Roll is always
// set to zero -- this might be counterintuitive if the user derives
// the original rotation using some amount of Roll, since this converts
// all the Roll into Yaw -- but at least that only happens when gimbal
// locked).
F32 extractZ(const MatrixF & matrix) {
  const F32 * mat = (const F32*)matrix;
  F32 Z;
  // Usually the matrix would be factored as RzRxRy,
  // but Euler angles are negative in Torque so really
  // R(-z)R(-x)R(-y) is being factored.
  // R(-z)R(-x)R(-y) =
  // Rz^-1Rx^-1Ry^-1 = (RR^-1=I)
  // Rz^TRx^TRy^T =    (R^-1=R^T)
  // Rz^T(RyRx)^T =    (A^TB^T=(BA)^T
  // (RyRxRz)^T        (A^TB^T=(BA)^T
  //
  // RyRzRz is more common in texts, so just use that
  // and work with the matrix transpose, and remember
  // that the text is computing (from Torque's perspective)
  // the negative angle.  The following is lifted from
  // extractEuler in worldEditor.cc.
  //
  // Transpose is easy, just transpose indices in
  // matrix calculations.
  //
  // Negative angle, sin(-theta) = -sin(theta), so
  // sin^-1(-theta) = -sin^1(theta).
  // Text:   thetaX = sin^-1(-R[1,2]).
  // Torque: thetaX = -sin^-1(-R[2,1]) = sin^-1(R[2,1]).
  if(mCos(mAsin(mat[MatrixF::idx(2,1)])) != 0.f) {
     // Negative angle can be accomplished by changing
     // the sign of opposite side for atan, which geometrically
     // reflects the rotation across the rotation axis.
     // Text:   thetaZ = tan-1(R[1,0], R[1,1])
     // Torque: thetaZ = tan-1(-R[0,1]), R[1,1])
     Z = mAtan(-mat[MatrixF::idx(0,1)], mat[MatrixF::idx(1,1)]);
  } else {
     // There is one degree of freedom because cos(thetaX) == 0,
     // thetaX = (+/-)pi/2 or +/-90 degrees (gimbal lock, Roll and Yaw
     // are interchangeable).  So either thetaY or thetaZ can be set
     // to a value (usually 0 for pure Roll or pure Yaw) and the other
     // rotation derived (i.e if Roll = 0, the rotation is factored as
     // pure Yaw, or vice versa if Yaw = 0).  extractEuler picks
     // thetaY = 0 and derives thetaZ.  Go with that.
     // Text:   thetaZ = tan-1(-R[0,1], R[0,0])
     // Torque: thetaZ = tan-1(R[1,0]), R[0,0])
     Z = mAtan(mat[MatrixF::idx(1,0)], mat[MatrixF::idx(0,0)]);
  }
  return Z;
}

This could be used as:

void TriggerChildClass::setTransform(const MatrixF& mat) {
  Point3F pos;
  MatrixF newMatrix;
  // Get the position from the matrix.
  mat.getColumn(3, &pos);
  // Set the new transfrom matrix using only the Z
  // rotation from the original matrix.  Must also
  // set pos because MatrixF::set(EulerF) sets pos
  // to (0,0,0).
  newMatrix.set(EulerF(0, 0, extractZ(mat), pos);
  // Pass new matrix to the parent class' method.
  Parent::setTransform(newMatrix);
  return;
}

I haven't tested this code. But it should take any arbitrary rotation applied to an object, factor out the rotation about the Z axis (Yaw), and throw away any rotation in the X or Y axes.

Non Sequitur: I've noticed that Eberly's 3D Game Engine Design often presents less optimal algorithms for its computations. In this case the sign of sin(thetaX) does not affect the solution when thetaY = 0, because sin(thetaX) does not appear in R[0,0] or R[0,1]. It does appear in R[2,1] = sin(thetaX)cos(thetaZ), so the algorithm would have needed to be broken into two cases for sin(thetaX) == 1 and sin(thetaX) = -1 if atan used R[2,1] instead of R[0,0]. In fact in the factoring of RyRxRz Eberly chooses thetaZ = 0 (which is why the optimization is missed), so I performed the factoring myself on paper to verify the extractEuler function.

Just a word of caution for anyone using Eberly's algorithms or code. Other than not including optimizations (which I admit at times make the algorithms or code harder to follow) it is an excellent book.
#3
03/12/2003 (10:11 pm)
Thanks a lot, guys!
I'm really appreciate your help

Best regards

Ania
#4
03/13/2003 (5:21 am)
Hi again,

How can I get world coordinates of each vertex describing trigger box.

Greetings

Ania
#5
03/13/2003 (8:40 am)
That information is kept in the Polyhedron struct in the Trigger class' variable mTriggerPolyhedron. Look in polyhedron.h. If you change the trigger polyhedron, you should make the same calls as are made at the end of Trigger::unpackUpdate.
#6
03/16/2003 (3:01 am)
Hi again,

Thanks for advise, finally I have what I needed.
There is one thing which still puzzle me deeply:

MatrixF mat( true );

When I apply scale in that way - everything works fine:
mat.mul( mObjToWorld );
somePoint *= mObjScale;
mat.mulP( somePoint );

but in this situation it generates strange values:

mat.scale( mObjScale );
mat.mul( mObjToWorld );
mat.mulP( somePoint );

Maybe my question looks trivial, but I thought both result should be the same...

Greetings

Ania
#7
03/16/2003 (10:56 am)
Isn't the second case scaling the transformed point, rather than transforming the scaled point?

That is, the middle line in the second example is transposing the scaling and transform matrices.