Представление ориентации (матрицы, углы Эйлера и кватернионы)

NOTE

Заметка подразумевает, что читатель предварительно знаком с матрицами, углами Эйлера и кватернионами.

Матричная форма

Преимущества

Недостатки

Углы эйлера

Используемая конвенция Эйлера (порядок вращения): Z->X->Y

Преимущества

Недостатки

Подробнее об недостатках

Во-первых, есть проблема, что для конкретной ориентации существует множество различных троек углов Эйлера, которые можно использовать для описания этой ориентации. Это известно как aliasing и может быть существенной проблемой. Основные вопросы, такие как «представляют ли две тройки угла Эйлера одну и ту же ориентацию?» трудно ответить из-за aliasing.

Вторая и более проблемная форма aliasing происходит потому, что три угла не совсем независимы друг от друга. Например, поворот X = 135° — это то же самое, что и Y = 180°, затем X = 45°, затем Z = 180°.

Чтобы гарантировать уникальное представление для любой данной ориентации, мы должны ограничить диапазоны углов. Одной из распространенных техник является ограничение Y и Z до ±180° и ограничение X до ±90°. Это устанавливает соглашение «канонического» набора углов Эйлера.

Самый известный (и раздражающий) тип проблемы с aliasing, с которой страдают углы Эйлера, иллюстрируется этим примером: если мы вращаем Y на 45°, а затем вращаем X на 90°, это то же самое, что X на 90°, а затем Z на 45°. (Рисунок со сферой нарисовать) Фактически, как только выбираем X ±90° – в этот момент ось Z совпадает с осью Y, и начинают вращаться вокруг одной и той же вертикальной оси. Мы теряем одну степень свободы — два угла делают одно и то же, и управлять ориентацией независимо по трём осям больше невозможно. Это явление известно как Gimbal Lock. Чтобы устранить эту неоднозначность в каноническом наборе углов Эйлера, мы договариваемся: всё вращение вокруг вертикальной оси отдаётся углу Y, а Z принудительно обнуляется. Другими словами, в каноническом наборе, если X = ±90°, то Z = 0°, а Y остаётся таким, какой есть.

Операция интерполирования между двумя ориентациями A и B также является важной в задачах, например, анимаций персонажей или автоматического управления камерой.

Наивный подход чтобы проинтерполировать заключается в том, чтобы интерполировать каждый из трех углов независимо. Но это чревато некоторыми проблемами:

wrap(x)=x360x+180360Δθ=wrap(θ1θ0)θt=θ0+tΔθ\begin{align*} \text{wrap}(x) &= x - 360^\circ \left\lfloor \frac{x + 180^\circ}{360^\circ} \right\rfloor \\ \Delta\theta &= \text{wrap}(\theta_1 - \theta_0) \\ \theta_t &= \theta_0 + t\Delta\theta \end{align*}

В итоге, простые проблемы aliasing имеют обходные пути, но не gimbal lock. К сожалению, gimbal lock – это не просто мелкая неприятность, это фундаментальная проблема. Возможно, мы могли бы переформулировать наши вращения и разработать систему, которая не страдает от этих проблем? К сожалению, это невозможно. Это фундаментальное ограничение, связанное с использованием трёх чисел для описания 3D-ориентации. Любая система, которая параметризует ориентацию в трехмерном пространстве с помощью трех чисел, гарантированно будет иметь особенности в пространстве параметризации и, следовательно, будет подвержена таким проблемам, как gimbal lock.

Кватернионы

Небольшое знакомство

Кватернион — это гиперкомплексное число, которое определяет вращение объекта в пространстве. w+xi+yj+zkw + xi + yj + zk.

Мнимая часть (x,y,z)(x,y,z) представляет вектор, который определяет направление вращения. Вещественная часть (w)(w) определяет угол, на который будет совершено вращение. Его основное отличие от всем привычных углов Эйлера в том, что нам достаточно иметь один вектор, который будет определять направление вращения, чем три линейно независимых вектора, которые вращают объект в 3 подпространствах.

Рекомендую две статьи, в которых подробно рассказывается о кватернионах: Раз и Два

Теперь, когда у нас есть минимальные представления о кватернионах, давайте поймем, как вращать вектор.

Формула вращения вектора

v=qvqˉ\vec{v}' = q * \vec{v} * \bar{q} , где

Для начала, дадим понятие обратного кватерниона в ортонормированном базисе — это кватернион с противоположной по знаку мнимой частью. q=w+xi+yj+zkq = w + xi + yj + zk qˉ=wxiyjzk\bar{q} = w - xi - yj - zk

Вычисление vqˉ\vec{v} * \bar{q}

Посчитаем vqˉ\vec{v} * \bar{q} и обозначим как MM:

M=vqˉ=(0+vxi+vyj+vzk)(qwqxiqyjqzk)==vxqwi+vxqxvxqyk+vxqzj++vyqwj+vyqxk+vyqyvyqzi++vzqwkvzqxj+vzqyi+vzqz\begin{align*} & M = \vec{v} * \bar{q} = (0 + v_x i + v_y j + v_z k)(q_w - q_x i - q_y j - q_z k) = \\ & = \textcolor{red}{v_x q_w i} + \textcolor{purple}{v_x q_x} - \textcolor{blue}{v_x q_y k} + \textcolor{green}{v_x q_z j} + \\ & + \textcolor{green}{v_y q_w j} + \textcolor{blue}{v_y q_x k} + \textcolor{purple}{v_y q_y} - \textcolor{red}{v_y q_z i} + \\ & + \textcolor{blue}{v_z q_w k} - \textcolor{green}{v_z q_x j} + \textcolor{red}{v_z q_y i} + \textcolor{purple}{v_z q_z} \end{align*}

Теперь выпишем отдельные компоненты и из этого произведения соберем новый кватернион:

M=uw+uxi+uyj+uzkuw=vxqx+vyqy+vzqzuxi=(vxqwvyqz+vzqy)iuyj=(vxqz+vyqwvzqx)juzk=(vxqy+vyqx+vzqw)k\begin{align*} & M = u_w + u_x i + u_y j + u_z k \\ & u_w = \textcolor{purple}{v_x q_x + v_y q_y + v_z q_z} \\ & u_x i = \textcolor{red}{(v_x q_w - v_y q_z + v_z q_y)i} \\ & u_y j = \textcolor{green}{(v_x q_z + v_y q_w - v_z q_x)j} \\ & u_z k = \textcolor{blue}{(-v_x q_y + v_y q_x + v_z q_w)k} \end{align*}

Вычисление qMq * M

Посчитаем оставшуюся часть, т.е. qMq * M и получим искомый вектор.

Примечание: Чтобы не загромождать вычисления, приведем только мнимую (векторную) часть этого произведения. Ведь именно она характеризует искомый вектор.

v=qM=(qw+qxi+qyj+qzk)(uw+uxi+uyj+uzk)==qwuxi+qwuyj+qwuzk++qxuwi+qxuykqxuzj++qyuwjqyuxk+qyuzi++qzuwk+qzuxjqzuyi\begin{align*} & \vec{v}' = q * M = (q_w + q_x i + q_y j + q_z k)(u_w + u_x i + u_y j + u_z k) = \\ & = \textcolor{red}{q_w u_x i} + \textcolor{green}{q_w u_y j} + \textcolor{blue}{q_w u_z k} + \\ & + \textcolor{red}{q_x u_w i} + \textcolor{blue}{q_x u_y k} - \textcolor{green}{q_x u_z j} + \\ & + \textcolor{green}{q_y u_w j} - \textcolor{blue}{q_y u_x k} + \textcolor{red}{q_y u_z i} + \\ & + \textcolor{blue}{q_z u_w k} + \textcolor{green}{q_z u_x j} - \textcolor{red}{q_z u_y i} \end{align*}

Компоненты результирующего вектора

vx=qwux+qxuw+qyuzqzuyvy=qwuyqxuz+qyuw+qzuxvz=qwuz+qxuyqyux+qzuwv=(vx,vy,vz)\begin{align*} v'_x &= \textcolor{red}{q_w u_x + q_x u_w + q_y u_z - q_z u_y} \\ v'_y &= \textcolor{green}{q_w u_y - q_x u_z + q_y u_w + q_z u_x} \\ v'_z &= \textcolor{blue}{q_w u_z + q_x u_y - q_y u_x + q_z u_w} \\ v' &= (v'_x, v'_y, v'_z) \end{align*}

Реализация:

Vector3 QuanRotation(Vector3 v, Quaternion q)
{
        float u0 = v.x * q.x + v.y * q.y + v.z * q.z;
        float u1 = v.x * q.w - v.y * q.z + v.z * q.y;
        float u2 = v.x * q.z + v.y * q.w - v.z * q.x;
        float u3 = -v.x * q.y + v.y * q.x + v.z * q.w;
        Quaternion M = new Quaternion(u1, u2, u3, u0);
        
        Vector3 resultVector;
        resultVector.x = q.w * M.x + q.x * M.w + q.y * M.z - q.z * M.y;  
        resultVector.y = q.w * M.y - q.x * M.z + q.y * M.w + q.z * M.x;
        resultVector.z = q.w * M.z + q.x * M.y - q.y * M.x + q.z * M.w;
        
        return resultVector;
}

Продолжение…

Best Practices