Поиск кватерниона, представляющего поворот от одного вектора к другому

У меня есть два вектора 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
 }

Убедитесь, что вам нужны единичные кватернионы (обычно это требуется для интерполяции).

Примечание: Неединичные кватернионы могут использоваться с некоторыми операциями быстрее единицы.