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


105

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

Ответы:


116
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);

Не забудьте нормализовать q.

Ричард прав в том, что не существует уникального вращения, но приведенное выше должно дать «кратчайшую дугу», что, вероятно, и нужно вам.


30
Имейте в виду, что это не касается случая параллельных векторов (как в одном направлении, так и в противоположных направлениях). crossproductне будет действительным в этих случаях, поэтому сначала необходимо проверить dot(v1, v2) > 0.999999и dot(v1, v2) < -0.999999, соответственно, и либо вернуть идентификационный quat для параллельных векторов, либо вернуть поворот на 180 градусов (вокруг любой оси) для противоположных векторов.
зловещий

11
Хорошую реализацию этого можно найти в исходном коде ogre3d
Жоао Портела

4
@sinisterchipmunk На самом деле, если v1 = v2, перекрестное произведение будет (0,0,0) и w будет положительным, что нормализует идентичность. Согласно gamedev.net/topic/… он должен работать нормально также для v1 = -v2 и в непосредственной близости от них.
jpa

3
Как у кого-то работает эта техника? Во-первых, sqrt((v1.Length ^ 2) * (v2.Length ^ 2))упрощает до v1.Length * v2.Length. Я не мог найти никаких вариаций для получения разумных результатов.
Джозеф Томсон

2
Да, это работает. См. Исходный код . L61 обрабатывает, если векторы обращены в противоположные стороны (верните PI, иначе он вернет идентичность в соответствии с замечанием @jpa). L67 обрабатывает параллельные векторы: математически ненужно, но быстрее. L72 - это ответ Polaris878, предполагая, что оба вектора имеют единичную длину (избегает sqrt). См. Также модульные тесты .
зловещий

63

Векторное решение на полпути

Я придумал решение, которое, как мне кажется, пытался представить Имброндир (хотя и с небольшой ошибкой, вероятно, поэтому зловещему бурундуку было трудно его проверить).

Учитывая, что мы можем построить кватернион, представляющий вращение вокруг оси, например:

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)));
}

12
+1: Отлично! Это сработало как оберег. Должен быть принятый ответ.
Rekin

1
Синтаксис Quaternion включен в некоторых примерах (Quaternion (xyz, w) и Quaternion (w, xyz)). Также кажется, что в последнем блоке кода радианы и градусы смешаны, чтобы выразить углы (180 против k_cos_theta + k).
Гильермо Бласко,

1
Кватернион (float, Vector3) - это построение из скалярного вектора, тогда как Quaternion (Vector3, float) - это построение из угла оси. Возможно, это сбивает с толку, но я думаю, что это правильно. Поправьте меня, если все еще думаете, что это неправильно!
Джозеф Томсон

Это сработало! Спасибо! Однако я нашел еще одну похожую и хорошо объясненную ссылку для выполнения вышеуказанной операции. Подумал, что должен поделиться для протокола;)
sinner

1
@JosephThomson Половина решения кватерниона, кажется, исходит отсюда .
legends2k

6

Проблема, как указано, четко не определена: для данной пары векторов не существует уникального поворота. Рассмотрим, например, случай, когда u = <1, 0, 0> и v = <0, 1, 0> . Один поворот от u до v будет поворотом на pi / 2 вокруг оси z. Другой поворот от u к v будет поворотом на пи вокруг вектора <1, 1, 0> .


1
На самом деле, не существует ли бесконечного количества возможных ответов? Потому что после того, как вы выровняете вектор «от» с вектором «до», вы все равно можете свободно вращать результат вокруг своей оси? Знаете ли вы, какую дополнительную информацию обычно можно использовать, чтобы ограничить этот выбор и четко обозначить проблему?
Дуг МакКлин,

5

Почему бы не представить вектор с помощью чистых кватернионов? Возможно, лучше сначала их нормализовать.
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


2
Я потерялся, разве поворот от q1 к q2 не рассчитывается как q_2 = q_rot q_1 q_rot ^ -1?
Йота

4

Я не очень хорошо разбираюсь в Кватернионе. Однако я часами бился над этим и не мог заставить решение 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его ценность определяется скалярным произведением.
Сэм Хосевар

Где находится функция Quaternion ()?
Джун Ван

3

Некоторые ответы, похоже, не учитывают возможность того, что кросс-произведение может быть 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
Максим Ганенко

2-й параметр - angleэто часть осевого угла кватерниона, измеряемого в радианах.
Шитал Шах

Вас попросили заставить кватернион вращаться от одного вектора к другому. У вас нет угла, вы должны сначала его вычислить. Ваш ответ должен содержать расчет угла. Ура!
Максим Ганенко

Это c ++? что такое ux ()?
Джун Ван

Да, это C ++. u - векторный тип из библиотеки Eigen (если вы ее используете).
Шитал Шах

2

С точки зрения алгоритма, самое быстрое решение выглядит в псевдокоде.

 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
 }

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

ПРИМЕЧАНИЕ. Неединичные кватернионы могут использоваться с некоторыми операциями быстрее, чем единицы.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.