Конкатенации кватернионов работают только при выравнивании по оси

У меня есть простая реализация кватерниона для вращения объекта. Если я создам два кватерниона, представляющих вращения вокруг одного и того же вектора, выровненного по оси (т. е. вдоль оси X, Y или Z), результат будет таким же, как одиночное вращение по сумме их величин (т. е. вращение на PI/2, затем PI /2 снова совпадает с вращением на PI). Это хорошо.

Как только ось вращения не выровнена по осям, конкатенации отклоняются от ожидаемых (поворот на PI/2, затем снова PI/2 не совпадает с вращением на PI). Это не хорошо.

После изучения моего кода в течение пары дней я не вижу ничего плохого, поэтому позвольте мне спросить сейчас: я каким-то образом принципиально неправильно понимаю, как работают кватернионы? Я рассуждаю о кватернионах с точки зрения поворота оси на угол, который они представляют, потому что, честно говоря, я не совсем понимаю кватернионы.

Если нет, можете ли вы взглянуть на мой код? :-) Я только что отправил все это (написанное на Java — я ориентируюсь на Android) на GitHub: https://github.com/wtracy/quaternions В каталоге Quaternions находится проект Eclipse. (Вам не нужно, чтобы Eclipse что-то читал, но это удобно.) Класс quaternion находится в папке src/. В папке test/ находятся тесты JUnit и заглушки классов, необходимых для запуска моего класса Quaternion.

Я сделал все возможное, чтобы сделать мой код и тесты простыми для понимания. Я чувствую себя глупо, прося Интернет найти ошибку в моем коде, но у меня нет идей. :-П


person wtracy    schedule 20.12.2012    source источник


Ответы (2)


У вас есть ошибка знака в умножении кватернионов.

public Quaternion times(Quaternion q2) {
    Quaternion q1 = this;
    float w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z;
    float x = q1.x*q2.w + q1.w*q2.x + q1.z*q2.y - q1.y*q2.z;
    float y = q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x;
    float z = q1.w*q2.z - q2.x*q2.y + q1.y*q2.x + q1.z*q2.w;
    return new Quaternion(w, x, y, z);
}

Произведение, выписанное,

(w1 + x1*i + y1*j + z1*k)*(w2 + x2*i + y2*j + z2*k)
= w1*w2 - x1*x2 - y1*y2 - z1*z2
+ (w1*x2 + x1*w2 + y1*z2 - z1*y2)*i
+ (w1*y2 - x1*z2 + y1*w2 + z1*x2)*j
+ (w1*z2 + x1*y2 - y1*x2 + z1*w2)*k

поскольку

i*j = k    j*i = -k
j*k = i    k*j = -i
k*i = j    i*k = -j

У вас неправильные термины с минусом в уравнениях для x и z - это не будет иметь значения, если две оси одинаковы, потому что можно также написать (в виде стенографии)

(r + v)*(s + w) = r*s - <v|w> + r*w + s*v + v×w

и v×w = 0 для коллинеарных векторов, но если оси разные, это будет отображаться.

Далее, в уравнении для z

float z = q1.w*q2.z - q2.x*q2.y + q1.y*q2.x + q1.z*q2.w;
                          ^^^^^^^^^

у вас есть опечатка, используя q2 для обоих факторов один раз.

Так должно быть

public Quaternion times(Quaternion q2) {
    Quaternion q1 = this;
    float w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z;
    float x = q1.x*q2.w + q1.w*q2.x - q1.z*q2.y + q1.y*q2.z;
    float y = q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x;
    float z = q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w;
    return new Quaternion(w, x, y, z);
}
person Daniel Fischer    schedule 21.12.2012
comment
Большое спасибо за то, что поставили меня на правильный путь. Я все еще получаю нечетные значения для w, но, по крайней мере, я знаю, что нахожусь на правильном пути. - person wtracy; 21.12.2012

Как только ось вращения не выровнена по оси, конкатенации расходятся с ожидаемыми (поворот на PI/2, затем снова PI/2 не совпадает с вращением на PI). Это не хорошо.

Даже после того, как вы правильно рассчитаете, вы все равно обнаружите, что это не так. Вращения в трехмерном пространстве вообще не коммутативны. Если вы выполняете вращение A, а затем вращение B, вы, как правило, получите другую ориентацию, чем если бы сначала выполнялось вращение B, а затем вращение A.

person David Hammen    schedule 21.12.2012