Существует Metod с очень хорошим компромиссом между скоростью и точностью, как приращения quaterniom, представляющим состоянием вращения (т.е. интегрировать дифференциальное уравнение вращательного движения) с помощью небольшого вектора приращения угла dphi
(который является вектором угловая скорость omega
mulptipliad по времени шаг dt
).
Exact (и медленный) метод вращения кватернион вектором:
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
с помощью расширения taylor, выраженного только с использованием 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
(косинус половинного угла), который approximativelly 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
для малых углов и можно иногда пренебречь (в основном это просто ренормируют quternion).
Вы говорите, что не получаете ожидаемых результатов. Что, кажется, идет не так? – JCooper
Я использую модель для отслеживания, и она нестабильна. –
Является ли вектор «w» уже умножен на «delta time»? Это уравнение работает правильно, только если «время дельта» мало. – minorlogic