Sign in to follow this  
MrLloyd1981

Quaternions - yayyyy! They don't work - Booooo!

Recommended Posts

I'm trying my hand at a garden variety skeletal animation system - the type that's like a tree with a 'root' where the transformations for each 'joint' are concatenated as you traverse it. I've been using euler angles inserted into a matrix for the rotations, but I'm feeling a bit ambitious and want to avoid Gimbal Lock so I'm dipping into Quaternions. However they don't seem to work at all!

I'll post some code below - I'll try and keep it all very brief and less painful for you reading it than me actually struggling with this stuff!

A couple of quick points - I'm using a matrix system where the translation is contained in the last column, so vectors have to be in column format and 'post-multipled' e.g M x V

EulerAngles are just a simple struct with x,y,z rotation values.

Vector3 is a pretty standard vector class.

Here is my Quaternion class
[code]//Quaternion.h - interface for a quaternion class that has the imaginary vector 'axis' and an angle theta

#ifndef _QUATERNION_H
#define _QUATERNION_H

#include "core.h"
#include "Vector3.h"
#include "EulerAngles.h"

class Quaternion
{
public:
Quaternion(Vector3 v = Vector3(0.0f,0.0f,0.0f),float t = 1.0f):
theta(t) {Normalise();}
~Quaternion(){};

//normalise, called on construction so that quaternion is unit length
void Normalise();

//accessors - first three components are the vector,
//last one is the angle
const float& operator[](int index)const;
float& operator[](int index);

//Accessors
const Vector3& GetVector()const {return axis;}
const float& GetTheta()const {return theta;}

//compound multiplication for quaternions
const Quaternion& operator*=(const Quaternion& rhsQuat);

//simple multiplication operator
Quaternion operator*(const Quaternion& rhsQuat);

//static functions to get certain types of quaternion or a matrix
static Quaternion GetQuaternionFromEuler(EulerAngles& euler);
static Matrix4 GetMatrixFromQuaternion(const Quaternion& quat);
private:
//simple private func used by the class itself to get length/magnitude
float Length()const;
Vector3 axis;
float theta;
};

#endif[/code]


[code]//Quaternion.cpp - implementation of the quaternion class
#include "Quaternion.h"

//normalise
void Quaternion::Normalise()
{
axis /= Length();
theta /=Length();
}

//[] overload to access diff elements
const float& Quaternion::operator[](int index)const
{
assert(index >= 0 && index <4);

if(index >= 0 && index<3)
{
return axis[index];
}
else
{
return theta;
}

}

float& Quaternion::operator[](int index)
{
assert(index >= 0 && index <4);
if(index >= 0 && index<3)
{
return axis[index];
}
else
{
return theta;
}
}

//compound multiplication operator
const Quaternion& Quaternion::operator*=(const Quaternion& rhsQuat)
{
float angle = (theta * rhsQuat.GetTheta()) - (axis * rhsQuat.GetVector());
Vector3 theAxis = (theta * rhsQuat.GetVector()) + (rhsQuat.GetTheta() * axis) + (theAxis.CrossProduct(theAxis));
theta = angle;
axis = theAxis;
return (*this);
}

Quaternion Quaternion::operator*(const Quaternion& rhsQuat)
{
float angle = (theta * rhsQuat.GetTheta()) - (axis * rhsQuat.GetVector());
Vector3 theAxis = (theta * rhsQuat.GetVector()) + (rhsQuat.GetTheta() * axis) + (theAxis.CrossProduct(theAxis));
return Quaternion(theAxis,angle);
}


Quaternion Quaternion::GetQuaternionFromEuler(EulerAngles& euler)
{
float cos_x = cos(euler.x * 0.5);
float cos_y = cos(euler.y * 0.5);
float cos_z = cos(euler.z * 0.5);
float sin_x = sin(euler.x * 0.5);
float sin_y = sin(euler.y * 0.5);
float sin_z = sin(euler.z * 0.5);

float angle = cos_x*cos_y*cos_z + sin_x*sin_y*sin_z;
float vecX = cos_z*cos_y*sin_x - sin_z*sin_y*cos_x;
float vecY = cos_z*sin_y*cos_x + sin_z*cos_y*sin_x;
float vecZ = sin_z*cos_y*cos_x - cos_z*sin_y*sin_x;

return Quaternion(Vector3(vecX,vecY,vecZ),angle);
}


Matrix4 Quaternion::GetMatrixFromQuaternion(const Quaternion& quart)
{
//remember - indices 0-2 are vector elems, index 3 is the angle
Matrix4 tempMatrix;

tempMatrix(0,0) = 1.0f - (2.0f * (quart[1] * quart[1])) + (2.0f * (quart[2] * quart[2])) ;
tempMatrix(1,0) = (2.0f * (quart[0] * quart[1])) - (2.0f * (quart[2] * quart[3]));
tempMatrix(2,0) = (2.0f * (quart[0] * quart[2])) + (2.0f * (quart[1] * quart[3]));

tempMatrix(0,1) = (2.0f * (quart[0] * quart[1])) + (2.0f * (quart[2] * quart[2]));
tempMatrix(1,1) = 1.0f - (2.0f * (quart[0] * quart[0])) + (2.0f * (quart[2] * quart[2]));
tempMatrix(2,1) = (2.0f * (quart[1] * quart[2])) - (2.0f * (quart[0] * quart[3]));

tempMatrix(0,2) = (2.0f * (quart[0] * quart[2])) - (2.0f * (quart[1] * quart[3]));
tempMatrix(1,2) = (2.0f * (quart[1] * quart[2])) + (2.0f * (quart[0] * quart[3]));;
tempMatrix(2,2) = 1.0f - (2.0f * (quart[0] * quart[0])) + (2.0f * (quart[1] * quart[1]));

return tempMatrix;
}

float Quaternion::Length()const
{
return (axis[0] * axis[0]) + (axis[1] * axis[1]) + (axis[2] * axis[2]) + (theta*theta);
}[/code]



Now to introduce where the 'magic' happens. The following function generates the transformation matrix for a particular joint using the parent matrix and its own local stored transformation.
The "DOF"s are "Degrees of freedom" which here represent rotation values around the x,y,z axes respectively, this only has 3 as it is a ball and socket style joint. If the commented line is used instead the rotation works as desired. However, of course, this is prone to Gimbal lock


[code]Matrix4 BallJoint::GetMatrix(Matrix4& parentMatrix)
{
Matrix4 translation = Matrix4::GetTranslationMatrix(offset);

EulerAngles a;
a.x = dofs[0].GetValue();
a.y = dofs[1].GetValue();
a.z = dofs[2].GetValue();

Quaternion quat = Quaternion::GetQuaternionFromEuler(a);
Matrix4 rotation = Quaternion::GetMatrixFromQuaternion(quat);
//Matrix4 rotation = Matrix4::GetEulerTransform(a);

translation = rotation * translation * parentMatrix;


return translation;
}
[/code]


If anyone can help that would be very cool!

Share this post


Link to post
Share on other sites
I only skimmed the code, but I'll go ahead and point out that this:

[code]Quaternion quat = Quaternion::GetQuaternionFromEuler(a);
Matrix4 rotation = Quaternion::GetMatrixFromQuaternion(quat);[/code]
And this:

[code]Matrix4 rotation = Matrix4::GetEulerTransform(a);[/code]
Will produce equivalent results (assuming the functions in question are implemented as one would expect).

In other words, using quaternions won't protect you from gimbal lock here. (Keep in mind that quaternions have no magical properties with respect to gimbal lock; in fact, their behavior is identical to that of matrices as far as gimbal lock is concerned.)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this