A quaternion implementation
- Header
- Create a quaternion
- Copying
- Streaming
- Operations on a quaternion
- Quaternion multiplication
- Setting the rotation of the quaternion
Header
#include <morph/quaternion.h>
Header file: morph/quaternion.h. Test and example code: tests/testQuaternion
A class for doing quaternion operations. Initially developed for use in morph::Visual
to compute rotations of the scene.
Defined as:
namespace morph {
template <typename Flt>
class quaternion
where Flt
hints that the template arg is a floating point type. The Hamiltonian convention is adopted for the order of the member elements: Flt w, x, y, z;
. These are the only member data attributes.
Create a quaternion
morph::quaternion<float> q; // By default w=1, and x, y, z = 0;
morph::quaternion<float> q(1, 0, 0, 0); // Explicit setting of elements
A newly created quaternion will be an identity quaternion (1,0,0,0) unless explicity set otherwise.
You can also create a quaternion with a specified rotation about a given vector axis:
morph::vec<float, 3> z_axis = { 0, 0, 1 }; // set a rotation about the z axis...
float angle = morph::mathconst<float>::pi_over_2; // ...of pi/2 radians
morph::quaternion<float> q (z_axis, angle); // The (axis, angle) constructor
Copying
morph::quaternion<float> q1;
morph::quaternion<float> q2 = q1; // Assignment works as expected
You can use equality and inequality operators on a quaternion: q1 == q2
and q1 != q2
.
Streaming
You can stream a quaternion object:
morph::quaternion<float> q1(1, 0, 0, 0);
std::cout << q1 << std::endl;
Output: quaternion[wxyz]=(1,0,0,0)
Operations on a quaternion
Renormalize to magnitude 1 or check if it’s a unit quaternion:
q1.renormalize();
q1.checkunit();
The quaternion can be reset to the identity with reset()
.
Quaternion multiplication
The most useful feature of quaternions, and the reason for their popularity in computer programs is the fact that the rotations can be combined by multiplication and their rotations applied to a vector by multiplication.
Applying a quaternion rotation to a morph::vec
You have a vector (3D or ‘3+1’D) and you want to apply the rotation specified by a quaternion? You use the multiplication operator*
:
using mc = morph::mathconst<float>;
// Create quaternion for a rotation of pi radians
morph::quaternion<float> q(morph::vec<float>{1, 0, 0}, mc::pi);
morph::vec<float> v = { 1, 2, 3 };
morph::vec<float> rotated = q * v; // rotates v by pi about the x axis
Combining rotations by quaternion multiplication If we have 2
quaternions q1
and q2
that specify rotations, we can combine them into a third quaternion by multiplication. In the following code, q3
becomes the rotation that would result from first applying rotation q1, then applying rotation q2.
using mc = morph::mathconst<float>;
morph::quaternion<float> q1(morph::vec<float>({1,0,0}), mc::pi_over_3);
morph::quaternion<float> q2(morph::vec<float>({0,1,0}), mc::pi_over_4);
morph::quaternion<float> q3 = q2 * q1;
To multiply one quaternion by another, it’s important to specify the multiplication order. The postmultiply
and premultiply
functions allow you to control this.
q1.postmultiply (q2); // Places result of q1 * q2 into q1.
q1.premultiply (q2); // Places result of q2 * q1 into q1.
The quaternion q3 = q2 * q1
is equivalent to applying an initial rotation of q1, followed by a rotation of q2. For this reason, in the following, rotated_vec1
and rotated_vec2
will hold the same result:
morph::quaternion<float> q3 = q2 * q1;
morph::vec<float> unrotated = { 1, 0, 2 };
morph::vec<float> rotated_vec1 = q3 * unrotated;
morph::vec<float> rotated_vec2 = q2 * (q1 * unrotated); // More computation required
Quaternion division is also possible: q3 = q1/q2;
as is division by a scalar. Note that quaternion addition and subtraction are not implemented in this class at present.
Inversions and conjugates
The rotational inversion, or ‘reverse rotation’ is obtained with invert
:
q.invert(); // Obtains the opposite rotation to the one specified by q
For the algebraic inverse or reciprocal, q-1, use inverse
. This finds the reciprocal, q-1 which has the property that q-1 * q = the identity quaternion (1,0,0,0).
q.inverse(); // q^-1
The conjugate is also obtainable with q.conjugate();
and the magnitude of the quaternion with q.magnitude();
.
q.norm()
is an alias for the magnitude and q.norm_squared()
returns the square of the norm.
Setting the rotation of the quaternion
The function set_rotation
sets the values of the quaternion to specify a rotation (specified in degrees, not radians) about the given three dimensional axis, starting from no rotation.
void set_rotation (const morph::vec<Flt>& axis, const Flt& angle);
Rotate the quaternion further with these methods:
void rotate (const morph::vec<Flt, 3>& axis, const Flt angle);
void rotate (const std::array<Flt, 3>& axis, const Flt angle);
void rotate (const Flt axis_x, const Flt axis_y, const Flt axis_z, const Flt angle)
Each method rotates the quaternion by an angle in radians about a 3D axis specified by the axis array (or by the individual components of the axis).
Rotation Matrix
You can obtain the equivalent 4x4 rotation matrix in column-major format (OpenGL friendly) from the quaternion with
std::array<Flt, 16> rotationMatrix() const; // Returns the rotation matrix
void rotationMatrix (std::array<Flt, 16>& mat) const // sets the values in the passed-in matrix
If you know that your quaternion can be assumed to be a unit quaternion, you can use
std::array<Flt, 16> unitRotationMatrix() const;
void unitRotationMatrix (std::array<Flt, 16>& mat) const;
These involve slightly less computation.