Существует метод с очень хорошим компромиссом между скоростью и точностью как увеличить кватерниому, представляющему вращательное состояние (то есть интегрировать дифференциальное уравнение вращательного движения) с небольшим векторным приращением угла 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
для малых углов и может иногда игнорироваться (в основном это просто перенормировка кватерниона).