Pull to refresh

Векторные и матричные вычисления на 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]

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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.