To move the camera, objects in the world, or physical effects, we need a reliable way to represent arbitrary rotations in 3D.

You can try this out with the "WASD" (X and Y tilt) and "QZ" (screen rotation) keys here:

Keyboard Euler Rotation Demo

Note that initially, the X and Y rotations tip the head correctly, in orthogonal directions. Now use Q to rotate the head onscreen by 90 degrees. X and Y now tip the head in the same direction.

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. Because rotations don't commute (this is the problem with Euler angles), quaternion multiplication is non-commutative: you get a different position from multiplying the quaternions in the opposite order, because you get a different position from applying the rotations in the opposite order.

In THREE.js, you can access the object's THREE.Quaternion orientation using "obj.quaternion". You can now rotate the object by building a quaternion to represent the new rotation, for example via "newRot.setFromAxisAngle(myVec3,myAngleRadians)". To apply the rotation in object space, you'd multiply on the right:

obj.quaternion.multiply(newRot);

You can also apply a rotation in world space by multiplying on the left, with:

obj.quaternion.multiplyQuaternions(newRot,obj.quaternion);

It's good practice to obj.quaternion.normalize() to prevent the object from changing size due to roundoff in the quaternion manipulation.

Keyboard Quaternion Rotation Demo

It's not immediately obvious there's a difference from Euler mode, but now the rotations stick to the coordinate frame of the world--X and Y rotations always tip in orthogonal directions, regardless of the object orientation.

You can force your X, Y, and Z to be orthonormal using cross products. If you didn't start out orthogonal, you might need to choose the order of orthogonalization carefully to preserve the axes you want.

// OrthonormalizeAny 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:

s.X.crossVectors(s.Y,s.Z).normalize();

s.Y.crossVectors(s.Z,s.X).normalize();

s.Z.crossVectors(s.X,s.Y).normalize();

[ 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.

In THREE.Matrix4, there's a handy method makeBasis that takes your X, Y, and Z axes as vec3's. You can call this on any object's obj.matrix member, although you need to set the magic booleans obj.matrixAutoUpdate=false to avoid trashing the matrix from the quaternion.

Here's a demo of building an object coordinate system and uploading it as a matrix:

Keyboard Rotation Matrix Demo

Quaternions are a compact, mathematically robust way to represent rotations. In animation, you can interpolate between two quaternion rotations using simple vector interpolation, and normalizing the quaternion at each step, to get a smooth and plausible rotation animation--Euler angles tend to lurch around the poles if you try this, and matrices need to be re-orthonormalized and can make strange axis-flipping choices.

Matrices are larger than quaternions, with an inconvenient 9 entries, and you must manually orthonormalize them or else they can get skewed (due to roundoff, or manual manipulation). The big advantage is it's easy to build the code, and you can draw 3D vectors along the coordinate edges if you get confused. Older graphics interfaces like fixed-function OpenGL expect you to represent orientation using matrices, although you could push quaternions directly to the vertex shader in modern programmable shaders.