This appendix implements a class called Quaternion
that encapsulates all of the operations
you need to handle quaternions when writing 3D rigid-body
simulations.
The Quaternion
class is defined with a scalar component, n, and vector component, v, where v is the vector, ×
i + y j + z k. The class has two constructors, one of which initializes the
quaternion to 0, and the other of which initializes the elements to those passed to the
constructor:
class Quaternion { public: float n; // number (scalar) part Vector v; // vector part: v.x, v.y, v.z Quaternion(void); Quaternion(float e0, float e1, float e2, float e3); float Magnitude(void); Vector GetVector(void); float GetScalar(void); Quaternion operator+=(Quaternion q); Quaternion operator-=(Quaternion q); Quaternion operator*=(float s); Quaternion operator/=(float s); Quaternion operator~(void) const { return Quaternion( n, -v.x, -v.y, -v.z);} }; // Constructor inline Quaternion::Quaternion(void) { n = 0; v.x = 0; v.y = 0; v.z = 0; } // Constructor inline Quaternion::Quaternion(float e0, float e1, float e2, float e3) { n = e0; v.x = e1; v.y = e2; v.z = e3; }
The method Magnitude
returns
the magnitude of the quaternion according to the following
formula:
|q| = |
This is similar to calculating the magnitude of a vector except that for quaternions you have to take the fourth term, the scalar n, into account.
Here’s the code that calculates the magnitude for our Quaternion
class:
inline float Quaternion::Magnitude(void) { return (float) sqrt(n*n + v.x*v.x + v.y*v.y + v.z*v.z); }
The method GetVector
returns the vector part of the quaternion. This method uses the Vector
class defined in Appendix A:
inline Vector Quaternion::GetVector(void) { return Vector(v.x, v.y, v.z); }
The method GetScalar
returns the scalar part of the quaternion:
inline float Quaternion::GetScalar(void) { return n; }
This operator performs quaternion addition by simply adding the quaternion, q, to the current quaternion on a component-by-component basis.
If q and p are two quaternions, then:
q + p = [nq + np, (xq + xp) i + (yq + yp) j + (zq + zp) k] |
Here, nq + np is the scalar part of the resulting quaternion, while (xq + xp) i + (yq + yp) j + (zq + zp) k is the vector part.
Quaternion addition is both associative and commutative; thus:
q + (p + h) = (q + p) + h |
q + p = p + q |
Here’s the code that adds the quaternion q to our Quaternion
class:
inline Quaternion Quaternion::operator+=(Quaternion q) { n += q.n; v.x += q.v.x; v.y += q.v.y; v.z += q.v.z; return *this; }
This operator performs quaternion subtraction by simply subtracting the quaternion, q, from the current quaternion on a component-by-component basis.
If q and p are two quaternions, then:
q − p = q + (−p) = [nq − np, (xq − xp) i + (yq − yp) j + (zq − zp) k] |
Here, nq − np is the scalar part of the resulting quaternion, while (xq − xp) i + (yq − yp) j + (zq − zp) k is the vector part.
Here’s the code that subtracts the quaternion q from our Quaternion
class:
inline Quaternion Quaternion::operator-=(Quaternion q) { n -= q.n; v.x -= q.v.x; v.y -= q.v.y; v.z -= q.v.z; return *this; }
This operator simply multiplies each component in the quaternion by the scalar s. This operation is similar to scaling a vector, as described in Appendix A:
inline Quaternion Quaternion::operator*=(float s) { n *= s; v.x *= s; v.y *= s; v.z *= s; return *this; }
This operator simply divides each component in the quaternion by the scalar s:
inline Quaternion Quaternion::operator/=(float s) { n /= s; v.x /= s; v.y /= s; v.z /= s; return *this; }
This operator takes the conjugate of the quaternion, ~q, which is simply the negative of the vector part. If q = [n, x i + y j + z k], then ~q = [n, (−x) i + (−y) j + (−z) k].
The conjugate of the product of quaternions is equal to the product of the quaternion conjugates, but in reverse order:
~(qp) = (~p)(~q) |
Here’s the code that computes the conjugate for our Quaternion
class:
Quaternion operator~(void) const { return Quaternion( n, -v.x, -v.y, -v.z);}
The functions and overloaded operators that follow are useful when you are performing
operations with two quaternions, or with a quaternion and a scalar, or a
quaternion and a vector. Here, the quaternions are assumed to be of the
type Quaternion
, and vectors of the
type Vector
, as discussed in Appendix A.
This operator performs quaternion addition by simply adding the quaternion q1
to quaternion q2
on a
component-by-component basis:
inline Quaternion operator+(Quaternion q1, Quaternion q2) { return Quaternion( q1.n + q2.n, q1.v.x + q2.v.x, q1.v.y + q2.v.y, q1.v.z + q2.v.z); }
This operator performs quaternion subtraction by simply subtracting the quaternion q2
from quaternion q1
on a
component-by-component basis:
inline Quaternion operator-(Quaternion q1, Quaternion q2) { return Quaternion( q1.n - q2.n, q1.v.x - q2.v.x, q1.v.y - q2.v.y, q1.v.z - q2.v.z); }
This operator performs quaternion multiplication according to the following formula:
q p = nq np − vq • vp + nq vp + np vq + (vq × vp) |
Here, nqnp − vq • vp is the scalar part of the result, while nq vp + np vq + (vq × vp) is the vector part. Also note that vq and vp are the vector parts of q and p, respectively, • is the vector dot-product operator, and × is the vector cross-product operator.
Quaternion multiplication is associative but not commutative; thus:
q(ph) = (qp)h |
qp ≠ pq |
Here’s the code that multiplies two Quaternion
s, q1
and q2
:
inline Quaternion operator*(Quaternion q1, Quaternion q2) { return Quaternion(q1.n*q2.n - q1.v.x*q2.v.x - q1.v.y*q2.v.y - q1.v.z*q2.v.z, q1.n*q2.v.x + q1.v.x*q2.n + q1.v.y*q2.v.z - q1.v.z*q2.v.y, q1.n*q2.v.y + q1.v.y*q2.n + q1.v.z*q2.v.x - q1.v.x*q2.v.z, q1.n*q2.v.z + q1.v.z*q2.n + q1.v.x*q2.v.y - q1.v.y*q2.v.x); }
This operator simply multiplies each component in the quaternion by the scalar s. There are two forms of this operator, depending on the order in which the quaternion and scalar are encountered:
inline Quaternion operator*(Quaternion q, float s) { return Quaternion(q.n*s, q.v.x*s, q.v.y*s, q.v.z*s); } inline Quaternion operator*(float s, Quaternion q) { return Quaternion(q.n*s, q.v.x*s, q.v.y*s, q.v.z*s); }
This operator multiplies the quaternion q
by the vector v
as though the vector v
were
a quaternion with its scalar component equal to 0. There are two forms of this operator,
depending on the order in which the quaternion and vector are encountered. Since v
is assumed to be a quaternion with its scalar part equal to 0,
the rules of multiplication follow those outlined earlier for quaternion
multiplication:
inline Quaternion operator*(Quaternion q, Vector v) { return Quaternion( -(q.v.x*v.x + q.v.y*v.y + q.v.z*v.z), q.n*v.x + q.v.y*v.z - q.v.z*v.y, q.n*v.y + q.v.z*v.x - q.v.x*v.z, q.n*v.z + q.v.x*v.y - q.v.y*v.x); } inline Quaternion operator*(Vector v, Quaternion q) { return Quaternion( -(q.v.x*v.x + q.v.y*v.y + q.v.z*v.z), q.n*v.x + q.v.z*v.y - q.v.y*v.z, q.n*v.y + q.v.x*v.z - q.v.z*v.x, q.n*v.z + q.v.y*v.x - q.v.x*v.y); }
This operator simply divides each component in the quaternion by the scalar s:
inline Quaternion operator/(Quaternion q, float s) { return Quaternion(q.n/s, q.v.x/s, q.v.y/s, q.v.z/s); }
This function[30] extracts the angle of rotation about the axis represented by the vector part of the quaternion:
inline float QGetAngle(Quaternion q) { return (float) (2*acos(q.n)); }
This function returns a unit vector along the axis of rotation represented by the vector part
of the quaternion q
:
inline Vector QGetAxis(Quaternion q) { Vector v; float m; v = q.GetVector(); m = v.Magnitude(); if (m <= tol) return Vector(); else return v/m; }
This function rotates the quaternion p by q according to the formula:
p’ = (q)(p)(~q) |
Here, ~q is the conjugate of the unit quaternion q. Here’s the code:
inline Quaternion QRotate(Quaternion q1, Quaternion q2) { return q1*q2*(~q1); }
This function rotates the vector v by the unit quaternion q according to the formula:
p’ = (q)(v)(~q) |
Here, ~q is the conjugate of the unit quaternion q. Here’s the code:
inline Vector QVRotate(Quaternion q, Vector v) { Quaternion t; t = q*v*(~q); return t.GetVector(); }
This function constructs a quaternion from a set of Euler angles.
For a given set of Euler angles, yaw (ψ), pitch (τ), and roll (φ), defining rotation about the z-axis, then the y-axis, and then the z-axis, you can construct the representative rotation quaternion. You do this by first constructing a quaternion for each Euler angle and then multiplying the three quaternions following the rules of quaternion multiplication. Here are the three quaternions representing each Euler rotation angle:
qroll = [cos(φ/2), (sin(φ/2)) i + 0 j + 0 k] |
qpitch = [cos(τ /2), 0 i + (sin(τ /2)) j + 0 k] |
qyaw = [cos(ψ /2), 0 i + 0 j + (sin(ψ /2)) k] |
Each one of these quaternions is of unit length.[31]
Now you can multiply these quaternions to obtain a single one that represents the rotation, or orientation, defined by the three Euler angles:
q = qyaw qpitch qroll |
Performing this multiplication yields:
q = [ {cos(φ/2) cos(τ /2) cos(ψ /2) + sin(φ/2) sin(τ /2) sin(ψ /2)}, |
{ sin(φ/2) cos(τ /2) cos(ψ /2) - cos(φ/2) sin(τ /2) sin(ψ /2) } i + |
{ cos(φ/2) sin(τ /2) cos(ψ /2) + sin(φ/2) cos(τ /2) sin(ψ /2) } j + |
{ cos(φ/2) cos(τ /2) sin(ψ /2) - sin(φ/2) sin(τ /2) cos(ψ /2) } k ] |
Here’s the code that takes three Euler angles and returns a quaternion:
inline Quaternion MakeQFromEulerAngles(float x, float y, float z) { Quaternion q; double roll = DegreesToRadians(x); double pitch = DegreesToRadians(y); double yaw = DegreesToRadians(z); double cyaw, cpitch, croll, syaw, spitch, sroll; double cyawcpitch, syawspitch, cyawspitch, syawcpitch; cyaw = cos(0.5f * yaw); cpitch = cos(0.5f * pitch); croll = cos(0.5f * roll); syaw = sin(0.5f * yaw); spitch = sin(0.5f * pitch); sroll = sin(0.5f * roll); cyawcpitch = cyaw*cpitch; syawspitch = syaw*spitch; cyawspitch = cyaw*spitch; syawcpitch = syaw*cpitch; q.n = (float) (cyawcpitch * croll + syawspitch * sroll); q.v.x = (float) (cyawcpitch * sroll - syawspitch * croll); q.v.y = (float) (cyawspitch * croll + syawcpitch * sroll); q.v.z = (float) (syawcpitch * croll - cyawspitch * sroll); return q; }
This function extracts the three Euler angles from a given quaternion.
You can extract the three Euler angles from a quaternion by first converting the quaternion to a rotation matrix and then extracting the Euler angles from the rotation matrix. Let R be a nine-element rotation matrix:
and let q be a quaternion:
q = [n, x i + y j + z k] |
Then each element in R is calculated from q as follows:
r11 = n2 + x2 − y2 − z2 |
r21 = 2xy+2zn |
r31 = 2zx − 2yn |
r12 = 2xy − 2zn |
r22 = n2 − x2 + y2 − z2 |
r32 = 2zy + 2xn |
r13 = 2xz + 2yn |
r23 = 2yz − 2xn |
r33 = n2 − x2 − y2 + z2 |
To extract the Euler angles, yaw (ψ), pitch (τ), and roll (φ), from R, you can use these relations:
sin τ = −r31 |
tan φ = r32 / r33 |
tan ψ = r21 / r11 |
Here’s the code that extracts the three Euler angles, returned in
the form of a Vector
, from a given
quaternion:
inline Vector MakeEulerAnglesFromQ(Quaternion q) { double r11, r21, r31, r32, r33, r12, r13; double q00, q11, q22, q33; double tmp; Vector u; q00 = q.n * q.n; q11 = q.v.x * q.v.x; q22 = q.v.y * q.v.y; q33 = q.v.z * q.v.z; r11 = q00 + q11 - q22 - q33; r21 = 2 * (q.v.x*q.v.y + q.n*q.v.z); r31 = 2 * (q.v.x*q.v.z - q.n*q.v.y); r32 = 2 * (q.v.y*q.v.z + q.n*q.v.x); r33 = q00 - q11 - q22 + q33; tmp = fabs(r31); if(tmp > 0.999999) { r12 = 2 * (q.v.x*q.v.y - q.n*q.v.z); r13 = 2 * (q.v.x*q.v.z + q.n*q.v.y); u.x = RadiansToDegrees(0.0f); //roll u.y = RadiansToDegrees((float) (-(pi/2) * r31/tmp)); // pitch u.z = RadiansToDegrees((float) atan2(-r12, -r31*r13)); // yaw return u; } u.x = RadiansToDegrees((float) atan2(r32, r33)); // roll u.y = RadiansToDegrees((float) asin(-r31)); // pitch u.z = RadiansToDegrees((float) atan2(r21, r11)); // yaw return u; }
These two functions are used to convert angles from degrees to radians and radians to degrees. They are not specific to quaternions but are used in some of the code samples shown earlier:
inline float DegreesToRadians(float deg) { return deg * pi / 180.0f; } inline float RadiansToDegrees(float rad) { return rad * 180.0f / pi; }
[30] For a description of how quaternions are used to represent rotation, refer to the section Quaternions in Chapter 11.
[31] You can verify this by recalling the trigonometric relation cos2θ + sin2 θ = 1.
18.119.163.238