Разница между двумя кватернионами
решить
я делаю систему 3D-портала в своем движке (например, Portal game). Каждый из порталов имеет свою собственную ориентацию, сохраненную в кватернионе. Чтобы отобразить виртуальную сцену на одном из порталов, мне нужно вычислить разницу между двумя кватернионами и использовать результат для поворота виртуальной сцены.
при создании первого портала на левой стене, и второго на правой стене, вращение от одного к другому будет происходить в только одна ось, но, например, когда первый портал будет создан на полу, а второй на правой стене, вращение от одного к другому может быть в двух осях, и это проблема, потому что вращение идет неправильно.
я думаю, что проблема существует потому, что ориентация, например X
axis и Z
оси хранятся вместе в одном кватернионе, и мне нужно отдельно вручную умножить X
* Z
(или Z
* X
), но как это сделать с только один кватернион, (разница кватернион)? Или есть другой способ исправить поворот сцены?
EDIT:
вот на этой картинке два портала P1 и P2, стрелки показывают, как они вращаются. Когда я смотрю в P1, я вижу, что видит P2. Чтобы найти вращение, которое мне нужно повернуть основную сцену, чтобы быть похожим на виртуальную сцену на этой картинке, я делаю следующее:
- получение разницы от кватерниона P2 до кватернион Р1
- вращающийся результат на 180 градусов по оси Y (портал вверх)
- использование результата для поворота виртуальной сцене
этот метод выше работает только тогда, когда разница имеет место только в одной оси. Когда один портал на полу, или на потолке, это не будет работать, потому что разница кватернион состоит из более чем одной оси. Как было предложено, я попытался умножить кватернион P1 на кватернион P2 и обратно, но это не так рабочий.
EDIT 2:
чтобы найти разницу от P2 до P1, я делаю следующее:
Quat q1 = P1->getOrientation();
Quat q2 = P2->getOrientation();
Quat diff = Quat::diff(q2, q1); // q2 * diff = q1 //
вот функция Quat:: diff:
GE::Quat GE::Quat::diff(const Quat &a, const Quat &b)
{
Quat inv = a;
inv.inverse();
return inv * b;
}
обратная:
void GE::Quat::inverse()
{
Quat q = (*this);
q.conjugate();
(*this) = q / Quat::dot((*this), (*this));
}
конъюгат:
void GE::Quat::conjugate()
{
Quat q;
q.x = -this->x;
q.y = -this->y;
q.z = -this->z;
q.w = this->w;
(*this) = q;
}
скалярное произведение:
float GE::Quat::dot(const Quat &q1, const Quat &q2)
{
return q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w;
}
оператора*:
const GE::Quat GE::Quat::operator* ( const Quat &q) const
{
Quat qu;
qu.x = this->w*q.x + this->x*q.w + this->y*q.z - this->z*q.y;
qu.y = this->w*q.y + this->y*q.w + this->z*q.x - this->x*q.z;
qu.z = this->w*q.z + this->z*q.w + this->x*q.y - this->y*q.x;
qu.w = this->w*q.w - this->x*q.x - this->y*q.y - this->z*q.z;
return qu;
}
оператор/:
const GE::Quat GE::Quat::operator/ (float s) const
{
Quat q = (*this);
return Quat(q.x / s, q.y / s, q.z / s, q.w / s);
}
все это работает, потому что я испытывал его с GLM библиотека
5 ответов
если вы хотите найти кватернион diff
такое, что diff * q1 == q2
, тогда вам нужно использовать мультипликативный обратный:
diff * q1 = q2 ---> diff = q2 * inverse(q1)
where: inverse(q1) = conjugate(q1) / abs(q1)
and: conjugate( quaternion(re, i, j, k) ) = quaternion(re, -i, -j, -k)
если ваши кватернионы являются кватернионами вращения, все они должны быть единичными кватернионами. Это упрощает поиск обратного: так как abs(q1) = 1
ваш inverse(q1) = conjugate(q1)
можно найти, просто отрицая i
, j
и k
компоненты.
однако, для вида основанной на сцене геометрической конфигурации, которую вы описываете, вы, вероятно на самом деле не хочу делать выше, потому что вам также нужно правильно вычислить перевод.
самый простой способ сделать все правильно-преобразовать ваши кватернионы в матрицы вращения 4x4 и умножить их в соответствующем порядке с матрицами перевода 4x4, как описано в большинстве вводных текстов компьютерной графики.
конечно, можно составить евклидовы преобразования вручную, сохраняя ваши вращения в форме кватерниона в то время как применение кватернионов постепенно к отдельному вектору перевода. Однако этот метод имеет тенденцию быть технически неясным и склонным к ошибке кодирования: есть веские причины, по которым форма матрицы 4x4 является обычной, и одна из больших заключается в том, что, похоже, легче получить ее правильно.
Я решил свою проблему. Как оказалось, мне не нужна разница между двумя вращениями. Просто умножьте одно вращение на вращение на 180 градусов, а затем умножьте на обратное второе вращение таким образом (используя матрицы):
Matrix m1 = p1->getOrientation().toMatrix();
Matrix m2 = p2->getOrientation().toMatrix();
Matrix model = m1 * Matrix::rotation(180, Vector3(0,1,0)) * Matrix::inverse(m2);
и перевод вычисляется таким образом:
Vector3 position = -p2->getPosition();
position = model * position + p1->getPosition();
model = Matrix::translation(position) * model;
нет, вы должны умножить два кватерниона вместе, чтобы получить окончательный кватернион, который вы хотите.
предположим, что ваша первая ротация q1
и второй q2
. Вы хотите применить их в таком порядке.
результирующий кватернион будет q2 * q1
, который будет представлять ваше составное вращение (напомним, что кватернионы используют левое умножение, поэтому q2
применяется к q1
умножением от слева)
для краткого руководства по вычислению одного кватерниона обратитесь к my предыдущий ответ переполнения стека
Edit:
чтобы уточнить, вы столкнетесь с аналогичной проблемой с матрицами вращения и углами Эйлера. Вы определяете свои преобразования о X, Y и Z, а затем умножаете их вместе, чтобы получить результирующую матрицу преобразования (wiki). У вас здесь та же проблема. Вращение матрицами и кватернионами эквивалентны в большинстве случаев для представления вращений. Кватернионы предпочтительнее в основном потому, что они немного легче представлять (и легче для адресации gimbal lock)
кватернионы работают следующим образом: локальная система отсчета представлена как мнимые направления кватернионов i,j,k. Например, для наблюдателя, стоящего в дверях портала 1 и смотрящего в направлении стрелки, направление i может представлять направление стрелки, j-вверх, а k=ij указывает вправо от наблюдателя. В глобальных координатах, представленных кватернионом q1, оси в 3D координатах
q1*(i,j,k)*q1^-1=q1*(i,j,k)*q1',
где q ' - конъюгат, а для единичные кватернионы, конъюгат является обратным.
теперь задача состоит в том,чтобы найти единичный кватернион q так,чтобы направления q*(i, j, k)*q' в локальном кадре 1, выраженные в глобальных координатах, совпадали с повернутыми направлениями кадра 2 в глобальных координатах. Из эскиза, который означает вперед, становится назад, а левый становится правым, то есть
q1*q*(i,j,k)*q'*q1'=q2*(-i,j,-k)*q2'
=q2*j*(i,j,k)*j'*q2'
, которая легко достигается путем приравнивания
q1*q=q2*j or q=q1'*q2*j.
но детали могут отличаться, в основном, что другой ось может представлять направление "вверх" вместо j.
если глобальная система эскиза находится снизу, так что global-i указывает вперед в вертикальном направлении, global-j вверх и global-k вправо, то local1-(i,j,k) является глобальным-(-i,j,-k), давая
q1=j.
local2-(i,j,k) является глобальным-(- k,j,i), который может быть реализован
q2=sqrt(0.5)*(1+j),
С
(1+j)*i*(1-j)=i*(1-j)^2=-2*i*j=-2*k and
(1+j)*k*(1-j)=(1+j)^2*k= 2*j*k= 2*i
сравнение этого с фактическими значениями в вашей реализации будет укажите, как должно быть изменено назначение осей и направлений кватернионов.
проверить https://www.emis.de/proceedings/Varna/vol1/GEOM09.pdf
представьте себе, чтобы получить dQ от Q1 до Q2, я объясню, почему dQ = Q1* * Q2, а не Q2·Q1*
Это поворачивает кадр вместо объекта. Для любого вектора v в R3 действие вращения оператора L (v) = Q*·v·Q
Это не Q * v * Q*, что является действием вращения объекта.
Если вы поворачиваете Q1, а затем Q1* , а затем Q2, вы можете написать (Q1·Q1* * Q2) * * v·(Q1*Q1· * Q2) = (1 квартал*·К2)*·1 кв.*·в·К1·(К1*·В2) = йд*·1 кв.*·в·К1·йд
Итак, dQ = Q1 * * Q2