Русский
Русский
English
Статистика
Реклама

Свой AR. Основы векторной алгебры


В настоящий момент появилось достаточно большое количество библиотек дополненной реальности с богатым функционалом (ARCore, ARKit, Vuforia). Тем не менее я решил начать свой открытый проект, попутно описывая как это работает изнутри. Если повезет, то позже получится добавить какой-то особый интересный функционал, которого нет в других библиотеках. В качестве целевых платформ пока возьмем Windows и Android. Библиотека пишется на C++, и сторонние библиотеки будут задействованы по минимуму, т.е. преимущественно не будет использовано ничего готового. Фокус в статьях будет направлен на алгоритмы и математику, которые постараюсь описать максимально доступно и подробно. В этой статье пойдет речь про основы векторной алгебры.


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


Вектора это частный случай матриц, состоящие либо из одного столбца, либо из одной строки. Когда мы говорим о векторе, обычно имеется вектор-столбец $\vec v = \begin{pmatrix}v_x \\ v_y \\ v_z \\v_w\end{pmatrix}$. Но записывать вектор как столбец неудобно, поэтому будем его транспонировать $\vec v = \begin{pmatrix}v_x & v_y & v_z & v_w\end{pmatrix}^T$.


Длина вектора.


Первое, что мы рассмотрим получение длины вектора $l = |\vec v|$, где $l$ значение длины, $\vec v$ наш вектор. Для примера возьмем двумерный вектор:

$\vec v = \begin{pmatrix}x & y\end{pmatrix}^T$, где $x$ и $y$ компоненты вектора, значения проекций вектора на оси двумерных координат. И мы видим прямоугольный треугольник, где $x$ и $y$ это длины катетов, а $l$ длина его гипотенузы. По теореме Пифагора получается, что $l = \sqrt{x^2 + y^2}$. Значит $l = |\vec v| = \sqrt{x^2 + y^2}$. Вид формулы сохраняется и для векторов большей размерности, например $l = |\vec v| = |\begin{pmatrix} x & y & z & w\end{pmatrix}^T| = \sqrt{x^2 + y^2 + z^2 + w^2}$.


Скалярное произведение.


Скалярное произведение векторов это сумма произведение их компонентов: $s = \vec a \cdot \vec b = a_x \cdot b_x + a_y \cdot b_y + a_z \cdot b_z$. Но так как мы знаем, что вектора это матрицы, то тогда удобнее записать это в таком виде: $s = \vec{a}^T \vec{b}$. Это же произведение можно записать в другой форме: $s = {\vec a}^T \vec b = |\vec a| \cdot |\vec b| \cdot \cos{\delta}$, где $\delta$ угол между векторами $\vec a$ и $\vec b$ (для двумерного случая эта формула доказывается через теорему косинусов). По этой формуле можно заключить, что скалярное произведение это мера сонаправленности векторов. Ведь, если $\delta = 0^{\circ}$, то $\cos{\delta} = 1$, и $s$ это просто произведение длин векторов. Так как $\cos{\delta}$ не может быть больше 1, то это максимальное значение, которые мы можем получить, изменяя только угол $\delta$. Минимальное значение $\cos{\delta}$ будет равно -1, и получается при $\delta = 180^{\circ}$, т.е. когда вектора смотрят в противоположные направления. Также заметим, что при $\delta = 90^{\circ}$$\cos{\delta}=0$, а значит какие бы длины не имели вектора $\vec a$ и $\vec b$, все равно $s = 0$. Можно в таком случае сказать, что вектора не имеют общего направления, и называются ортогональными.
Также при помощи скалярного произведения, мы можем записать формулу длины вектора красивее: $|\vec v| = \sqrt{\vec{v}^T \vec v}$, $|\vec v|^2 = \vec{v}^T \vec{v}$.


Проекция вектора на другой вектор.


Возьмем два вектора: $\vec a$ и $\vec b$.
Проекцию вектора на другой вектор можно рассматривать в двух смыслах: геометрическом и алгебраическом. В геометрическом смысле проекция вектора на ось это вектор, а в алгебраическом число.



Вектора это направления, поэтому их начало лежит в начале координат. Обозначим ключевые точки: $O$ начало координат, $A$ конечная точка вектора $\vec a$, $B$ конечная точка вектора $\vec b$.


В геометрическом смысле мы ищем такой $\vec c$, чтобы конечная точка вектора (обозначим ее как $C$) была ближайшей точкой к точке $B$, лежащей на прямой $OA$.


Иначе говоря, мы хотим найти составляющую $\vec b$ в $\vec a$, т.е. такое значение $t$, чтобы $\vec c = \vec a \cdot t$ и $|\vec c - \vec b| \rightarrow min$


Расстояние между точками $B$ и $C$ будет минимальным, если $\angle OCB = 90^\circ$. Получаем прямоугольный треугольник $OCB$. Обозначим $\alpha = \angle COB$. Мы знаем, что $\cos{\alpha} = \frac{|OC|}{|OB|}$ по определению косинуса через соотношение сторон прямоугольного треугольника
($OB$ гипотенуза, $OC$ прилежащий катет).
Также возьмем скалярное произведение $s = \vec a \cdot \vec b = |\vec a| \cdot |\vec b| \cdot \cos{\alpha}$. Отсюда следует, что $\cos{\alpha} = \frac {\vec a \cdot \vec b } {|\vec a| \cdot |\vec b|}$. А значит $\frac{|OC|}{|OB|} = \frac {\vec a \cdot \vec b } {|\vec a| \cdot |\vec b|}$.
Тут вспоминаем, что $OC$ это искомый вектор $\vec c$, а $OB$ $\vec b$, и получаем $\frac{|\vec c|}{|\vec b|} = \frac {\vec a \cdot \vec b } {|\vec a| \cdot |\vec b|}$. Умножаем обе части на $|\vec b|$ и получаем $|\vec c| = \frac {\vec a \cdot \vec b } {|\vec a|}$. Теперь мы знаем длину $\vec c$. Вектор $\vec c$ отличается от вектора $\vec a$ длинной, но не направлением, а значит через соотношение длин можно получить: $\vec c = \vec a \cdot \frac{|\vec c|}{|\vec a|}$. И мы можем вывести финальные формулы:
$t = \frac {\vec a \cdot \vec b } {|\vec a|^2} = \frac {{\vec a}^T \vec b}{{\vec a}^T \vec a}$ и
$\vec c = \vec a t = \vec a (\frac {{\vec a}^T \vec b}{{\vec a}^T \vec a})$


Нормализованный вектор.


Хороший способ упростить работу над векторами использовать вектора единичной длины. Возьмем вектор $\vec v$ и получим сонаправленный вектор $\vec{nv}$ единичной длины. Для этого вектор разделим на его длину: $\vec{nv} = \frac{\vec v}{|\vec v|}$. Эта операция называется нормализацией, а вектор нормализованным.
Зная нормализованный вектор и длину исходного вектора, можно получить исходный вектор: $\vec v = \vec{nv} \cdot |\vec v|$.
Зная нормализованный вектор и исходный вектор, можно получить его длину: $|\vec v| = {\vec v}^T \vec{nv}$.
Хорошим преимуществом нормализованных векторов является то, что сильно упрощается формула проекции (т.к. длина равна 1, то она сокращается). Проекция вектора $\vec b$ на $\vec a$ единичной длины:
$t = \vec a \cdot \vec b = {\vec a}^T \vec b$
$\vec c = \vec a {(\vec a}^T \vec b)$


Матрица поворота двумерного пространства.


