Как стать автором
Обновить

Векторные и матричные вычисления на C++, Шаг 1

Вступление

Дорогой читатель, прежде чем приступить следует понять что данная тема очень обширна, и не только потому что векторные и матричные вычисления достаточно сложны в понимании для простого обывателя, но и благодаря большому разнообразию компиляторов и стандартов для языка 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]

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

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.