Trouble using quaternions to orient models

This topic is 2471 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

I'm developing a game for OpenGL ES 2.0 and using the PowerVR SDK which comes with Vector, Matrix and Quaternion functionality.

I have a scene hierarchy similar to Ogre that is using Quaternions for Scene Node orientations.

I am trying to orient a model to face a direction in world space. The model by default is oriented to face the Z axis.

This is what I'm doing each frame:

PVRTQUATERNION q = getRotationTo(VEC3_UNIT_Z, direction); //q.w = -q.w; // Doesn't work unless I do this, seems to be inverted m_SceneNode->setOrientation(q); 

getRotationTo() code (from Game Programming Gems)
PVRTQUATERNION getRotationTo(const PVRTVec3& original, const PVRTVec3& dest, const PVRTVec3& fallbackAxis = VEC3_ZERO) { PVRTQUATERNION q; PVRTVec3 v0 = original; PVRTVec3 v1 = dest; v0.normalize(); v1.normalize(); float dot = v0.dot(v1); if (dot >= 1.0f) return Q_IDENTITY; if (dot < 1e-6f - 1.0f) { if (!(fallbackAxis == VEC3_ZERO)) { // Rotate 180 degrees about the fallback axis PVRTMatrixQuaternionRotationAxis(q, fallbackAxis, 3.14159265f); } else { // Generate an axis PVRTVec3 axis = VEC3_UNIT_X.cross(v0); if (axis.lenSqr() == 0.0f) axis = VEC3_UNIT_Y.cross(v0); // pick another axis if colinear axis.normalize(); PVRTMatrixQuaternionRotationAxis(q, axis, 3.14159265f); } } else { float s = sqrtf((1+dot)*2); float invs = 1 / s; PVRTVec3 c = v0.cross(v1); q.x = c.x * invs; q.y = c.y * invs; q.z = c.z * invs; q.w = s * 0.5f; PVRTMatrixQuaternionNormalize(q); } return q; }

Inside the Scene Node class the quaternion gets converted to a matrix and then combined into a 4x4 scale, rotation and translation matrix.

My problem is that unless I use the line q.w = -q.w; after the getRotationTo function then the orientation seems inverted and incorrect. I can't seem to figure out what the issue is and would appreciate some insight from a math guru

Share on other sites
It turns out the issue was in the PowerVR library, but I'm not quite sure if I am doing something wrong that causes it. The Quaternion to Matrix function inside the library has the following code:

 void PVRTMatrixRotationQuaternionF(PVRTMATRIXf &mOut, const PVRTQUATERNIONf &quat) { const PVRTQUATERNIONf *pQ; #if defined(BUILD_DX9) || defined(BUILD_D3DM) || defined(BUILD_DX10) PVRTQUATERNIONf qInv; qInv.x = -quat.x; qInv.y = -quat.y; qInv.z = -quat.z; qInv.w = quat.w; pQ = &qInv; #else pQ = &quat; // Uses this #endif // Uses pQ to build matrix mOut here } 

I had to change to this for it to work as expected:

 void PVRTMatrixRotationQuaternionF(PVRTMATRIXf &mOut, const PVRTQUATERNIONf &quat) { const PVRTQUATERNIONf *pQ; #if defined(BUILD_DX9) || defined(BUILD_D3DM) || defined(BUILD_DX10) PVRTQUATERNIONf qInv; qInv.x = -quat.x; qInv.y = -quat.y; qInv.z = -quat.z; qInv.w = quat.w; pQ = &qInv; #else // Uses this PVRTQUATERNIONf qInv; qInv.x = -quat.x; qInv.y = -quat.y; qInv.z = -quat.z; qInv.w = quat.w; pQ = &qInv; #endif // Uses pQ to build matrix mOut here } 

Once this was done, I can convert a quaternion to a matrix and back again and it is correct.

Share on other sites
You could be having left handed vs right handed issues.
Also try doing getRotationTo(direction, VEC3_UNIT_Z); (parameters inverted)
Having that said:

This is what I'm doing each frame:

PVRTQUATERNION q = getRotationTo(VEC3_UNIT_Z, direction); //q.w = -q.w; // Doesn't work unless I do this, seems to be inverted m_SceneNode->setOrientation(q); 

That's a terrible way of setting an orientation. There are infinite solutions that satisfy a quaternion that moves from +Z to "direction"; because there are infinite rolls (i.e. upside down, half way upside down, etc).
A library function will return one of those results, usually the one you want; but not always.

The easiest way to set quaternion to face towards a particular direction is to use a 3x3 matrix and set each row to the X, Y & Z axis respectively. This way you have full control of the "Up" and "Right" vectors and avoid any surprises. The Z axis (last row) will be the direction, and the "Y" (2nd row) would be the Up vectors.

Depending on the game, the "Up" vector would be parallel to the normal of the surface you're on (i.e. an RTS game), the cross product of the "X" axis and the direction (i.e. an action game), or an arbitrary one (i.e. a flight simulator)
The "Right" vector can be obtained by performing the cross product between the direction and the Up Vector.

Then search google on how to convert a 3x3 matrix into a quaternion.
There are of course many other ways to do the same, but I think this is the easiest and most robust.

Share on other sites
I also though that it might have something to do with a LH vs RH problem but the matrix -> quaternion -> matrix are well defined and don't know about handedness as I understand it.

Funnily enough I ended up implementing them as you suggested. The quaternion issue was still a problem though because I use quaternion driven scene nodes, so even after building the matrix I would have to convert to a quaternion, which then gets converted back and would be wrong.

The code I ended up using to do orientation:

 // Create the right vector from facing direction and universal up direction PVRTVec3 right = direction.cross(VEC3_UNIT_Y); right.normalize(); // Create the local up vector PVRTVec3 up = direction.cross(right); up.normalize(); direction.normalize(); PVRTMat3 rotation(right.x, right.y, right.z, up.x, up.y, up.z, direction.x, direction.y, direction.z); PVRTQUATERNION q; QuaternionFromRotationMatrix(rotation, q); m_ProjectileNode->setOrientation(q);

Works like a charm

Share on other sites
Just as a tip; that code won't work if direction is parallel to the Y axis (either +Y or -Y) or direction is 0.
Unless you're 100% sure "direction" won't fall in any of those 3 cases; you may want to provide a fallback mechanism; or avoid setting the orientation (use the last valid one).

If direction is 0, the solution is pretty obvious, use the last valid orientation since there's no valid direction suplied.
If direction is parallel to the Y axis, either set a fixed orientation (i.e. a premade one looking up) or use a fallback vector: instead of doing right = direction.cross(VEC3_UNIT_Y); perform right = direction.cross(VEC3_UNIT_Z); (only in that case, of course).

Share on other sites
BTW, since you're working in handhelds, I normally don't say this; but you may want to prefer assuming direction is valid and perform a debug check (i.e. an assert) in case you supply an invalid direction vector.

The reason behind that is that you may notice your code contains only one branch (inside QuaternionFromRotationMatrix a branch is needed, unless fsel is used in platforms where supported and optimized).
Branches can be very expensive in CPUs without branch predictors (Google LHS - Load Hit Store; and read this for more information) and even in machines with predictors.

Furthermore calculating an orientation is something you're probably end up doing a lot, per frame.

1. 1
2. 2
3. 3
Rutin
19
4. 4
khawk
14
5. 5
frob
12

• 9
• 11
• 11
• 23
• 12
• Forum Statistics

• Total Topics
633659
• Total Posts
3013210
×