15
\$\begingroup\$

I have a Vector3 which has an euler angle for each axis.

Usually, when I want to create a rotation matrix I will use functions such as D3DXMatrixRotationX passing the respective angle from my rotation vector above and multiply the matrices (ZXY) to create the overall rotation matrix which is used to form the complete object transformation matrix.

However, this method will produce a set of rotations in object space. That is, passing a vector of (90, 0, 90) into my method will create a rotation in world space effectively of (90, 90, 0).

Is there a way to always ensure each component of my rotation vector results in a rotation around the respective world space aligned axes?

EDIT:

This is an animation of what is currently happening - I want a way to rotate around the blue axes, not the red.

Euler Angles

EDIT 2:

Just to note I am not looking for a solution involving Euler angles, but simply a way in which I can represent a transformation of multiple rotations around the world axes.

\$\endgroup\$
  • \$\begingroup\$ What's wrong with just calling the differnet functions three times and filtering out the parts of the vectors you don't want (by setting them to 0 before calling the function)? Otherwise I'm not sure what you're trying to achieve. \$\endgroup\$ – TravisG Dec 11 '13 at 21:04
  • \$\begingroup\$ Filtering what out? I do call the 3 separate functions and then multiply them to create the transformation matrix. This archieves a local rotation though. \$\endgroup\$ – Syntac_ Dec 11 '13 at 21:43
  • \$\begingroup\$ Do you want Euler angles, or rotation about world axes? Note that by the definition of Euler angles (eg en.wikipedia.org/wiki/Euler_angles ), only the alpha angle is strictly about a world axis. The other two angles are relative to tilted axes which do not necessarily coincide with the world axes. \$\endgroup\$ – DMGregory Dec 11 '13 at 23:04
  • 1
    \$\begingroup\$ Using Euler angles you are multiplying all the three rotation matrices before applying then on the vertex. If M, N, O are the rotation matrices, the result operation is MNOv. What I have proposed is to apply each matrix separately: v1= Ov0, then v2 = Nv1 and finally v3= Mv2. This way each vi will be in world coordinates and you just need to use a rotation matrix for the current axis in world coordinates too. \$\endgroup\$ – dsilva.vinicius Dec 12 '13 at 2:02
  • 3
    \$\begingroup\$ @dsilva.vinicius Your separated transformations are exactly the same as the combined one, or to put it another way: MNOv == M*(N*(Ov)) \$\endgroup\$ – GuyRT Dec 12 '13 at 9:55
1
\$\begingroup\$

Based on you comments, it seems that you're storing the orientation of the object as a set of Euler angles, and in/decrementing the angles when the player rotates the object. That is, you have something like this pseudocode:

// in player input handling:
if (axis == AXIS_X) object.angleX += dir;
else if (axis == AXIS_Y) object.angleY += dir;
else if (axis == AXIS_Z) object.angleZ += dir;

// in physics update and/or draw code:
matrix = eulerAnglesToMatrix(object.angleX, object.angleY, object.angleZ);

As Charles Beattie notes, because rotations don't commute, this won't work as expected unless the player rotates the object in the same order in which the eulerAnglesToMatrix() applies the rotations.

In particular, consider the following sequence of rotations:

  1. rotate object by x degrees around the X axis;
  2. rotate object by y degrees around the Y axis;
  3. rotate object by −x degrees around the X axis;
  4. rotate object by −y degrees around the Y axis.

In the naïve Euler angle representation, as implemented in the pseudocode above, these rotations will cancel out and the object will return to its original orientation. In the real world, this does not happen — if you don't believe me, grab a six-sided die or a Rubik's cube, let x = y = 90°, and try it out yourself!

The solution, as you note in your own answer, is to store the orientation of the object as a rotation matrix (or a quaternion), and update that matrix based on user input. That is, instead of the pseudocode above, you'd do something like this:

// in player input handling:
if (axis == AXIS_X) object.orientation *= eulerAnglesToMatrix(dir, 0, 0);
else if (axis == AXIS_Y) object.orientation *= eulerAnglesToMatrix(0, dir, 0);
else if (axis == AXIS_Z) object.orientation *= eulerAnglesToMatrix(0, 0, dir);

// in physics update and/or draw code:
matrix = object.orientation;  // already in matrix form!

(Technically, since any rotation matrix or quaternion can be represented as a set of Euler angles, it is possible to use them to store the orientation of the object. But the physically correct rule for combining two sequential rotations, each represented as Euler angles, into a single rotation is rather complicated, and essentially amounts to converting the rotations into matrices / quaternions, multiplying them, and then converting the result back into Euler angles.)

\$\endgroup\$
  • \$\begingroup\$ Yes you are correct this was the solution. I feel this is slightly better than concept3d's answer as he gives the impression that a quaternion is needed but that isn't true. As long as I stored the current rotation as a matrix and not the three Euler angles it was fine. \$\endgroup\$ – Syntac_ Nov 10 '15 at 9:57
14
+500
\$\begingroup\$

The problem with rotations, is that, most people think of it in terms of Euler angles, since they are easy to understand.

