Rigid-body rotation is actually quite a bit more complex than translation.

Linear Translation |
Rotation |

Position P vec3, meters |
Angular Orientation R (see last lecture) 3 angles in radians, a quaternion, or a matrix |

Velocity V=dP/dt vec3, m/s |
Angular Velocity W = dR/dt 3 rates in radians/sec, a quaternion derivative, a skew matrix or a vec3, radians/sec |

Acceleration A=dV/dt vec3, m/s^2 |
Angular Acceleration
α = dW/dt 3 rates in radians/sec^2 |

Mass m float, Kg |
Rotational
inertia matrix
I mat3, Kg*m^2 |

Linear Momentum p=mV vec3, N*s |
Angular
Momentum L=I
W vec3, Kg*m^2/s |

Force F=mA=dp/dt vec3, N |
Torque T = R x F =
dL/dt vec3, N*m |

OK! |
huh?! |

The standard trick here is to ignore angular acceleration. Instead, sum up net torques in 3D, integrate (multiply by dt and sum) to get angular momentum, divide by rotational inertia (or multiply by the inverse inertia matrix) to get angular velocity, and use that to adjust the current angular orientation. Note that everything's a vec3 until that last step.

If you're using

If you're using a

var axis=W.clone(); axis.normalize();

dR.setFromAxisAngle(axis, W.length()*lib.dt);

R.multiplyQuaternions(dR,R);

There's also a handy quaternion differentiation formula:

dR/dt = 0.5 * W * R

Expanding the derivative into a finite difference, we have:

R += dt * 0.5 * W * R

Since W is a vec3, we first scale it by dt*0.5. We then need to multiply it by the quaternion orientation R, so we promote it to a vec4 by tacking on a w component of 0 and multiply. Finally, we add the resulting product to R term-by-term. In GLSL, this is simple other than the quaternion multiplication quat_mult:

vec4 R=...; // orientation quaternion

vec3 W=...; // angular velocity vector (radians/sec)

R+=quat_mult(vec4(dt*0.5*W,0.0), R);

In THREE.js, there doesn't seem to be a way to convert from Vector3 to Quaternion, or add quaternions, so we need to work term by term:

var v=W.t(0.5*lib.dt); // scale as vec3

dR.x=v.x; dR.y=v.y; dR.z=v.z; dR.w=0.0; // copy vec3 to quaternion

dR.multiplyQuaternions(dR,R); // W*R

R.x+=dR.x; R.y+=dR.y; R.z+=dR.z; R.w+=dR.w; // add quaternions

Axis-angle is simpler to understand, but the trig inside setFromAxisAngle makes it slower than this quaternion derivative approach. In both cases, it's good practice to normalize the quaternion afterward to keep roundoff from changing the object scale.

If you're using a

[ 0 -W.z W.y ]You do need to first rotate the skew matrix by the current rotation, or it will stop working after a quarter turn. You then need to orthonormalize, or the object will slowly grow in size.

[ W.z 0 -W.x ]

[ -W.y W.x 0 ]

(See a more complete discussion of applying angular velocity here.)