角度の小さなベクトル増分(時間ステップで多重化されたベクトル角速度)によって回転状態を表す四分位をインクリメントする方法(つまり、回転運動の微分方程式を統合する方法)には、速度と精度の間に非常に良いトレードオフの方法があります。dphi
omega
dt
ベクトルによるクォータニオンの正確な(そして遅い)回転方法:
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;
}
問題は、のような遅い関数を計算する必要があることですcos, sin, sqrt
。sin
代わりに、近似し、の代わりにをcos
使用して表現されたテイラー展開によって、小さな角度でかなりの速度ゲインと妥当な精度を得ることができます(シミュレーションの時間ステップが妥当な場合)。norm^2
norm
このように、ベクトルによるクォータニオンの高速回転方法:
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;
}
角度を半分にして、さらに5回乗算することで、精度を上げることができます。
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_ ;
または、他の分割角度を半分にするとさらに正確になります。
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_;
注:ベレ(ルンゲクッタ法など)よりも高度な統合スキームを使用する場合は、新しい(更新された)クォータニオンではなく、クォータニオンの差分が必要になる可能性があります。
これはここのコードで見ることができます
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;
ca
これは、古い(更新されていない)クォータニオンに(半角のコサイン)を掛けたものと解釈できます。これは、おおよそca ~ 1
小さな角度で、残りを追加します(いくつかの相互作用)。したがって、差分は単純に:
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;
ここで( 1 - ca ) ~ 0
、小さな角度の用語であり、無視されることもあります(基本的には、クターニオンを繰り込みます)。