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


У меня есть два вектора u и v. Есть ли способ найти кватернион, представляющий вращение от u до v?

7 82

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
 }

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

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