Yet most people forget the point that Euler angles are three sequential angles. Meaning that rotation around the first axis, will make next rotation be relative to the first original rotation, hence you cannot independently rotate a vector around each of the 3 axis using Euler angles.

This directly translates to matrices when you multiply two matrices, you can think of this multiplication as transforming one matrix to the space of the other matrix.

This is meant to happen with any 3 sequential rotations even when using quaternions.

enter image description here

I want to stress the fact that quaternions are not a solution for gimble lock. Actually gimble lock will always happen if you represented Euler angles using quaternions. The problem is not the representation, the problem is the 3 sequential steps.

The Solution?

The solution for rotating a vector around 3 axis independently is to combine into a single axis and a single angle, this way you can get rid of the step where you have to do a sequential multiplication. This will effectively translate into:

My rotation matrix represents the outcome of the rotation around X and Y and Z.

rather than the Euler interpretation of

My rotation matrix represents the rotation around X then Y then Z.

To clarify this I will quote from wikipedia Euler's rotation theorem:

According to Euler's rotation theorem, any rotation or sequence of rotations of a rigid body or Coordinate system about a fixed point is equivalent to a single rotation by a given angle θ about a fixed axis (called Euler axis) that runs through the fixed point. The Euler axis is typically represented by a unit vector u→. Therefore, any rotation in three dimensions can be represented as a combination of a vector u→ and a scalar θ. Quaternions give a simple way to encode this axis–angle representation in four numbers, and to apply the corresponding rotation to a position vector representing a point relative to the origin in R3.

Notice that multiplying 3 matrices will always represent 3 sequential rotations.

Now inorder to combine rotations around 3 axis, you need to get a single axis and single angles that represents the rotation around X,Y,Z. In other words you need to use an Axis/Angle or quaternion representation to get rid of the sequential rotations.

This is usually done, by starting with an initial orientation (orientation can be thought of as a an axis angle), usually represented as a quaternion or an axis angle, and then modifying that orintation to represent your destination orientation. For example you start with the identity quaterion and then rotate by the difference to reach the destination orientation. This way you don't lose any degree of freedom.

\$\endgroup\$
  • \$\begingroup\$ Marked as answer as it seems insightful. \$\endgroup\$ – Syntac_ Dec 13 '13 at 19:34
  • \$\begingroup\$ I'm having some trouble figuring out what you're trying to say with this answer. Is it simply "don't store the orientation of an object as Euler angles"? And if so, why not just say so? \$\endgroup\$ – Ilmari Karonen Nov 9 '15 at 17:59
  • \$\begingroup\$ @IlmariKaronen It could be more clearly stated, but I think concept3d is promoting the axis-angle representation; see section 1.2.2 of this document for the relationship between axis-angle and quaternions. The axis-angle representation is easier to implement for the above reasons, it doesn't suffer from gimbal-lock, and (for me at least) it's just as easy to understand as Euler angles. \$\endgroup\$ – NauticalMile Jan 30 '17 at 17:00
  • \$\begingroup\$ @concept3d, that's very interesting, and I really like your answer. There is one thing missing for me though, people interact with the computer using a keyboard and a mouse, if we think of the mouse then we are talking about x and y mouse deltas. How to represent these x, y deltas with a single quaternion that we can use to generate the rotation matrix, for instance to change an object orientation? \$\endgroup\$ – gmagno Mar 12 at 0:05
  • \$\begingroup\$ @gmagno the approach is usually to project the mouse movement on the objects or scene and calculate deltas in that space, you do this by casting a ray and calculate intersection. Search for ray casting, project and unproject, I am rough on the details as I didn't work in CG for years now. hope that helps. \$\endgroup\$ – concept3d Mar 12 at 10:33
2
\$\begingroup\$

Switching a combination of rotations from object space to world space is trivial: you just have to reverse the order in which rotations are applied.

In your case, instead of multiplying matrices Z × X × Y, you just need to compute Y × X × Z.

A rationale for this can be found on Wikipedia: conversion between intrinsic and extrinsic rotations.

\$\endgroup\$
  • \$\begingroup\$ If that was true then the following statement from your source wouldn't be true, because the rotations would be different: "Any extrinsic rotation is equivalent to an intrinsic rotation by the same angles but with inverted order of elemental rotations, and vice-versa." \$\endgroup\$ – Syntac_ Dec 14 '13 at 13:20
  • 1
    \$\begingroup\$ I see no contradiction here. Both my answer and that statement are true. And yes, performing rotations in object space and in world space yield different rotations; that’s precisely the point, isn’t it? \$\endgroup\$ – sam hocevar Dec 14 '13 at 13:54
  • \$\begingroup\$ That statement says that changing the order will always result in the same rotation. If one order is producing the wrong rotation the other order will also, meaning it is not a solution. \$\endgroup\$ – Syntac_ Dec 14 '13 at 13:57
  • 1
    \$\begingroup\$ You are misreading. Changing the order does not result in the same rotation. Changing the order and switching from intrinsic rotations to extrinsic rotations results in the same rotation. \$\endgroup\$ – sam hocevar Dec 14 '13 at 14:02
  • 1
    \$\begingroup\$ I don’t think I understand your question. Your GIF shows a rotation of about 50 degrees around Z (object space), then 50 degrees around X (object space), then 45 degrees around Y (object space). This is exactly the same as a rotation of 45 degrees around Y (world space), then 50 degrees around X (world space), then 50 degrees around Z (world space). \$\endgroup\$ – sam hocevar Dec 14 '13 at 14:29
