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

, где  и  — компоненты вектора, значения проекций вектора на оси двумерных координат. И мы видим прямоугольный треугольник, где  и  — это длины катетов, а  — длина его гипотенузы. По теореме Пифагора получается, что . Значит . Вид формулы сохраняется и для векторов большей размерности, например — . Скалярное произведение векторов — это сумма произведение их компонентов: . Но так как мы знаем, что вектора — это матрицы, то тогда удобнее записать это в таком виде: . Это же произведение можно записать в другой форме: , где  — угол между векторами  и  (для двумерного случая эта формула доказывается через теорему косинусов). По этой формуле можно заключить, что скалярное произведение — это мера сонаправленности векторов. Ведь, если , то , и  — это просто произведение длин векторов. Так как  — не может быть больше 1, то это максимальное значение, которые мы можем получить, изменяя только угол . Минимальное значение  будет равно -1, и получается при , т.е. когда вектора смотрят в противоположные направления. Также заметим, что при  , а значит какие бы длины не имели вектора  и , все равно . Можно в таком случае сказать, что вектора не имеют общего направления, и называются ортогональными.
Также при помощи скалярного произведения, мы можем записать формулу длины вектора красивее: , . Возьмем два вектора:  и .
Проекцию вектора на другой вектор можно рассматривать в двух смыслах: геометрическом и алгебраическом. В геометрическом смысле проекция вектора на ось — это вектор, а в алгебраическом – число.  
Вектора — это направления, поэтому их начало лежит в начале координат. Обозначим ключевые точки:  — начало координат,  — конечная точка вектора ,  — конечная точка вектора .  В геометрическом смысле мы ищем такой , чтобы конечная точка вектора (обозначим ее как — ) была ближайшей точкой к точке , лежащей на прямой . Иначе говоря, мы хотим найти составляющую  в , т.е. такое значение , чтобы  и  Расстояние между точками  и  будет минимальным, если . Получаем прямоугольный треугольник — . Обозначим . Мы знаем, что  по определению косинуса через соотношение сторон прямоугольного треугольника
( — гипотенуза,  — прилежащий катет).
Также возьмем скалярное произведение . Отсюда следует, что . А значит .  Тут вспоминаем, что  — это искомый вектор , а  — , и получаем . Умножаем обе части на  и получаем — . Теперь мы знаем длину . Вектор  отличается от вектора  длинной, но не направлением, а значит через соотношение длин можно получить: . И мы можем вывести финальные формулы:
 и
 Хороший способ упростить работу над векторами — использовать вектора единичной длины. Возьмем вектор  и получим сонаправленный вектор  единичной длины. Для этого вектор разделим на его длину: . Эта операция называется нормализацией, а вектор — нормализованным.
Зная нормализованный вектор и длину исходного вектора, можно получить исходный вектор: .  Зная нормализованный вектор и исходный вектор, можно получить его длину: .  Хорошим преимуществом нормализованных векторов является то, что сильно упрощается формула проекции (т.к. длина равна 1, то она сокращается). Проекция вектора  на  единичной длины:

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

Сюрприз — он совпадает с нашим исходным вектором.  Теперь попробуем как-то изменить нашу фигуру — повернем ее на угол . Для этого повернем векторы  и , задающих оси координат. Поворот вектора  задается косинусом и синусом угла — . А чтобы получить вектор оси , возьмем перпендикуляр к : . Выполнив эту трансформацию, получаем новую фигуру:
   Вектора  и  являются ортонормированным базисом, потому как вектора ортогональны между собой (а значит базис ортогонален), и вектора имеют единичную длину, т.е. нормированы. Теперь мы говорим о нескольких системах координат — базовой системы координат (назовем ее мировой), и локальной для нашего объекта (которую мы поворачивали). Удобно объединить наш набор векторов в матрицу — 
Тогда .  В итоге — .  Матрица , составляющая ортонормированный базис и описывающая поворот, называется матрицей поворота.  Также матрица поворота имеет ряд полезных свойств, которые следует иметь ввиду:  Теперь попробуем повернуть наш объект два раза, первый раз на угол , второй раз на угол . Матрицу, полученную из угла , обозначим как , из угла  — . Распишем наше итоговое преобразование:
.    Обозначим , тогда . И из двух операций мы получили одну. Так как поворот — это линейное преобразование (описали ее при помощи одной матрицы), множество преобразований можно описать одной матрицей, что сильно упрощает над ними работу. Масштабировать объект достаточно просто, нужно только умножить координаты точек на коэффициент масштаба: . Если мы хотим масштабировать объект на разную величину по разным осям, то формула принимает вид: . Для удобства переведем операцию в матричный вид: . Теперь предположим, что нам нужно повернуть и масштабировать наш объект. Нужно отметить, что если сначала масштабировать, а затем повернуть, то результат будет отличаться, от того результата, где мы сначала повернули, а затем масштабировали: Сначала поворот, а затем масштабирование по осям:   Сначала масштабирование по осям, а затем поворот:   Как мы видим порядок операций играет большое значение, и его нужно обязательно учитывать.
Также здесь мы также можем объединять матрицы преобразования в одну:


 Хотя в данном случае, если , то . Тем не менее, с порядком преобразований нужно быть очень аккуратным. Их нельзя просто так менять местами. Перейдем в трехмерное пространство и рассмотрим определенное на нем векторное произведение.
