У меня есть два вектора u и v. Есть ли способ найти кватернион, представляющий поворот от u до v?
Поиск кватерниона, представляющего поворот от одного вектора к другому
Ответ 1
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
Не забудьте нормализовать q.
Ричард прав насчет того, чтобы не было уникального вращения, но приведенное выше должно дать "кратчайшую дугу", которая, вероятно, вам нужна.
Ответ 2
Полуостровное векторное решение
Я придумал решение, которое, как я считаю, Imbrondir пытался представить (хотя и с небольшой ошибкой, что, вероятно, было связано с тем, что зловещий маклер не мог проверить его).
Учитывая, что мы можем построить кватернион, представляющий поворот вокруг оси так:
q.w == cos(angle / 2)
q.x == sin(angle / 2) * axis.x
q.y == sin(angle / 2) * axis.y
q.z == sin(angle / 2) * axis.z
И что точка и поперечное произведение двух нормализованных векторов:
dot == cos(theta)
cross.x == sin(theta) * perpendicular.x
cross.y == sin(theta) * perpendicular.y
cross.z == sin(theta) * perpendicular.z
Наблюдение за поворотом от u до v может быть достигнуто путем поворота на тэта (угол между векторами) вокруг перпендикулярного вектора, похоже, что мы можем непосредственно построить кватернион, представляющий такое вращение по результатам точечного и кросс-произведения; однако, как он есть, theta = angle/2, что означает, что это приведет к удвоенному желаемому вращению.
Одним из решений является вычисление векторного полупериода между u и v, а также использование точечного и поперечного произведения u, а на полпути, чтобы построить кватернион, представляющий поворот в два раза по углу между u и вектором на полпути, который берет нас всех путь к v!
Существует особый случай, когда u == -v, и уникальный вектор полупотока становится невозможным для вычисления. Ожидается, что с учетом бесконечного числа "кратчайших" поворотов, которые могут взять нас от u до v, и мы должны просто вращаться на 180 градусов вокруг любого вектора, ортогонального к u (или v) в качестве нашего специального решения. Это делается путем взятия нормированного поперечного произведения u с любым другим вектором, не параллельным u.
Далее следует псевдокод (очевидно, в действительности особый случай должен был бы учитывать неточности с плавающей запятой - возможно, путем проверки точечных продуктов на некоторый порог, а не на абсолютное значение).
Также обратите внимание, что нет специального случая, когда u == v (создается кватернион идентичности - проверьте и убедитесь сами).
// N.B. the arguments are _not_ axis and angle, but rather the
// raw scalar-vector components.
Quaternion(float w, Vector3 xyz);
Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
// It is important that the inputs are of equal length when
// calculating the half-way vector.
u = normalized(u);
v = normalized(v);
// Unfortunately, we have to check for when u == -v, as u + v
// in this case will be (0, 0, 0), which cannot be normalized.
if (u == -v)
{
// 180 degree rotation around any orthogonal vector
return Quaternion(0, normalized(orthogonal(u)));
}
Vector3 half = normalized(u + v);
return Quaternion(dot(u, half), cross(u, half));
}
Функция orthogonal
возвращает любой вектор, ортогональный данному вектору. Эта реализация использует кросс-произведение с самым ортогональным базисным вектором.
Vector3 orthogonal(Vector3 v)
{
float x = abs(v.x);
float y = abs(v.y);
float z = abs(v.z);
Vector3 other = x < y ? (x < z ? X_AXIS : Z_AXIS) : (y < z ? Y_AXIS : Z_AXIS);
return cross(v, other);
}
Решение четвертичного полушария
Это на самом деле решение, представленное в принятом ответе, и оно, по-видимому, немного быстрее, чем решение на полпути (на 20% быстрее по моим измерениям, хотя не верю мне на слово). Я добавляю его здесь, если другие, подобные мне, заинтересованы в объяснении.
В сущности, вместо вычисления кватерниона с использованием вектора полуострова вы можете вычислить кватернион, который приводит к удвоенному требуемому вращению (как подробно описано в другом решении) и найти кватернион на полпути между этим и нулевым градусом.
Как я уже объяснял ранее, кватернион для двойного требуемого вращения:
q.w == dot(u, v)
q.xyz == cross(u, v)
И кватернион для нулевого вращения:
q.w == 1
q.xyz == (0, 0, 0)
Вычисление полутонового кватерниона - это просто вопрос суммирования кватернионов и нормализации результата, как и векторов. Однако, как и в случае с векторами, кватернионы должны иметь одинаковую величину, иначе результат будет искажен по отношению к кватерниону с большей величиной.
Кватернион, построенный из точки и поперечного произведения двух векторов, будет иметь такую же величину, что и те продукты: length(u) * length(v)
. Вместо того, чтобы делить все четыре компонента на этот коэффициент, мы можем вместо этого увеличить кватернион идентичности. И если вам интересно, почему принятый ответ, по-видимому, усложняет вопросы, используя sqrt(length(u) ^ 2 * length(v) ^ 2)
, потому что квадрат длины вектора быстрее вычисляется, чем длина, поэтому мы можем сохранить один расчет sqrt
. Результат:
q.w = dot(u, v) + sqrt(length_2(u) * length_2(v))
q.xyz = cross(u, v)
И затем нормализовать результат. Ниже следует псевдокод:
Quaternion get_rotation_between(Vector3 u, Vector3 v)
{
float k_cos_theta = dot(u, v);
float k = sqrt(length_2(u) * length_2(v));
if (k_cos_theta / k == -1)
{
// 180 degree rotation around any orthogonal vector
return Quaternion(0, normalized(orthogonal(u)));
}
return normalized(Quaternion(k_cos_theta + k, cross(u, v)));
}
Ответ 3
Задача, как указано, не определена корректно: для данной пары векторов нет единственного вращения. Рассмотрим случай, например, где u = < 1, 0, 0 > и v = < 0, 1, 0 > . Одно вращение от u до v было бы поворот pi/2 вокруг оси z. Другим поворотом от u до v будет поворот pi вокруг вектора < 1, 1, 0 > .
Ответ 4
Почему бы не представить вектор, используя чистые кватернионы? Это лучше, если вы сначала нормализуете их. q 1= (0 u x u y u z) '
q 2= (0 v x v y v z) '
q 1 q rot= q 2
Предмножество с q 1 -1
q rot= q 1 -1 q 2
где q 1 -1= q 1 conj/q norm
Это можно рассматривать как "левое деление".
Правильное деление, которого вы не хотите:
q rot, right= q 2 -1 q 1
Ответ 5
Я не очень хорош в Кватернионе. Однако я много часов боролся с этим и не мог заставить работу Polaris878 работать. Я пробовал предварительно нормализовать v1 и v2. Нормализация q. Нормализация q.xyz. Но все же я этого не понимаю. Результат по-прежнему не дал мне правильного результата.
В конце концов я нашел решение. Если это помогает кому-то еще, здесь мой рабочий (python) код:
def diffVectors(v1, v2):
""" Get rotation Quaternion between 2 vectors """
v1.normalize(), v2.normalize()
v = v1+v2
v.normalize()
angle = v.dot(v2)
axis = v.cross(v2)
return Quaternion( angle, *axis )
Необходимо сделать особый случай, если v1 и v2 paralell как v1 == v2 или v1 == -v2 (с некоторым допуском), где я считаю, что решения должны быть Quaternion (1, 0,0,0) ( нет вращения) или Quaternion (0, * v1) (поворот на 180 градусов)
Ответ 6
Некоторые из ответов, по-видимому, не учитывают возможность того, что кросс-произведение может быть равно 0. Ниже в фрагменте используется представление по angular оси:
//v1, v2 are assumed to be normalized
Vector3 axis = v1.cross(v2);
if (axis == Vector3::Zero())
axis = up();
else
axis = axis.normalized();
return toQuaternion(axis, ang);
toQuaternion
может быть реализован следующим образом:
static Quaternion toQuaternion(const Vector3& axis, float angle)
{
auto s = std::sin(angle / 2);
auto u = axis.normalized();
return Quaternion(std::cos(angle / 2), u.x() * s, u.y() * s, u.z() * s);
}
Если вы используете библиотеку Eigen, вы также можете просто сделать:
Quaternion::FromTwoVectors(from, to)
Ответ 7
С точки зрения алгоритма, самое быстрое решение выглядит в псевдокоде
Quaternion shortest_arc(const vector3& v1, const vector3& v2 )
{
// input vectors NOT unit
Quaternion q( cross(v1, v2), dot(v1, v2) );
// reducing to half angle
q.w += q.magnitude(); // 4 multiplication instead of 6 and more numerical stable
// handling close to 180 degree case
//... code skipped
return q.normalized(); // normalize if you need UNIT quaternion
}
Убедитесь, что вам нужны единичные кватернионы (обычно это требуется для интерполяции).
Примечание: Неединичные кватернионы могут использоваться с некоторыми операциями быстрее единицы.