Coordinate System Rotation: Euler Angles, Rotation Matrix, and Quaternions
Lecture, Dr. Lawlor
To move the camera, objects in the world, or physical effects, we
need a reliable way to represent arbitrary rotations in 3D.
This is by far the simplest scheme--you store three rotation angles,
and rotate first about X, then about Y, and finally about Z.
The only problem is that the Z has changed by the time you get to
it, so it's difficult to control the later rotations properly.
By default, THREE.js supports Euler angles in the "rotation" field
of each object. So "sim.spheres[i].rotation.x" is the rotation
angle around the X axis, measured in radians.
You can try this out with the "WASD" (X and Y rotation) and "QZ" (Z
rotation) keys here:
Note that initially, the Y rotation (W and S) is aligned with the
pointy tips, which stick along the Y axis. But if I rotate in
Z (press Z), then the Y rotation direction has changed.
Quaternions are a cool mathematical construct that lets you
represent an arbitrary rotation as a 4D vector. The 3D XYZ
parts of the vector give the rotation axis for the direction.
The magnitude of the 3D part is the sine of half the rotation angle;
the cosine of half the rotation angle is stored in the final W
component of the vector.
It's a strange definition, but the neat part is that 'multiplying'
quaternions (using a cross product looking formula) gives you the
composition of the underlying rotations.
In THREE.js, you can set "obj.useQuaternion=true" to disable that
object's rotation angles and use "obj.quaternion" instead. You
can now rotate the object by building a quaternion to represent the
new rotation, and computing "obj.quaternion.multiplySelf(newRot);"
It's not immediately obvious there's a difference from Euler mode,
but now the rotations stick to the coordinate frame of the object--Y
rotations are always along the pointy tips.
Rotation Matrix / Orthonormal Coordinate Frame
I personally find quaternions fun but confusing, and I usually find
a need for object-local 3D vectors pointing in all directions: for
example, Z is used for run, X is used for strafe, Y is used for
jump. So I usually just keep my own object-local X, Y, and Z
vectors for each object, and keep them (1) normalized to unit
length, and (2) orthogonal to each other (I use cross product for
Any set of three orthonormal vectors actually forms the 3x3 core of
a rotation matrix. To get from object-Local coordinates
L into world-Global coordinates G:
[ G.x ] [ X.x Y.x Z.x ] [ L.x ]
This is equivalent to the vector equation G = L.x*X + L.y*Y + L.z*Z.
[ G.y ] = [ X.y Y.y Z.y ] [ L.y ]
[ G.z ] [ X.z Y.z Z.z ] [ L.z ]
To get back into local coordinates, we just transpose the matrix
(transposing a rotation matrix gives you the inverse matrix):
[ L.x ] [ X.x X.y X.z ] [ G.x ]
This is equivalent to L=vec3(dot(X,G),dot(Y,G),dot(Z,G)).
[ L.y ] = [ Y.x Y.y Y.z ] [ G.y ]
[ L.z ] [ Z.x Z.y Z.z ] [ G.z ]
To adjust a rotation matrix, I often just push the coordinate system
around. For example, to push the camera Z direction in the +X
direction due to mouse movement, I'd use something like:
Z' = Z + 0.001*mouse_dx*X;
The scalar in front of X is approximately an angle in radians (for
small angles). Of course, after pushing the coordinate axes
around, you need to re-orthonormalize by taking cross products.
Here's a demo of building an object coordinate system and matrix: