Quaternions

A quaternion represents an angular rotation around an axis; or more technically, the rotational transformation of a space from one frame of reference to another.

In practical terms, a quarternion gives us a way to rotate a vector around an axis by some angle. Quarternions do not suffer from gimble-lock.

A quarternion can be constructed as an angle and an axis of rotation or by casting other rotation objects. It is not important for us to understand the internal math involved with using quaternions. Instead we only care here about its application to rotations.

Construction

In MAXScript, quaternions are implemented through the Quat class.

quat <degrees_float> <axis_point3>
<angleaxis> as quat
<eulerangle> as quat
<matrix3> as quat --extracts the rotation component as a quat

We can construct a quat by passing an amount of degrees and an axis of rotation to the class constructor or by casting any other rotation object to a quat as seen above.

Eg:

q1 = quat 30 [0, 0, 1]
q2 = (eulerangles 30 0 0) as quat

In the above code segment, we construct the quats q1 and q2 representing the following:

  • q1 represents a rotation of 30 degrees around the z axis
  • q2 represents the same rotation as the casted eulerangles object, which in this case amounts to 30 degrees around the x axis

Note: When constructing a quaternion from an angle and axis, be sure that the axis vector is normalized (See vectors article).

Usage

A quaternion object can be used to rotate objects and vectors. We will look at both capacities.

Components

A quaternion has 4 components, x, y, z, and w. Each is a floating point value. The components of a quat must follow a long list of very specific rules that are beyond the scope of this course. Quaternions overall are complex objects so accessing their raw component values is not very useful. Most of the time, we will use quats as black boxes that we can construct using their angle axis constructor or using a cast. Using the listed high level operations on a quat allows us to make use of them without having to understand the theory behind them.

rotate node quat

To rotate an object using a quaternion, we use the rotate function. Eg:

/* Assume that mybox is a geometric object */

rotate mybox (quat 45 [0, 0, 1]) /* rotate mybox 45 degrees counterclockwise around z axis */

point3 * quat -> point3

We can get a rotated vector by multiplying a point3 by a quat. Eg:

a = [1, 0, 0]
q = quat 90 [0, 1, 0]
b = a * q /* b is [0, 0, 1] ie [1, 0, 0] rotated 90 degrees CLOCKWISE around the y axis */

Note: Due to issues with MAXScript's implementation of quats, vector rotation is clockwise while object rotation is counterclockwise. For a workaround, see the Implementation Issues section.

Identity Quaternion

An identity quaternion is the default quaternion that performs a zero rotation on objects and vectors ie it does not rotate them. Mathematically, this is similar to multiplying a number by 1. The identity quat has the following special component values:

x = 0, y = 0, z = 0, w = 1

To construct this quaternion, we use the 4 element quat constructor. Eg:

q = quat 0 0 0 1 /* 4 element constructor passing in special identity value. Each value is a quat component value in the order x y z w */

a = [1, 0, 0]
b = a * q /* b is [1, 0, 0] since q performs no rotation */

/* Assume that mybox is a geometric object */

rotate mybox q /* mybox's rotation is not changed since q performs no rotation */

quat * quat -> quat

Rotations can be appended by using the operator. The resultant quaternion represents the rotation induced by applying the quat on the right first then the quat on the *left second. Eg:

a = quat 90 [1, 0, 0] /* a is a 90 degree rotation around the x axis */
b = quat 90 [0, 1, 0] /* b is a 90 degree rotation around the y axis */
c = b * a /* c rotates 90 degrees around the x axis first then rotates 90 degrees around the y axis second */

/* Assume that mybox, mybox2 are geometric objects */
rotate mybox a /* rotate mybox 90 degrees counterclockwise around the x axis */
rotate mybox b /* rotate mybox 90 degrees counterclockwise around the y axis */

rotate mybox2 c /* rotate mybox2 90 degrees counterclockwise around the x axis first then 90 degrees counterclockwise around the y axis second */

In the above code segment, mybox and mybox2 were ultimately rotated by the same amount.

inverse quat -> quat

A useful aspect of quaternions is that we can get the negative of a rotation (the opposite rotation) by inversing the rotation quaternion. This is useful since quaternions can be made to hold complex rotations where creating the opposite rotation could be difficult. This is not possible with eulerangles objects. Eg:

a = quat 45 [0, 0, 1] /* a is a 45 degree rotation around the z axis */
b = inverse qp /* b is the inverse of a, ie a -45 degree rotation around the z axis */
c = a * b /* c is the identity quaternion since any quat multiplied by its inverse is nullified */

/* Assume that mybox is a geometric object */

rotate mybox a /* mybox is now rotate 45 degrees around the z axis */
rotate mybox b /* mybox is now returned to its rotation before the last line */

Implementation Issues

MAXScript's implementation of quaternions is slightly problematic. The biggest issue is that a quaternion will rotate a vector clockwise around an axis while it will rotate a geometric object counterclockwise around an axis. This will be an issue if your algorithm needs to rotate vectors and objects by the same quaternion; for example, if you need to rotate an object and also update a heading vector.

To solve this problem, we need to rotate either the vector OR the object by an inverse copy of the quaternion used to store overall rotation.

Note: This must be done consistently or else you will produce incorrect results!

Eg:

q = quat 90 [1, 0, 0] /* q is a 90 degree rotation around the x axis */
a = [0, 1, 0]

/* Assume that mybox is a geometric object */

rotate mybox q  /* mybox is rotated 90 degrees counterclockwise around the x axis */
b = a * q /* b is now the vector [0, 0, -1] since a is rotated 90 degrees clockwise around the x axis (refer to coordinate system diagram in Math Review section, reiterated below) */

/* To solve this inconsistency, we should have rotated a by the inverse of q */

b = a * (inverse q) /* b is now the vector [0, 0, 1] representing a 90 degree counterclockwise rotation around the x axis, matching the rotation of mybox */


Image 1: 3DS Max's right handed viewport coordinate system.

Reference

For more information, see the quat maxscript api.