1
\$\begingroup\$

I'll provide my solution as an answer until someone can explain why this works.

Every render I was rebuilding my quaternion using the angles stored in my rotation vector and then applying the quaternion to my final transform.

However in order to keep it around the world axes I had to retain the quaternion across all frames and only rotate objects using a difference in angle, i.e...

// To rotate an angle around X - note this is an additional rotation.
// If currently rotated 90, apply this function with angle of 90, total rotation = 180.
D3DXQUATERNION q;
D3DXQuaternionRotation(&q, D3DXVECTOR3(1.0f, 0.0f, 0.0f), fAngle);
m_qRotation *= q; 

//...

// When rendering rebuild world matrix
D3DXMATRIX mTemp;
D3DXMatrixIdentity(&m_mWorld);

// Scale
D3DXMatrixScaling(&mTemp, m_vScale.x, m_vScale.y, m_vScale.z);
m_mWorld *= mTemp;

// Rotate
D3DXMatrixRotationQuaternion(&mTemp, m_qRotation);
m_mWorld *= mTemp;

// Translation
D3DXMatrixTranslation(&mTemp, m_vPosition.x, m_vPosition.y, m_vPosition.z);
m_mWorld *= mTemp;

(Verbose for readiblity)

I think dsilva.vinicius was trying to get to this point.

\$\endgroup\$
1
\$\begingroup\$

You will need to store the order of the the rotations.

Rotating around x 90 then rotate around z 90 !=
Rotating around z 90 then rotate around x 90.

Store your current rotation matrix and premultiply each rotation as they come.

\$\endgroup\$
0
\$\begingroup\$

In addition to @concept3d answer you can use 3 extrinsic rotation matrices to rotate around axis in world coordinates. Quoting from Wikipedia:

Extrinsic rotations are elemental rotations that occur about the axes of the fixed coordinate system xyz. The XYZ system rotates, while xyz is fixed. Starting with XYZ overlapping xyz, a composition of three extrinsic rotations can be used to reach any target orientation for XYZ. The Euler or Tait Bryan angles (α, β, γ) are the amplitudes of these elemental rotations. For instance, the target orientation can be reached as follows:

The XYZ-system rotates about the z-axis by α. The X-axis is now at angle α with respect to the x-axis.

The XYZ-system rotates again about the x-axis by β. The Z-axis is now at angle β with respect to the z-axis.

The XYZ-system rotates a third time about the z-axis by γ.

Rotation matrices can be used to represent a sequence of extrinsic rotations. For instance,

R = Z(γ) Y(β) X(α)

represents a composition of extrinsic rotations about axes x-y-z, if used to pre-multiply column vectors, while

R = X(α) Y(β) Z(γ)

represents exactly the same composition when used to post-multiply row vectors.

So what you need is to invert the order of the rotations in relation of what you would do using intrinsic (or local space) rotations. @Syntac asked for a zxy rotation, so we should do a yxz extrinsic rotation to achieve the same result. The code is below:

Matrix values explanation here.

// Init things.
D3DXMATRIX *rotationMatrixX = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixY = new D3DXMATRIX();
D3DXMATRIX *rotationMatrixZ = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix0 = new D3DXMATRIX();
D3DXMATRIX *resultRotationMatrix1 = new D3DXMATRIX();

D3DXMatrixRotationX(rotationMatrixX, angleX);
D3DXMatrixRotationY(rotationMatrixY, angleY);
D3DXMatrixRotationZ(rotationMatrixZ, angleZ);

// yx extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix0, rotationMatrixY, rotationMatrixX);
// yxz extrinsic rotation matrix
D3DXMatrixMultiply(resultRotationMatrix1, resultRotationMatrix0, rotationMatrixZ);

D3DXVECTOR4* originalVector = // Original value to be transformed;
D3DXVECTOR4* transformedVector = new D3DXVECTOR4();

// Applying matrix to the vector.
D3DXVec4Transform(transformedVector, originalVector, resultRotationMatrix1);

// Don't forget to clean memory!

This code is didactic, not optimal, since You could reuse several D3DXMATRIX matrices.

\$\endgroup\$
  • 1
    \$\begingroup\$ sorry man this is not correct. matrix/vector multiplication is associative. this is exactly the same as combined matrix multiplication. \$\endgroup\$ – concept3d Dec 13 '13 at 16:57
  • \$\begingroup\$ You are right. I have mix the concepts of extrinsic and intrinsic rotations. \$\endgroup\$ – dsilva.vinicius Dec 14 '13 at 10:12
  • \$\begingroup\$ I'll fix this answer. \$\endgroup\$ – dsilva.vinicius Dec 14 '13 at 10:31
  • \$\begingroup\$ The answer is fixed now. \$\endgroup\$ – dsilva.vinicius Dec 14 '13 at 11:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.