Возможно, вы найдете кватернионов более элегантным и эффективным решением.
После недавнего столкновения с этим ответом я решил дать более надежный ответ.Тот, который можно использовать, не обязательно понимая полное математическое значение кватернионов.Я собираюсь предположить (учитывая тег C ++), что у вас есть что-то вроде Vector3
класса с «очевидными» функциями, такими как inner
, cross
и *=
скалярные операторы и т. Д. *
#include <cfloat>
#include <cmath>
...
void make_quat (float quat[4], const Vector3 & v2, float angle)
{
// BTW: there's no reason you can't use 'doubles' for angle, etc.
// there's not much point in applying a rotation outside of [-PI, +PI];
// as that covers the practical 2.PI range.
// any time graphics / floating point overlap, we have to think hard
// about degenerate cases that can arise quite naturally (think of
// pathological cancellation errors that are *possible* in seemingly
// benign operations like inner products - and other running sums).
Vector3 axis (v2);
float rl = sqrt(inner(axis, axis));
if (rl < FLT_EPSILON) // we'll handle this as no rotation:
{
quat[0] = 0.0, quat[1] = 0.0, quat[2] = 0.0, quat[3] = 1.0;
return; // the 'identity' unit quaternion.
}
float ca = cos(angle);
// we know a maths library is never going to yield a value outside
// of [-1.0, +1.0] right? Well, maybe we're using something else -
// like an approximating polynomial, or a faster hack that's a little
// rough 'around the edge' cases? let's *ensure* a clamped range:
ca = (ca < -1.0f) ? -1.0f : ((ca > +1.0f) ? +1.0f : ca);
// now we find cos / sin of a half-angle. we can use a faster identity
// for this, secure in the knowledge that 'sqrt' will be valid....
float cq = sqrt((1.0f + ca) / 2.0f); // cos(acos(ca) / 2.0);
float sq = sqrt((1.0f - ca) / 2.0f); // sin(acos(ca) / 2.0);
axis *= sq / rl; // i.e., scaling each element, and finally:
quat[0] = axis[0], quat[1] = axis[1], quat[2] = axis[2], quat[3] = cq;
}
Таким образом, float quat[4]
содержит единичный кватернион, который представляет ось и угол поворота, учитывая исходные аргументы (, v2, A)
.
Вот процедура умножения кватернионов.SSE / SIMD, вероятно, могут ускорить это, но сложные преобразования и освещение обычно основаны на GPU в большинстве сценариев.Если вы помните сложное умножение чисел как-то странно, quaternion умножение тем более.Умножение комплексных чисел является коммутативной операцией: a*b = b*a
.Кватернионы даже не сохраняют это свойство, то есть q*p != p*q
:
static inline void
qmul (float r[4], const float q[4], const float p[4])
{
// quaternion multiplication: r = q * p
float w0 = q[3], w1 = p[3];
float x0 = q[0], x1 = p[0];
float y0 = q[1], y1 = p[1];
float z0 = q[2], z1 = p[2];
r[3] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
r[0] = w0 * x1 + x0 * w1 + y0 * z1 - z0 * y1;
r[1] = w0 * y1 + y0 * w1 + z0 * x1 - x0 * z1;
r[2] = w0 * z1 + z0 * w1 + x0 * y1 - y0 * x1;
}
Наконец, вращая трехмерный «вектор» v
(или, если хотите, «точку» v
, чтовопрос с именем v1
(представлен в виде вектора) с использованием кватерниона: float q[4]
имеет несколько странную формулу: v' = q * v * conjugate(q)
.Кватернионы имеют конъюгаты, похожие на комплексные числа.Вот процедура:
static inline void
qrot (float v[3], const float q[4])
{
// 3D vector rotation: v = q * v * conj(q)
float r[4], p[4];
r[0] = + v[0], r[1] = + v[1], r[2] = + v[2], r[3] = +0.0;
glView__qmul(r, q, r);
p[0] = - q[0], p[1] = - q[1], p[2] = - q[2], p[3] = q[3];
glView__qmul(r, r, p);
v[0] = r[0], v[1] = r[1], v[2] = r[2];
}
Собираем все вместе.Очевидно, что вы можете использовать ключевое слово static
, где это уместно.Современные оптимизирующие компиляторы могут игнорировать подсказку inline
в зависимости от собственной эвристики генерации кода.Но давайте сейчас сосредоточимся на правильности:
Как повернуть вектор v1 вокруг другого вектора v2 на угол A?
Предполагая что-то вроде Vector3
class и (A)
в радианах, мы хотим, чтобы кватернион представлял вращение на угол (A)
вокруг оси v2
, и мы хотим применить это вращение кватерниона к v1
для результата:
float q[4]; // we want to find the unit quaternion for `v2` and `A`...
make_quat(q, v2, A);
// what about `v1`? can we access elements with `operator [] (int)` (?)
// if so, let's assume the memory: `v1[0] .. v1[2]` is contiguous.
// you can figure out how you want to store and manage your Vector3 class.
qrot(& v1[0], q);
// `v1` has been rotated by `(A)` radians about the direction vector `v2` ...
Это то, что люди хотели бы видеть расширенным на сайте бета-документации?Я не совсем ясно о его требованиях, ожидаемой строгости и т. Д.