Предположим у нас есть некая фигура:
Figure
Чтобы ее нарисовать, заданы координаты ее вершин, от которых строятся линии. Координаты заданы в виде набора векторов следующим образом $\vec v_i = \begin{pmatrix}{v_i}_x & {v_i}_y \end{pmatrix}^T$. Наша координатная сетка задана двумя осями единичными ортогональными (перпендикулярными) векторами. В двумерном пространстве можно получить два перпендикулярных вектора к другому вектору такой же длины следующим образом: $\perp \vec v = \begin{pmatrix}\mp v_y & \pm v_x \end{pmatrix}^T$ левый и правый перпендикуляры. Берем вектор, задающим ось $X$ $\vec{aX}=\begin{pmatrix} 1 & 0 \end{pmatrix}^T$ и ось $Y$ левый к нему перпендикуляр $\vec{aY}=\begin{pmatrix} 0 & 1 \end{pmatrix}^T$.
Выведем новый вектор, получаемый из наших базисный векторов:
$\vec{v'} = \vec{aX}^T \cdot v_x + \vec{aY}^T \cdot v_y = \begin{pmatrix} 1 & 0 \end{pmatrix}^T \cdot v_x + \begin{pmatrix} 0 & 1 \end{pmatrix}^T \cdot v_y = \begin{pmatrix} v_x & v_y \end{pmatrix}^T = \vec v$
Сюрприз он совпадает с нашим исходным вектором.
Теперь попробуем как-то изменить нашу фигуру повернем ее на угол $\alpha$. Для этого повернем векторы $\vec{aX}$ и $\vec{aY}$, задающих оси координат. Поворот вектора $\begin{pmatrix} 1 & 0 \end{pmatrix}^T$ задается косинусом и синусом угла $\vec{aX}=rotate(\begin{pmatrix} 0 & 1 \end{pmatrix}^T, \alpha) = \begin{pmatrix} \cos{\alpha} & \sin{\alpha} \end{pmatrix}^T$. А чтобы получить вектор оси $Y$, возьмем перпендикуляр к $\vec{aX}$: $\vec{aY}=\perp \vec{aX}=\begin{pmatrix} -\sin{\alpha} & \cos{\alpha} \end{pmatrix}^T$. Выполнив эту трансформацию, получаем новую фигуру:
$\vec{v_i'} = \vec{aX} \cdot {v_i}_x + \vec{aY} \cdot {v_i}_y$


$<!-- math>$inline$\alpha=20^\circ$inline$</math -->$


Вектора $\vec{aX}$ и $\vec{aY}$ являются ортонормированным базисом, потому как вектора ортогональны между собой (а значит базис ортогонален), и вектора имеют единичную длину, т.е. нормированы.


Теперь мы говорим о нескольких системах координат базовой системы координат (назовем ее мировой), и локальной для нашего объекта (которую мы поворачивали). Удобно объединить наш набор векторов в матрицу $R = \begin{pmatrix} \vec{aX} & \vec{aY} \end{pmatrix} = \begin{pmatrix} \cos{\alpha} & -\sin{\alpha} \\ \sin{\alpha} & \cos{\alpha} \end{pmatrix}$
Тогда $\vec{v_i'} = \vec{aX} \cdot {v_i}_x + \vec{aY} \cdot {v_i}_y = \begin{pmatrix} \vec{aX} & \vec{aY} \end{pmatrix} \vec{v_i} = R \cdot \vec{v_i}$.
В итоге $\vec{v_i'} = R \cdot \vec{v_i}$.
Матрица $R$, составляющая ортонормированный базис и описывающая поворот, называется матрицей поворота.
Также матрица поворота имеет ряд полезных свойств, которые следует иметь ввиду:


  • При $R = I$, где $I$ единичная матрица, матрица соответствует нулевому повороту (угол $\alpha = 0$), и в таком случае локальные оси совпадают с мировыми. Как рассматривали выше, матрица никак не меняет исходный вектор.
  • $|R|=1$ определитель матрицы равен 1, если у нас, как обычно бывает, правая тройка векторов. $|R| = -1$, если тройка векторов левая.
  • $R^T = R^{-1}$.
  • $R^T R = R^{-1} R = I$.
    $R^T R = \begin{pmatrix}\vec{aX} & \vec{aY}\end{pmatrix}^T \begin{pmatrix}\vec{aX} & \vec{aY}\end{pmatrix} = \begin{pmatrix}\vec{aX}^T\vec{aX} & \vec{aX}^T\vec{aY} \\ \vec{aY}^T\vec{aX} & \vec{aY}^T\vec{aY}\end{pmatrix} = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}$.
  • $\vec{v'} = R \vec v \Rightarrow |\vec{v'}|=|\vec v|$, поворот не меняет длины вектора.
  • зная $\vec{v'}$ и $R$, можем получить исходный вектор $\vec v$ $\vec v = R^{-1} \vec{v'} = R^T \vec{v'}$. Т.е. умножая вектор на матрицу поворота мы выполняем преобразование координат вектора из локальной системы координат объекта в мировую, но также мы можем поступать и наоборот преобразовывать мировые координаты в локальную систему координат объекта, умножая на обратную матрицу поворота.

Теперь попробуем повернуть наш объект два раза, первый раз на угол $\alpha$, второй раз на угол $\beta$. Матрицу, полученную из угла $\alpha$, обозначим как $R_a$, из угла $\beta$ $R_b$. Распишем наше итоговое преобразование:
$\vec{{v'}_i} = R_b R_a \vec{v_i}$.


$<!-- math>$inline$\alpha=20^\circ, \beta=10^\circ$inline$</math -->$


Обозначим $R_c = R_b R_a$, тогда $\vec{{v'}_i} = R_c \vec{v_i}$. И из двух операций мы получили одну. Так как поворот это линейное преобразование (описали ее при помощи одной матрицы), множество преобразований можно описать одной матрицей, что сильно упрощает над ними работу.


Масштабирование в двумерном пространстве.


Масштабировать объект достаточно просто, нужно только умножить координаты точек на коэффициент масштаба: $\vec{v_i'} = s \cdot \vec{v_i}$. Если мы хотим масштабировать объект на разную величину по разным осям, то формула принимает вид: $\vec{v_i'} = \begin{pmatrix} s_x \cdot {v_i}_x & s_y \cdot {v_i}_y \end{pmatrix}^T$. Для удобства переведем операцию в матричный вид: $S = \begin{pmatrix} s_x & 0 \\ 0 & s_y \end{pmatrix}, \vec{v_i'} = S \cdot \vec{v_i}$.


Теперь предположим, что нам нужно повернуть и масштабировать наш объект. Нужно отметить, что если сначала масштабировать, а затем повернуть, то результат будет отличаться, от того результата, где мы сначала повернули, а затем масштабировали:


Сначала поворот, а затем масштабирование по осям:
$<!-- math>$inline$\alpha=20^\circ, s_x=1.5, s_y=0.5$inline$</math -->$


Сначала масштабирование по осям, а затем поворот:
$<!-- math>$inline$\alpha=20^\circ, s_x=1.5, s_y=0.5$inline$</math -->$


Как мы видим порядок операций играет большое значение, и его нужно обязательно учитывать.
Также здесь мы также можем объединять матрицы преобразования в одну:
$\vec{{v'}_i} = S R \vec{v_i}, \space T_a = S R \space \Rightarrow \space \vec{{v'}_i} = T_a \vec{v_i}$
$\vec{{v'}_i} = R S \vec{v_i}, \space T_b = R S \space \Rightarrow \space \vec{{v'}_i} = T_b \vec{v_i}$
$T_a \neq T_b!$


Хотя в данном случае, если $s_x = s_y$, то $T_a = T_b$. Тем не менее, с порядком преобразований нужно быть очень аккуратным. Их нельзя просто так менять местами.


Векторное произведение векторов.


Перейдем в трехмерное пространство и рассмотрим определенное на нем векторное произведение.
Векторное произведение двух векторов в трёхмерном пространстве вектор, ортогональный к обоим исходным векторам, длина которого равна площади параллелограмма, образованного исходными векторами.


Для примера возьмем два трехмерных вектора $\vec a$, $\vec b$. И в результате векторного произведения получим $\vec c = \vec a \times \vec b = \begin{pmatrix}a_y \cdot b_z - a_z \cdot b_y & a_z \cdot b_x - a_x \cdot b_z & a_x \cdot b_y - a_y \cdot b_x \end{pmatrix}^T$


Визуализируем данную операцию:

Здесь наши вектора $\vec a$, $\vec b$ и $\vec c$. Вектора начинаются с начала координат, обозначенной точкой $O$. Конечная точка вектора $\vec a$ точка $A$. Конечная точка $\vec b$ точка $B$. Параллелограмм из определения формируются точками $O$, $A$, $B$, $D$. Координаты точки $D$ находим как $D = \vec a + \vec b$. В итоге имеем следующие соотношения:


  • $|\vec c| = S(OABD)$, где $S$ площадь,
  • $\vec{a}^T \vec c = 0$,
  • $\vec{b}^T \vec c = 0$.

Два вектора образуют плоскость, а векторное произведение позволяет получить перпендикуляр к этой плоскости. Получившиеся вектора образуют образуют правую тройку векторов. Если берем обратный вектор, то получаем второй перпендикуляр к плоскости, и тройка векторов будет уже левой.


Для запоминания этой формулы удобно использовать мнемонический определитель. Пусть $i = \begin{pmatrix} 1 & 0 & 0 \end{pmatrix}^T, \space j = \begin{pmatrix} 0 & 1 & 0 \end{pmatrix}^T, \space k = \begin{pmatrix} 0 & 0 & 1 \end{pmatrix}^T$, и мы раскладываем определить по строке как сумму определителей миноров исходной матрицы $i, j, k$:
$\vec c = \vec a \times \vec b = \begin{vmatrix} (i, j, k)^T & \vec a & \vec b \end{vmatrix} = \begin{vmatrix} i & a_x & b_x \\ j & a_y & b_y \\ k & a_z & b_z \end{vmatrix} = i \cdot \begin{vmatrix} a_y & b_y \\ a_z & b_z \end{vmatrix} - j \cdot \begin{vmatrix} a_x & b_x \\ a_z & b_z \end{vmatrix} + k \cdot \begin{vmatrix} a_x & b_x \\ a_y & b_y \end{vmatrix} \space \Rightarrow$
$\vec c = \begin{pmatrix}a_y \cdot b_z - a_z \cdot b_y & a_z \cdot b_x - a_x \cdot b_z & a_x \cdot b_y - a_y \cdot b_x \end{pmatrix}^T$


Некоторые удобные свойства данного произведения:


  • Если два вектора ортогональны и нормализованы, то вектор также будет иметь единичную длину. Параллелограмм, который образуется двумя исходными векторами, станет квадратом с длинной сторон равной единице. Т.е. площадь равна единице, отсюда длина выходного вектора единица.
  • $\vec a \times \vec b = - \vec b \times \vec a$

Матрица поворота трехмерного пространства.


С тем, как формировать матрицу в двумерном пространстве мы разобрались. В трехмерном она формируется уже не двумя, а тремя ортогональными векторами $R = \begin{pmatrix} \vec{aX} & \vec{aY} & \vec{aZ} \end{pmatrix}$. По свойствам, описанным выше, можно вывести следующие отношения между этими векторам:


  • $\vec{aZ} = \vec{aX} \times \vec{aY}$
  • $\vec{aX} = \vec{aY} \times \vec{aZ}$
  • $\vec{aY} = \vec{aZ} \times \vec{aX}$

Вычислить вектора этих осей сложнее, чем в матрице поворота двумерного пространства. Для примера получения этих векторов рассмотрим алгоритм, который в трехмерных движках называется lookAt. Для этого нам понадобятся вектор направления взгляда $\vec z$ и опорный вектор для оси $Y$ $\vec y$. Сам алгоритм:


  1. Обычно направление камеры совпадает с осью $Z$. Поэтому нормализуем $\vec z$ и получаем ось $Z$ $\vec aZ = \frac{\vec z}{|\vec z|}$.
  2. Получаем вектор оси $X$ $\vec{aX} = \frac{\vec{y} \times \vec{aZ}}{|\vec{y} \times \vec{aZ}|}$. В итоге у нас есть два нормализованных ортогональных вектора $\vec{aX}$ и $\vec{aZ}$, описывающих оси $X$ и $Z$, при этом ось $Z$ сонаправлена с входным вектором $\vec z$, а ось $X$ перпендикулярна к входному опорному вектору $\vec y$.
  3. Получаем вектор оси $Y$ из полученных $\vec{aX}$ и $\vec{aZ}$ $\vec{aY} = \vec{aZ} \times \vec{aX}$.
  4. В итоге $R = \begin{pmatrix} \vec{aX} & \vec{aY} & \vec{aZ} \end{pmatrix}$

В трехмерных редакторах и движках в интерфейсах часто используются углы Эйлера для задания поворота. Углы Эйлера более интуитивно понятны это три числа, обозначающие три последовательных поворота вокруг трех основных осей $X, Y, Z$. Однако, работать с ними не очень то просто. Если попробовать выразить итоговый вектор напрямую через эти повороты, то получим довольно объемную формулу, состоящую из синусов и косинусов наших углов. Есть еще пара проблем с этими углами. Первая проблема это то, что сами по себе углы не задают однозначного поворота, так как результат зависит от того, в какой последовательности происходили повороты $X \rightarrow Y \rightarrow Z$ или $Z \rightarrow X \rightarrow Y$ или как-то еще. Углы Эйлера это последовательность поворотов, а как мы помним, смена порядка трансформаций меняет итоговый результат. Вторая проблема это gimbal lock.


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


Существуют разные способы задания поворота в трехмерном пространстве, и каждый имеет свои плюсы и минусы:


  • Матрица поворота. С ней просто работать (т.к. это просто матрицы). Но есть логическая избыточность данных все элементы матрицы связаны определенными условиями, так как количество элементов больше степеней свободы (12 элементов против трех степеней). Т.е. мы не можем взять матрицу и наполнить ее случайными числами, так при несоблюдении условий матрица просто не будет являться матрицей поворота.
  • Углы Эйлера. Они интуитивно понятны, но работать с ними сложно.
  • Вектор оси вращения и угол порота вокруг нее. Любой возможный поворот можно описать таким образом. Поворота вектора вокруг заданной оси рассмотрим ниже.
  • Вектор поворота Родрига. Это трехмерный вектор, где нормализованный вектор представляет собой ось вращения, а длина вектора угол поворота. Этот способ задания поворота похож на предыдущий способ, но количество элементов здесь равно числу степеней свободы, и элементы не связаны между собой жесткими ограничениями. И мы можем взять трехмерный вектор с абсолютно случайными числами, и любой полученный вектор будет задавать какое-то возможное вращение.

Поворот вектора вокруг заданной оси.


Теперь рассмотрим операцию, позволяющую реализовать поворот вектора вокруг оси.
Возьмем вектор $\vec n$ описывающий ось, вокруг которой нужно повернуть вектор $\vec v$ на угол $\alpha$. Результирующий вектор обозначим как $\vec{v'} = rotate(\vec n, \alpha, \vec v)$. Иллюстрируем процесс:



Вектор $\vec n$ мы можем разложить сумму векторов: вектора, параллельный к вектору $\vec n$ $\vec{v_\parallel}$, и вектора, перпендикулярному к вектору к вектору $\vec n$ $\vec{v_{\perp}}$.
$\vec v = \vec{v_{\parallel}} + \vec{v_{\perp}}$.
Вектор $\vec{v_\parallel}$ это проекция вектора $\vec v$ на вектор $\vec n$. Т.к. $\vec n$ нормализованный вектор, то:
$\vec{v_\parallel} = \vec{n} (\vec{n}^T \vec{v})$
Та часть $\vec v$, которая принадлежит оси вращения ($\vec{v_{\parallel}}$) не измениться во время вращения. Повернуть нам нужно только $\vec{v_{\perp}}$ в плоскости перпендикулярной к $\vec n$ на угол $\alpha$, Обозначим этот вектор как $\vec{v_{\perp rot}}$. Тогда наш искомый вектор $\vec{v'} = \vec{v_\parallel} + \vec{v_{\perp rot}}$.
Вектор $\vec{v_{\perp}}$ можем найти следующим образом:
$\vec{v_{\perp}} = \vec v - \vec{v_\parallel}$
Для того, чтобы повернуть $\vec{v_{\perp}}$, выведем оси $X$ и $Y$ в плоскости, в которой будем выполнять поворот. Это должны быть два ортогональных нормализованных вектора, ортогональных к $\vec n$. Один ортогональный вектор у нас уже есть $\vec{v_{\perp}}$, нормализуем его и обозначим как ось $X$ $\vec{aX} = \frac{\vec{v_{\perp}}}{|\vec{v_{\perp}}|}$.
Теперь получим вектор оси $Y$. Это должен быть вектор, ортогональный к $\vec n$ и $\vec{aX}$ (т.е. и к $\vec{v_{\perp}}$). Получить его можно через векторное произведение: $\vec{d_y} = \vec n \times \vec{v_{\perp}}$. Значит $\vec{aY} = \frac{\vec{d_y}}{|\vec{d_y}|}$. По свойству векторного произведения $|\vec{d_y}|$ будет равно площади параллелограмма, образуемого двумя исходными векторами ($\vec n$ и $\vec{aX}$). Так как вектора ортогональны, то у нас будет не параллелограмм, а прямоугольник, а значит $|\vec{d_y}| = |\vec{n}| \cdot |\vec{v_{\perp}}|$. $|\vec{n}| = 1 \space \Rightarrow \space |\vec{d_y}| = |\vec{v_{\perp}}|$. Значит $\vec{aY} = \frac{\vec n \times \vec{v_{\perp}}}{|\vec{v_{\perp}}|}$.
Поворот двумерного вектора $\vec v = \begin{pmatrix} 1 & 0 \end{pmatrix}^T$ на угол $\alpha$ можно получить через синус и косинус $rotate2D(\vec v, \alpha) = \begin{pmatrix} \cos{\alpha} & \sin{\alpha} \end{pmatrix}^T$. Т.к. $\vec{v_{\perp}}$ в координатах полученной плоскости сонаправлен с осью $X$, то он будет равен $\begin{pmatrix} |\vec{v_{\perp}}| & 0 \end{pmatrix}^T = \begin{pmatrix} 1 & 0 \end{pmatrix}^T \cdot |\vec{v_{\perp}}|$. Этот вектор после поворота $rotate2D(\begin{pmatrix} 0 & 1 \end{pmatrix}^T, \alpha) \cdot |\vec{v_{\perp}}| = \begin{pmatrix} \cos{\alpha} & \sin{\alpha} \end{pmatrix}^T \cdot |\vec{v_{\perp}}|$. Отсюда можем вывести: $\vec{v_{\perp rot}} = (\vec{aX} \cdot \cos{\alpha} + \vec{aY} \cdot \sin{\alpha}) \cdot |\vec{v_{\perp}}|$$\space \Rightarrow \space$
$\vec{v_{\perp rot}} = (\frac{\vec{v_{\perp}}}{|\vec{v_{\perp}}|} \cdot \cos{\alpha} + \frac{\vec n \times \vec{v_{\perp}}}{|\vec{v_{\perp}}|} \cdot \sin{\alpha}) \cdot |\vec{v_{\perp}}|$$\space \Rightarrow \space$
$\vec{v_{\perp rot}} = \vec{v_{\perp}} \cdot \cos{\alpha} + (\vec n \times \vec{v_{\perp}}) \cdot \sin{\alpha}$
Теперь мы можем получить наш искомый вектор:
$\vec{v'} = \vec{v_{\parallel}} + \vec{v_{\perp rot}} = \vec{v_{\parallel}} + \vec{v_{\perp}} \cdot \cos{\alpha} + (\vec n \times \vec{v_{\perp}}) \cdot \sin{\alpha}$


Мы разобрались с тем, как поворачивать вектор вокруг заданной оси на заданный угол, значит теперь мы умеем использовать поворот, заданный таким образом.
Получить вектор оси вращения и угол из вектора Родрига не составляет большого труда, а значит мы теперь умеем работать и с ним тоже.
Напоминаю, что матрица поворота представляет собой три базисных вектора $R = \begin{pmatrix} \vec{aX} & \vec{aY} & \vec{aZ} \end{pmatrix}$, а углы Эйлера три последовательных поворота вокруг осей $X$, $Y$, $Z$. Значит мы можем взять единичную матрицу, как нулевой поворот $R_0 = I$, а затем последовательно поворачивать базисные вектора вокруг нужных нам осей. В результате получим матрицу поворота соответствующую углам Эйлера. Например:
$\vec{aX'} = rotate((0, 0, 1)^T, angle_z, rotate((0, 1, 0)^T, angle_y, rotate((1, 0, 0)^T, angle_x, \vec{aX})))$
$\vec{aY'} = rotate((0, 0, 1)^T, angle_z, rotate((0, 1, 0)^T, angle_y, rotate((1, 0, 0)^T, angle_x, \vec{aY})))$
$\vec{aZ'} = rotate((0, 0, 1)^T, angle_z, rotate((0, 1, 0)^T, angle_y, rotate((1, 0, 0)^T, angle_x, \vec{aZ})))$
$R = \begin{pmatrix} \vec{aX'} & \vec{aY'} & \vec{aZ'} \end{pmatrix}$
$rotateEuler(angle_x, angle_y, angle_z, v) = R \cdot \vec v$
Также можно отдельно вывести матрицы вращения по каждой из осей $X$, $Y$, $Z$ ($R_x$, $R_Y$, $R_z$ соответственно) и получить итоговую матрицу последовательным их умножением:
$R = R_x \cdot R_y \cdot R_z$


Таким же образом можно перевести вектор поворота Родрига в матрицу поворота: также поворачиваем оси матрицы поворота, полученные от единичной матрицы.


Итак, с вращением объекта разобрались. Переходим к остальным трансформациям.


Масштабирование в трехмерном пространстве.


Все тоже самое что и двумерном пространстве, только матрица масштабирования принимает вид: $S = \begin{pmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & s_z \end{pmatrix}$


Перемещение объекта.


До этого момента точка начала локальных координат не смещалась в мировом пространстве. Так как точка начала координат нашего объекта это его центр, то центр объект никуда не смещался. Реализовать это смещение просто: $\vec{v'_i} = \vec{v_i} + \vec t$, где $\vec t$ вектор, задающий смещение.
Теперь мы умеем масштабировать объект по осям, поворачивать его и перемещать.
Объединим все одной формулой: $\vec{v'_i} = R \cdot S \cdot \vec{v_i} + \vec t$:


s_x = 0.5, \space s_y = 0.5, \space $<!-- math>$inline$\alpha={30^{\circ}}, \space \vec t = \begin{pmatrix} 6 & 4 \end{pmatrix}^T$inline$</math -->$


Чтобы упростить формулу, мы можем, как уже делали ранее, объединить матрицы $T = R \cdot S \space \Rightarrow \space \vec{v'_i} = T \cdot \vec{v_i} + \vec t$. В итоге наше преобразование описывает матрица $T$ и вектор $\vec t$. Объединение вектора $\vec t$ с матрицей $T$ еще более бы упростило формулу, однако сделать в данном случае не получится, потому как сложение здесь это не линейная операция. Тем не менее сделать это возможно, и рассмотрим этот момент уже в следующей статье.


Заключение.


Для какого-то покажется, что статья описывает очевидные вещи, кому-то может показаться наоборот немного запутанной. Тем не менее это базовый фундамент, на котором будет строиться все остальное. Векторная алгебра является фундаментом для многих областей, так что статья может вам оказаться полезной не только в дополненной реальности. Следующая статья будет уже более узконаправленной.

Источник: habr.com
К списку статей
Опубликовано: 22.03.2021 18:15:22
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Алгоритмы

Математика

Разработка под ar и vr

Ar и vr

Ar

Vector math

Векторная алгебра

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru