有一种方法在速度和精度之间取得了很好的折衷,如何通过角度的小矢量增量(即矢量角速度乘以时间步长)来增加表示旋转状态的四元组(即积分旋转运动的微分方程)。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;
}
您可以通过做半 o 角来提高精度,再乘以 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_;
注意:如果您使用比 verlet 更复杂的集成方案(如 Runge-Kutta ),您可能需要四元数的微分,而不是新的(更新的)四元数。
这可以在这里的代码中看到
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
,有时可以忽略(基本上它只是重新规范化四元数)。