Вступление
Дорогой читатель, прежде чем приступить следует понять что данная тема очень обширна, и не только потому что векторные и матричные вычисления достаточно сложны в понимании для простого обывателя, но и благодаря большому разнообразию компиляторов и стандартов для языка C++, поэтому следует определится с конкретной версией компилятора и стандартом языка. Для этой статьи выбраны следующие настройки:
Среда разработки Visual Studio 2019
Версия Release x64
Стандарт языка ISO C++ 14
Также не смотря на то что C++ считается "быстрым" языком в рамках этой статьи будет часть на языке Assembler, тем более что в нем есть инструменты подходящие для таких случаев, кому интересно можете прочитать про SIMD.
Цель
Цель. этой статьи является изготовление универсального, простого и оптимизированного средства для расчета векторов и матриц на ЦПУ, а также эта статья будет полезна как теоретические знания в области от векторов и матриц до основ ООП на C++ и особенностей применения языка Assemblerа. Практическое применение этим знаниям каждый найдет по своему: Расчет графики (таких технологий как Ray Casting, Ray Tracing, Ray Marching, многих других), физики (скорости тела, импульса), эти знания будут полезны как в GameDev так и в прочих проектах связанных с расчетами в любом пространстве, будь то на плоскости, в трехмерном мире или даже в четырехмерном.
Формат наименования
Для создания структур данных под вектора и матрицы можно взять за основу типы данных языка GLSL, но с некоторыми изменениями, так например вместо векторных типов именуемых просто vecn будет пять основных типов данных:
floatn
doublen
intn
shortn
longn
Где n обозначает мерность векторов от 2 до 4 (включительно), например - float4. Тоже самое будет и с матрицами:
floatnxn
doublenxn
intnxn
shortnxn
longnxn
Определение векторов
Для лаконичности и читаемости кода создадим один общий файл под названием "VecMath.h", этот файл будет единственным который надо будет подключить в программу, все остальное будет подключено в него, в этом файле будут определены функции для работы с векторами и матрицами, но сами определения векторов и матриц будут содержаться в сторонних файлах. Далее создаем файлы для определения векторов и матриц и подключаем их в выше созданный основной файл:
#pragma once
/// VECTORS INCLUDE
#include "Vec2.h"
#include "Vec3.h"
#include "Vec4.h"
/// MATRIX INCLUDE
#include "Mat2x2.h"
#include "Mat3x3.h"
#include "Mat4x4.h"
Далее мы переходим в файл Vec2.h и определяем двухмерные структуры данных, на этом этапе функционал не реализован, но уже определен, двухмерные векторы будут определяться только стандартными типами данных таких как int, float, double и т.д.:
struct int2 {
int x, y;
int2(void) : x(0), y(0) {} // int2 example;
int2(int _a) : x(_a), y(_a) {} // int2 example = a;
int2(int _x, int _y) : x(_x), y(_y) {} // int2 example = int2(x, y);
int2 operator+(const int2& oth); // (int2)example + (int2)other;
int2 operator-(const int2& oth); // (int2)example - (int2)other;
int2 operator/(const int2& oth); // (int2)example / (int2)other;
int2 operator*(const int2& oth); // (int2)example * (int2)other;
bool operator==(const int2& oth); // (int2)example == (int2)other;
bool operator>=(const int2& oth); // (int2)example >= (int2)other;
bool operator<=(const int2& oth); // (int2)example <= (int2)other;
bool operator>(const int2& oth); // (int2)example > (int2)other;
bool operator<(const int2& oth); // (int2)example < (int2)other;
void operator+=(const int2& oth); // (int2)example += (int2)other;
void operator-=(const int2& oth); // (int2)example -= (int2)other;
void operator/=(const int2& oth); // (int2)example /= (int2)other;
void operator*=(const int2& oth); // (int2)example *= (int2)other;
};
Важно для оптимизации передавать сложные типы данных не целиком а как ссылку, ведь в таком случае данные не будут лишний раз копироваться, а передаваться будет лишь ссылка на объект что будет быстрее.
Трехмерные же векторы, описывать которые необходимо в файле Vec3.h, будут определяться как и базовыми так и выше созданными типами данных, для этого в файл Vec3.h необходимо подключить файл Vec2.h, благодаря таким определениям будет возможен подобный синтаксис:
int2 a = int2(10, 100);
int2 b = a + 10;
В примерах кода показывается описание лишь одной структуры, все остальные определяются похожим образом, меняется лишь название типа данных, в остальное все по шаблону.
struct int3 {
int x, y, z;
int3(void) : x(0), y(0), z(0) {} // int3 example;
int3(int _a) : x(_a), y(_a), z(_a) {} // int3 example = a;
int3(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {} // int3 example = int3(0, 0, 0);
int3(const int2& _v) : x(_v.x), y(_v.y), z(0) {} // int3 example = int2(0, 0);
int3(const int2& _v, int _z) : x(_v.x), y(_v.y), z(_z) {} // int3 example = int3(int2(0, 0), 0);
int3(int _x, const int2& _v) : x(_x), y(_v.x), z(_v.y) {} // int3 example = int3(0, int2(0, 0));
int3 operator+(const int3& oth); // (int3)example + (int3)other;
int3 operator-(const int3& oth); // (int3)example - (int3)other;
int3 operator*(const int3& oth); // (int3)example * (int3)other;
int3 operator/(const int3& oth); // (int3)example / (int3)other;
bool operator==(const int3& oth); // (int3)example == (int3)other;
bool operator>=(const int3& oth); // (int3)example >= (int3)other;
bool operator<=(const int3& oth); // (int3)example <= (int3)other;
bool operator>(const int3& oth); // (int3)example > (int3)other;
bool operator<(const int3& oth); // (int3)example < (int3)other;
void operator+=(const int3& oth); // (int3)example += (int3)other;
void operator-=(const int3& oth); // (int3)example -= (int3)other;
void operator*=(const int3& oth); // (int3)example *= (int3)other;
void operator/=(const int3& oth); // (int3)example /= (int3)other;
};
Как видно vec3 формат отличается от vec2 пока только тем что определяться он может не только с помощью стандартных типов, но и с помощью vec2, это позволяет реализовать нечто подобное:
int3 a = int3(10, 100, 1000);
int3 b = a + int2(10, 100);
vec4 определяется подобным образом за исключением пары деталей:
struct int4 {
int x, y, z, w;
int4(void) : x(0), y(0), z(0), w(0) {}
int4(int _a) : x(_a), y(_a), z(_a), w(_a) {}
int4(int _x, int _y, int _z, int _w) : x(_x), y(_y), z(_z), w(_w) {}
int4(const int2& _v1) : x(_v1.x), y(_v1.y), z(0), w(0) {}
int4(const int2& _v1, const int2 _v2) : x(_v1.x), y(_v1.y), z(_v2.x), w(_v2.y) {}
int4(const int2& _v1, int _z, int _w) : x(_v1.x), y(_v1.y), z(_z), w(_w) {}
int4(int _x, const int2& _v1, int _w) : x(_x), y(_v1.x), z(_v1.y), w(_w) {}
int4(int _x, int _y, const int2& _v1) : x(_x), y(_y), z(_v1.x), w(_v1.y) {}
int4(const int3& _v) : x(_v.x), y(_v.y), z(_v.z), w(0) {}
int4(const int3& _v, int _w) : x(_v.x), y(_v.y), z(_v.z), w(_w) {}
int4(int _x, const int3& _v) : x(_x), y(_v.x), z(_v.y), w(_v.z) {}
int4 operator+(const int4& oth);
int4 operator-(const int4& oth);
int4 operator*(const int4& oth);
int4 operator/(const int4& oth);
bool operator==(const int4& oth);
bool operator>=(const int4& oth);
bool operator<=(const int4& oth);
bool operator>(const int4& oth);
bool operator<(const int4& oth);
void operator+=(const int4& oth);
void operator-=(const int4& oth);
void operator*=(const int4& oth);
void operator/=(const int4& oth);
};
Итоги
В рамках этой статьи были определены основные векторные типы данных и их функционал, к сожалению не все возможности языка GLSL возможно перенесnи на C++, например наименование переменных такие как:
x | r | [0] |
y | g | [1] |
z | b | [2] |
w | a | [3] |
В теории такое возможно реализовать, но работать это будет разительно медленнее чем текущее определение, в следующих статьях цикла будут реализованы матрицы и функции для работы с ними, а также свою реализацию получат уже имеющиеся вектора и работа с ними