У меня есть два вектора u и v. Есть ли способ найти кватернион, представляющий поворот от u к v?
У меня есть два вектора u и v. Есть ли способ найти кватернион, представляющий поворот от u к v?
Ответы:
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
Не забудьте нормализовать q.
Ричард прав в том, что не существует уникального вращения, но приведенное выше должно дать «кратчайшую дугу», что, вероятно, и нужно вам.
sqrt((v1.Length ^ 2) * (v2.Length ^ 2))
упрощает до v1.Length * v2.Length
. Я не мог найти никаких вариаций для получения разумных результатов.
Я придумал решение, которое, как мне кажется, пытался представить Имброндир (хотя и с небольшой ошибкой, вероятно, поэтому зловещему бурундуку было трудно его проверить).
Учитывая, что мы можем построить кватернион, представляющий вращение вокруг оси, например:
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)));
}
Проблема, как указано, четко не определена: для данной пары векторов не существует уникального поворота. Рассмотрим, например, случай, когда u = <1, 0, 0> и v = <0, 1, 0> . Один поворот от u до v будет поворотом на pi / 2 вокруг оси z. Другой поворот от u к v будет поворотом на пи вокруг вектора <1, 1, 0> .
Почему бы не представить вектор с помощью чистых кватернионов? Возможно, лучше сначала их нормализовать.
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 con / q norm
Это можно рассматривать как «левое деление». Правое деление, которое вам не нужно:
q rot, right = q 2 -1 q 1
Я не очень хорошо разбираюсь в Кватернионе. Однако я часами бился над этим и не мог заставить решение 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 параллельны, как v1 == v2 или v1 == -v2 (с некоторым допуском), где я считаю, что решения должны быть Quaternion (1, 0,0,0) (без вращения) или Quaternion (0, * v1) (поворот на 180 градусов)
quat = diffVectors(v1, v2); assert quat * v1 == v2
.
angle
его ценность определяется скалярным произведением.
Некоторые ответы, похоже, не учитывают возможность того, что кросс-произведение может быть 0. В приведенном ниже фрагменте используется представление оси угла:
//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)
toQuaternion(axis, ang)
-> вы забыли указать, что этоang
angle
это часть осевого угла кватерниона, измеряемого в радианах.
С точки зрения алгоритма, самое быстрое решение выглядит в псевдокоде.
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
}
Убедитесь, что вам нужны единичные кватернионы (обычно это требуется для интерполяции).
ПРИМЕЧАНИЕ. Неединичные кватернионы могут использоваться с некоторыми операциями быстрее, чем единицы.
crossproduct
не будет действительным в этих случаях, поэтому сначала необходимо проверитьdot(v1, v2) > 0.999999
иdot(v1, v2) < -0.999999
, соответственно, и либо вернуть идентификационный quat для параллельных векторов, либо вернуть поворот на 180 градусов (вокруг любой оси) для противоположных векторов.