Векторное произведение двух векторов в трёхмерном пространстве — вектор, ортогональный к обоим исходным векторам, длина которого равна площади параллелограмма, образованного исходными векторами. Для примера возьмем два трехмерных вектора — , . И в результате векторного произведения получим   Визуализируем данную операцию:    Здесь наши вектора ,  и . Вектора начинаются с начала координат, обозначенной точкой . Конечная точка вектора  — точка . Конечная точка  — точка . Параллелограмм из определения формируются точками , , , . Координаты точки  находим как — . В итоге имеем следующие соотношения:  Два вектора образуют плоскость, а векторное произведение позволяет получить перпендикуляр к этой плоскости. Получившиеся вектора образуют образуют правую тройку векторов. Если берем обратный вектор, то получаем второй перпендикуляр к плоскости, и тройка векторов будет уже левой. Для запоминания этой формулы удобно использовать мнемонический определитель. Пусть , и мы раскладываем определить по строке как сумму определителей миноров исходной матрицы :

 Некоторые удобные свойства данного произведения:  С тем, как формировать матрицу в двумерном пространстве мы разобрались. В трехмерном она формируется уже не двумя, а тремя ортогональными векторами — . По свойствам, описанным выше, можно вывести следующие отношения между этими векторам:  Вычислить вектора этих осей сложнее, чем в матрице поворота двумерного пространства. Для примера получения этих векторов рассмотрим алгоритм, который в трехмерных движках называется lookAt. Для этого нам понадобятся вектор направления взгляда —  и опорный вектор для оси  — . Сам алгоритм: В трехмерных редакторах и движках в интерфейсах часто используются углы Эйлера для задания поворота. Углы Эйлера более интуитивно понятны — это три числа, обозначающие три последовательных поворота вокруг трех основных осей . Однако, работать с ними не очень то просто. Если попробовать выразить итоговый вектор напрямую через эти повороты, то получим довольно объемную формулу, состоящую из синусов и косинусов наших углов. Есть еще пара проблем с этими углами. Первая проблема — это то, что сами по себе углы не задают однозначного поворота, так как результат зависит от того, в какой последовательности происходили повороты —  или  или как-то еще. Углы Эйлера — это последовательность поворотов, а как мы помним, смена порядка трансформаций меняет итоговый результат. Вторая проблема — это gimbal lock. Внутри же трехмерные движки чаще всего используют кватернионы, которых мы касаться не будем. Существуют разные способы задания поворота в трехмерном пространстве, и каждый имеет свои плюсы и минусы: Теперь рассмотрим операцию, позволяющую реализовать поворот вектора вокруг оси.  Возьмем вектор  — описывающий ось, вокруг которой нужно повернуть вектор  на угол . Результирующий вектор обозначим как . Иллюстрируем процесс:   Вектор  мы можем разложить сумму векторов: вектора, параллельный к вектору  — , и вектора, перпендикулярному к вектору к вектору  — .
.
Вектор  — это проекция вектора  на вектор . Т.к.  — нормализованный вектор, то:

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

Для того, чтобы повернуть , выведем оси  и  в плоскости, в которой будем выполнять поворот. Это должны быть два ортогональных нормализованных вектора, ортогональных к . Один ортогональный вектор у нас уже есть — , нормализуем его и обозначим как ось  — .  Теперь получим вектор оси . Это должен быть вектор, ортогональный к  и  (т.е. и к ). Получить его можно через векторное произведение: . Значит . По свойству векторного произведения  будет равно площади параллелограмма, образуемого двумя исходными векторами ( и ). Так как вектора ортогональны, то у нас будет не параллелограмм, а прямоугольник, а значит . . Значит .
Поворот двумерного вектора  на угол  можно получить через синус и косинус — . Т.к.  в координатах полученной плоскости сонаправлен с осью , то он будет равен . Этот вектор после поворота — . Отсюда можем вывести:  
 

Теперь мы можем получить наш искомый вектор:
 Мы разобрались с тем, как поворачивать вектор вокруг заданной оси на заданный угол, значит теперь мы умеем использовать поворот, заданный таким образом.  Получить вектор оси вращения и угол из вектора Родрига не составляет большого труда, а значит мы теперь умеем работать и с ним тоже.  Напоминаю, что матрица поворота представляет собой три базисных вектора , а углы Эйлера — три последовательных поворота вокруг осей , , . Значит мы можем взять единичную матрицу, как нулевой поворот , а затем последовательно поворачивать базисные вектора вокруг нужных нам осей. В результате получим матрицу поворота соответствующую углам Эйлера. Например:





Также можно отдельно вывести матрицы вращения по каждой из осей , ,  (, ,  соответственно) и получить итоговую матрицу последовательным их умножением:
 Таким же образом можно перевести вектор поворота Родрига в матрицу поворота: также поворачиваем оси матрицы поворота, полученные от единичной матрицы. Итак, с вращением объекта разобрались. Переходим к остальным трансформациям.  Все тоже самое что и двумерном пространстве, только матрица масштабирования принимает вид:  До этого момента точка начала локальных координат не смещалась в мировом пространстве. Так как точка начала координат нашего объекта — это его центр, то центр объект никуда не смещался. Реализовать это смещение просто: , где  — вектор, задающий смещение.  Теперь мы умеем масштабировать объект по осям, поворачивать его и перемещать.
Объединим все одной формулой: :  Чтобы упростить формулу, мы можем, как уже делали ранее, объединить матрицы . В итоге наше преобразование описывает матрица  и вектор . Объединение вектора  с матрицей  еще более бы упростило формулу, однако сделать в данном случае не получится, потому как сложение здесь — это не линейная операция. Тем не менее сделать это возможно, и рассмотрим этот момент уже в следующей статье. Для какого-то покажется, что статья описывает очевидные вещи, кому-то может показаться наоборот немного запутанной. Тем не менее это базовый фундамент, на котором будет строиться все остальное. Векторная алгебра — является фундаментом для многих областей, так что статья может вам оказаться полезной не только в дополненной реальности. Следующая статья будет уже более узконаправленной.