У мене є два вектори u і v. Чи є спосіб знайти кватерніон, що представляє обертання від u до v?
У мене є два вектори u і v. Чи є спосіб знайти кватерніон, що представляє обертання від u до v?
Відповіді:
Quaternion q;
vector a = crossproduct(v1, v2);
q.xyz = a;
q.w = sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + dotproduct(v1, v2);
Не забудьте нормалізувати q.
Річард має рацію про те, що не існує унікального обертання, але вищезазначене повинно дати "найкоротшу дугу", що, мабуть, те, що вам потрібно.
sqrt((v1.Length ^ 2) * (v2.Length ^ 2))
спрощується v1.Length * v2.Length
. Я не міг отримати жодних варіантів цього для отримання розумних результатів.
Я придумав рішення, яке, на мою думку, Імброндір намагався викласти (хоч і з незначною помилкою, імовірно, тому у зловмисного чорнобривця виникли проблеми з його верифікацією).
Враховуючи, що ми можемо побудувати кватерніон, що представляє обертання навколо осі так:
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)));
}
Зазначена проблема недостатньо чітко визначена: не існує унікального обертання для даної пари векторів. Розглянемо випадок, наприклад, де u = <1, 0, 0> і v = <0, 1, 0> . Одне обертання від u до v буде обертанням pi / 2 навколо осі z. Іншим обертанням від u до v було б пі обертання навколо вектора <1, 1, 0> .
Чому б не представити вектор, використовуючи чисті кватерніони? Краще, якщо ви їх спочатку нормалізуєте.
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
Я не дуже добре в Кватерніоні. Однак я багато років боровся з цим, і не зміг змусити рішення 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
.
angle
отримує свою цінність від крапкового продукту.
Деякі з відповідей, здається, не враховують можливості того, що поперечний продукт може бути 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
це частина вісьового кута кватерніона, виміряна в радіанах.
З точки зору алгоритму, найшвидше рішення виглядає в псевдокоді
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
}
Будьте впевнені, що вам потрібні одиниці кватерніонів (зазвичай це потрібно для інтерполяції).
ПРИМІТКА: Квартіні, що не входять до складу підрозділу, можуть використовуватися з деякими операціями швидше, ніж одиниця.
crossproduct
не буде дійсним у цих випадках, тому спочатку потрібно перевіритиdot(v1, v2) > 0.999999
іdot(v1, v2) < -0.999999
, відповідно, або повернути ідентифікаційний кват для паралельних векторів, або повернути на 180 градусів обертання (приблизно на будь-яку вісь) для протилежних векторів.