Quaternions are commonly used to represent orientations and rotations in 3d games. They are more efficient than using 3d matrices in number of operations, storage and quaternions also avoid gimbal lock. You can download the entire math package
here or visit my
downloadable code page.
#pragma once
#include "matrix4.h"
#include "assert.h"
struct quaternion
{
union {
struct {
float s;
vector3f v;
};
struct { float elem[4]; };
};
quaternion() {}
quaternion(float real, float x, float y, float z): s(real), v(x,y,z) {}
quaternion(float real, const vector3f &i): s(real), v(i) {}
quaternion(float theta_z, float theta_y, float theta_x)
{
float cos_z_2 = cosf(0.5*theta_z);
float cos_y_2 = cosf(0.5*theta_y);
float cos_x_2 = cosf(0.5*theta_x);
float sin_z_2 = sinf(0.5*theta_z);
float sin_y_2 = sinf(0.5*theta_y);
float sin_x_2 = sinf(0.5*theta_x);
s = cos_z_2*cos_y_2*cos_x_2 + sin_z_2*sin_y_2*sin_x_2;
v.x = cos_z_2*cos_y_2*sin_x_2 - sin_z_2*sin_y_2*cos_x_2;
v.y = cos_z_2*sin_y_2*cos_x_2 + sin_z_2*cos_y_2*sin_x_2;
v.z = sin_z_2*cos_y_2*cos_x_2 - cos_z_2*sin_y_2*sin_x_2;
}
quaternion(const vector3f &angles)
{
float cos_z_2 = cosf(0.5*angles.z);
float cos_y_2 = cosf(0.5*angles.y);
float cos_x_2 = cosf(0.5*angles.x);
float sin_z_2 = sinf(0.5*angles.z);
float sin_y_2 = sinf(0.5*angles.y);
float sin_x_2 = sinf(0.5*angles.x);
s = cos_z_2*cos_y_2*cos_x_2 + sin_z_2*sin_y_2*sin_x_2;
v.x = cos_z_2*cos_y_2*sin_x_2 - sin_z_2*sin_y_2*cos_x_2;
v.y = cos_z_2*sin_y_2*cos_x_2 + sin_z_2*cos_y_2*sin_x_2;
v.z = sin_z_2*cos_y_2*cos_x_2 - cos_z_2*sin_y_2*sin_x_2;
}
quaternion &operator =(const quaternion &q)
{ s= q.s; v= q.v; return *this; }
const quaternion operator +(const quaternion &q) const
{ return quaternion(s+q.s, v+q.v); }
const quaternion operator -(const quaternion &q) const
{ return quaternion(s-q.s, v-q.v); }
const quaternion operator *(const quaternion &q) const
{ return quaternion(s*q.s - v*q.v,
v.y*q.v.z - v.z*q.v.y + s*q.v.x + v.x*q.s,
v.z*q.v.x - v.x*q.v.z + s*q.v.y + v.y*q.s,
v.x*q.v.y - v.y*q.v.x + s*q.v.z + v.z*q.s);
}
const quaternion operator /(const quaternion &q) const
{
quaternion p(q);
p.invert();
return *this * p;
}
const quaternion operator *(float scale) const
{ return quaternion(s*scale,v*scale); }
const quaternion operator /(float scale) const
{ return quaternion(s/scale,v/scale); }
const quaternion operator -() const
{ return quaternion(-s, -v); }
const quaternion &operator +=(const quaternion &q)
{ v+=q.v; s+=q.s; return *this; }
const quaternion &operator -=(const quaternion &q)
{ v-=q.v; s-=q.s; return *this; }
const quaternion &operator *=(const quaternion &q)
{
float x= v.x, y= v.y, z= v.z, sn= s*q.s - v*q.v;
v.x= y*q.v.z - z*q.v.y + s*q.v.x + x*q.s;
v.y= z*q.v.x - x*q.v.z + s*q.v.y + y*q.s;
v.z= x*q.v.y - y*q.v.x + s*q.v.z + z*q.s;
s= sn;
return *this;
}
const quaternion &operator *= (float scale)
{ v*=scale; s*=scale; return *this; }
const quaternion &operator /= (float scale)
{ v/=scale; s/=scale; return *this; }
float length() const
{ return (float)sqrt(s*s + v*v); }
float length_squared() const
{ return (float)(s*s + v*v); }
void normalize()
{ *this/=length(); }
quaternion normalized() const
{ return *this/length(); }
void conjugate()
{ v=-v; }
void invert()
{ conjugate(); *this/=length_squared(); }
quaternion log() const
{
float a = (float)acos(s);
float sina = (float)sin(a);
quaternion ret;
ret.s = 0;
if (sina > 0)
{
ret.v.x = a*v.x/sina;
ret.v.y = a*v.y/sina;
ret.v.z = a*v.z/sina;
} else {
ret.v.x= ret.v.y= ret.v.z= 0;
}
return ret;
}
quaternion exp() const
{
float a = (float)v.length();
float sina = (float)sin(a);
float cosa = (float)cos(a);
quaternion ret;
ret.s = cosa;
if (a > 0)
{
ret.v.x = sina * v.x / a;
ret.v.y = sina * v.y / a;
ret.v.z = sina * v.z / a;
} else {
ret.v.x = ret.v.y = ret.v.z = 0;
}
return ret;
}
operator matrix4() const
{
return matrix4(s, -v.x, -v.y,-v.z,
v.x, s, -v.z, v.y,
v.y, v.z, s,-v.x,
v.z,-v.y, v.x, s);
}
operator matrix3() const
{
Assert(length() > 0.9999 && length() < 1.0001, "quaternion is not normalized");
return matrix3(1-2*(v.y*v.y+v.z*v.z), 2*(v.x*v.y-s*v.z), 2*(v.x*v.z+s*v.y),
2*(v.x*v.y+s*v.z), 1-2*(v.x*v.x+v.z*v.z), 2*(v.y*v.z-s*v.x),
2*(v.x*v.z-s*v.y), 2*(v.y*v.z+s*v.x), 1-2*(v.x*v.x+v.y*v.y));
}
static inline float dot(const quaternion &q1, const quaternion &q2)
{ return q1.v*q2.v + q1.s*q2.s; }
static quaternion lerp(const quaternion &q1, const quaternion &q2, float t)
{ return (q1*(1-t) + q2*t).normalized(); }
static quaternion slerp(const quaternion &q1, const quaternion &q2, float t)
{
quaternion q3;
float dot = quaternion::dot(q1, q2);
if (dot < 0)
{
dot = -dot;
q3 = -q2;
} else q3 = q2;
if (dot < 0.95f)
{
float angle = acosf(dot);
return (q1*sinf(angle*(1-t)) + q3*sinf(angle*t))/sinf(angle);
} else
return lerp(q1,q3,t);
}
static quaternion slerpNoInvert(const quaternion &q1, const quaternion &q2, float t)
{
float dot = quaternion::dot(q1, q2);
if (dot > -0.95f && dot < 0.95f)
{
float angle = acosf(dot);
return (q1*sinf(angle*(1-t)) + q2*sinf(angle*t))/sinf(angle);
} else
return lerp(q1,q2,t);
}
static quaternion squad(const quaternion &q1,const quaternion &q2,const quaternion &a,const quaternion &b,float t)
{
quaternion c= slerpNoInvert(q1,q2,t),
d= slerpNoInvert(a,b,t);
return slerpNoInvert(c,d,2*t*(1-t));
}
static quaternion bezier(const quaternion &q1,const quaternion &q2,const quaternion &a,const quaternion &b,float t)
{
quaternion q11= slerpNoInvert(q1,a,t),
q12= slerpNoInvert(a,b,t),
q13= slerpNoInvert(b,q2,t);
return slerpNoInvert(slerpNoInvert(q11,q12,t), slerpNoInvert(q12,q13,t), t);
}
static quaternion spline(const quaternion &qnm1,const quaternion &qn,const quaternion &qnp1)
{
quaternion qni(qn.s, -qn.v);
return qn * (( (qni*qnm1).log()+(qni*qnp1).log() )/-4).exp();
}
static inline quaternion from_axis_angle(const vector3f &axis, float angle)
{ return quaternion(cosf(angle/2), axis*sinf(angle/2)); }
void to_axis_angle(vector3f &axis, float &angle) const
{
angle = acosf(s);
float sinf_theta_inv = 1.0/sinf(angle);
axis.x = v.x*sinf_theta_inv;
axis.y = v.y*sinf_theta_inv;
axis.z = v.z*sinf_theta_inv;
angle*=2;
}
vector3f rotate(const vector3f &v)
{
quaternion V(0, v);
quaternion conjugate(*this);
conjugate.conjugate();
return (*this * V * conjugate).v;
}
vector3f euler_angles(bool homogenous=true) const
{
float sqw = s*s;
float sqx = v.x*v.x;
float sqy = v.y*v.y;
float sqz = v.z*v.z;
vector3f euler;
if (homogenous) {
euler.x = atan2f(2.f * (v.x*v.y + v.z*s), sqx - sqy - sqz + sqw);
euler.y = asinf(-2.f * (v.x*v.z - v.y*s));
euler.z = atan2f(2.f * (v.y*v.z + v.x*s), -sqx - sqy + sqz + sqw);
} else {
euler.x = atan2f(2.f * (v.z*v.y + v.x*s), 1 - 2*(sqx + sqy));
euler.y = asinf(-2.f * (v.x*v.z - v.y*s));
euler.z = atan2f(2.f * (v.x*v.y + v.z*s), 1 - 2*(sqy + sqz));
}
return euler;
}
};
I think there are some problem in computing euler angle from quaternion. I changed it to code below and it worked. All came from wikipedia page:
http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
euler.x = atan2f(2.f * (v.z*v.y + v.x*s), 1 - 2* (sqx + sqy));
euler.y = asinf(-2.f * (v.x*v.z - v.y*s));
euler.z = atan2f(2.f * (v.x*v.y + v.z*s), 1 - 2* (sqy + sqz));
look at the url you gave !
your way is the inhomogeneous way, Will used the homogeneous one.
#include "matrix3.h"
but the file is called Matrix3.h . More difficult to fix are errors like
vector3.h: In member function 'void vector3<T>::operator()(T, T, T)':
vector3.h:53: error: 'x' was not declared in this scope
or
utility.h:137: error: ISO C++ forbids declaration of 'interp_lin' with no type