Найти кватернион, представляющий поворот от одного вектора к другому
У меня есть два вектора u и v. Есть ли способ найти кватернион, представляющий вращение от u до v?
7 ответов:
Quaternion q; vector a = crossproduct(v1, v2); q.xyz = a; q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
Не забудьте нормализовать q.
Ричард прав насчет того, что не существует уникального вращения, но вышеизложенное должно дать "самую короткую дугу", что, вероятно, вам нужно.
Половинное Векторное Решение
я придумал решение, которое, как я считаю, Имброндир пытался представить (хотя и с незначительной ошибкой, которая, вероятно, была причиной того, что sinisterchipmunk не смог ее проверить).
учитывая, что мы можем построить кватернион, представляющий вращение вокруг оси следующим образом:
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 может быть достигнуто путем вращения тета (угол между векторами) вокруг перпендикулярного вектора, похоже, что мы можем непосредственно построить кватернион, представляющий такое вращение из результатов точечных и поперечных произведений; однако, как он стоит,тета = угол / 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)); }
The
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 = и v = . Один оборот от u до v будет a pi / 2 вращение вокруг оси z. Другой поворот от u к v будет a pi вращение вокруг вектора .
Почему бы не представить вектор с использованием чистых кватернионов? Лучше, если вы сначала нормализуете их, возможно.
вопрос1 = (0 u x uy uz)'
вопрос2 = (0 v x vy vz)'
вопрос1 qрот = q2
Предварительно умножьте на q1-1
вопросрот = вопрос1-1 q2
где q1-1 = q1conj / qнорма
Это можно рассматривать как"левое разделение". Правильное разделение, которое не то, что вы хотите:
вопросгниль,право = q2-1 q1
Я не очень хорошо на кватернион. Однако я боролся в течение нескольких часов на этом, и не мог заставить решение Polaris878 работать. Я пробовал предварительно нормализовать v1 и v2. Нормализуя вопрос. Нормализуя вопрос.АБВ. И все же я не понимаю. Результат еще не дал мне нужного результата.
в конце концов, хотя я нашел решение, которое сделал. Если это поможет кому-то еще, вот мой рабочий (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 (с некоторым допуском), где я считаю, что решения должны быть кватернион (1, 0,0,0) (без вращения) или кватернион (0, *v1) (поворот на 180 градусов)
некоторые из ответов, похоже, не рассматривают возможность того, что перекрестный продукт может быть 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);
The
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)
С точки зрения алгоритма, самое быстрое решение выглядит в псевдокоде
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 }
убедитесь, что вам нужны единичные кватернионы (обычно это требуется для интерполяции).
Примечание.: Неунитарные кватернионы могут использоваться с некоторыми операциями быстрее единицы.