Знаходження кватерніона, що представляє обертання від одного вектора до іншого


105

У мене є два вектори u і v. Чи є спосіб знайти кватерніон, що представляє обертання від u до v?

Відповіді:


115
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, відповідно, або повернути ідентифікаційний кват для паралельних векторів, або повернути на 180 градусів обертання (приблизно на будь-яку вісь) для протилежних векторів.
sinisterchipmunk


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). Дивіться також одиничні тести .
sinisterchipmunk

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 = кут / 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: Чудово! Це спрацювало як шарм. Повинна бути прийнята відповідь.
Рекін

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

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

Це спрацювало! Дякую! Однак я знайшов ще одне подібне і добре пояснене посилання для виконання вищезгаданої операції. Думав, що я повинен поділитися для запису;)
грішник

1
@JosephThomson Рішення про кватерніон на півдорозі, здається, походить звідси .
legends2k

6

Зазначена проблема недостатньо чітко визначена: не існує унікального обертання для даної пари векторів. Розглянемо випадок, наприклад, де u = <1, 0, 0> і v = <0, 1, 0> . Одне обертання від u до v буде обертанням pi / 2 навколо осі z. Іншим обертанням від u до v було б пі обертання навколо вектора <1, 1, 0> .


1
Насправді чи не існує нескінченна кількість можливих відповідей? Тому що після вирівнювання вектора "від" з вектором "до" ви все ще можете вільно обертати результат навколо своєї осі? Чи знаєте ви, яку додаткову інформацію зазвичай можна використати для обмеження цього вибору та чітко визначеної проблеми?
Doug McClean

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 conj / q норма
Це можна вважати "лівим поділом". Правий поділ, який не є тим, чого ви хочете, це:
q гниль, правий = 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 (з деякою толерантністю), де я вважаю, що рішення повинні бути кватерніоном (1, 0,0,0) (без обертання) або Кватерніон (0, * v1) (обертання на 180 градусів)


У мене працює реалізація, але ця твоя гарніша, тому я дуже хотіла, щоб вона працювала. На жаль, це не вдалося в усіх моїх тестових випадках. Всі мої тести виглядають приблизно так quat = diffVectors(v1, v2); assert quat * v1 == v2.
sinisterchipmunk

Навряд чи це буде працювати взагалі, оскільки angleотримує свою цінність від крапкового продукту.
sam hocevar

Де функція кватерніона ()?
Червень Ван

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
Максим Ганенко

Другий параметр - angleце частина вісьового кута кватерніона, виміряна в радіанах.
Shital Shah

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

Це c ++? що таке ux ()?
Червень Ван

Так, це C ++. u - векторний тип із бібліотеки Eigen (якщо ви його використовуєте).
Shital Shah

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.