Jump to content
  • Advertisement
Sign in to follow this  
reapz

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.

If you intended to correct an error in the post then please contact us.

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 smile.png

Share this post


Link to post
Share on other sites
Advertisement
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 this post


Link to post
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 this post


Link to post
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 this post


Link to post
Share on other sites
I'm glad it worked :)
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 this post


Link to post
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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!