There is a method with a very good compromise between speed and accuracy , how to increase the quaternion representing the rotational state (i.e. integrate the differential equation of rotational motion), the vector increase in the angle dphi (which is the omega mulptipliad angular velocity vector with the time step dt ).
The exact (and slow) vector quaternion rotation method :
void rotate_quaternion_by_vector_vec ( double [] dphi, double [] q ) { double x = dphi[0]; double y = dphi[1]; double z = dphi[2]; double r2 = x*x + y*y + z*z; double norm = Math.sqrt( r2 ); double halfAngle = norm * 0.5d; double sa = Math.sin( halfAngle )/norm; // we normalize it here to save multiplications double ca = Math.cos( halfAngle ); x*=sa; y*=sa; z*=sa; double qx = q[0]; double qy = q[1]; double qz = q[2]; double qw = q[3]; q[0] = x*qw + y*qz - z*qy + ca*qx; q[1] = -x*qz + y*qw + z*qx + ca*qy; q[2] = x*qy - y*qx + z*qw + ca*qz; q[3] = -x*qx - y*qy - z*qz + ca*qw; }
The problem is that you have to compute slow functions like cos, sin, sqrt . Instead, you can get a significant increase in speed and reasonable accuracy at small angles (which occurs if the time step of your simulation is reasonably small) by approximating sin and cos a Taylor extension, expressed only using norm^2 instead of norm .
Like this Fast vector quaternion rotation method :
void rotate_quaternion_by_vector_Fast ( double [] dphi, double [] q ) { double x = dphi[0]; double y = dphi[1]; double z = dphi[2]; double r2 = x*x + y*y + z*z; // derived from second order taylor expansion // often this is accuracy is sufficient final double c3 = 1.0d/(6 * 2*2*2 ) ; // evaulated in compile time final double c2 = 1.0d/(2 * 2*2) ; // evaulated in compile time double sa = 0.5d - c3*r2 ; double ca = 1 - c2*r2 ; x*=sa; y*=sa; z*=sa; double qx = q[0]; double qy = q[1]; double qz = q[2]; double qw = q[3]; q[0] = x*qw + y*qz - z*qy + ca*qx; q[1] = -x*qz + y*qw + z*qx + ca*qy; q[2] = x*qy - y*qx + z*qw + ca*qz; q[3] = -x*qx - y*qy - z*qz + ca*qw; }
you can increase accuracy by making half the angle, which is another 5 multiplications:
final double c3 = 1.0d/( 6.0 *4*4*4 ) ; // evaulated in compile time final double c2 = 1.0d/( 2.0 *4*4 ) ; // evaulated in compile time double sa_ = 0.25d - c3*r2 ; double ca_ = 1 - c2*r2 ; double ca = ca_*ca_ - sa_*sa_*r2 ; double sa = 2*ca_*sa_ ;
or an even more accurate other half angle:
final double c3 = 1.0d/( 6 *8*8*8 ); // evaulated in compile time final double c2 = 1.0d/( 2 *8*8 ); // evaulated in compile time double sa = ( 0.125d - c3*r2 ) ; double ca = 1 - c2*r2 ; double ca_ = ca*ca - sa*sa*r2; double sa_ = 2*ca*sa; ca = ca_*ca_ - sa_*sa_*r2; sa = 2*ca_*sa_;
NOTE. If you use a more complex integration scheme than just a verlet (for example, Runge-Kutta), you probably need the quaternion differential , and not a new (updated) quaternion.
this can be seen in the code here
q[0] = x*qw + y*qz - z*qy + ca*qx; q[1] = -x*qz + y*qw + z*qx + ca*qy; q[2] = x*qy - y*qx + z*qw + ca*qz; q[3] = -x*qx - y*qy - z*qz + ca*qw;
it could be interpreted as a multiplication of the old (not updated) quaternion by ca (cosine with a half angle), which is approximately ca ~ 1 for small angles and adding the rest (some transverse interactions). Therefore, the differential is simple:
dq[0] = x*qw + y*qz - z*qy + (1-ca)*qx; dq[1] = -x*qz + y*qw + z*qx + (1-ca)*qy; dq[2] = x*qy - y*qx + z*qw + (1-ca)*qz; dq[3] = -x*qx - y*qy - z*qz + (1-ca)*qw;
where the term ( 1 - ca ) ~ 0 for small angles and can sometimes be neglected (basically it's just a renormalization of quternion).