У меня есть два вектора u и v. Есть ли способ найти кватернион, представляющий поворот от u до v?
Ответ 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)));
}