Сегодня мы рассмотрим реализацию класса векторов. В этом классе есть почти всё, что нам понадобится при работе с векторами.

Кроме того, этот класса впоследствии войдёт в библиотеку, на основе которой мы создадим игровой движок.

Реализация класса трёхмерных векторов vector3

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

Название класса - vector3. Цифра говорит о размерности вектора. В конце текста есть ссылка, по которой можно найти код файла vector3.h. В классе представлены основные операции над векторами.

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

const float EPSILON = 0.001;
Она используется при сравнении двух векторов. Подробности ниже.

Все переменные (а это компоненты вектора) определены в области public. Я неоднократно писал, что все данные класса нужно располагать в области private. Но это не тот случай.

код на языке c++
union
{
  struct
  {
    float x,y,z;
  };
  float v[3];
};

Каждый вектор состоит из трёх компонент: x,y,z. В данном примере я использовал безымянное объединения для получения доступа к компонентам вектора, как поимённо, так и с помощью индексов массива. Для того, чтобы компоненты вектора не рассматривались как одно и то же значение, мы их помещаем в безымянную структуру. Может показаться, что данная конструкция сложна. Конечно же, это не так. Давайте рассмотрим пример использования векторов:

код на языке c++
vector3 v1;
v1.x = 1;
v1.y = 1;
v1.z = 1;

for (i = 0; i < 3; i++)
  cout << v1[i] << ", ";

Более подробные объяснения принципа работы объединений можно найти в статье "Структуры, перечисления и объединения".

Для хранения компонент векторов используются переменные типа float. Почему не double? Тип double нужно использовать когда необходима повышенная точность или при моделировании огромных пространств, как например, в ArmA 2. В наших учебных примерах такая точность не понадобится.

Конструкторы класса векторов

В классе vector3 используется три конструктора:

код на языке c++
vector3() : x(0), y(0), z(0) {}
vector3(vector3& v) : x(v.x), y(v.y), z(v.z) {}
vector3(float _x, float _y, float _z) : x(_x),y(_y),z(_z){}

Первый конструктор инициализирует компоненты вектора нулями. Второй конструктор принимает в качестве аргумента ссылку на другой вектор. Благодаря этому, значения создаваемого вектора можно инициализировать значениями другого вектора. И последний конструктор принимает три аргумента.

Присваивание векторов

В данном коде происходит присваиваение компонент одного вектора - другому. Делается это с помощью перегруженной операции =:

код на языке c++
vector3& operator= (vector3& v)
{
  x = v.x;
  y = v.y;
  z = v.z;
  return *this;
}

Сравнение двух векторов

Это, наверное, самая сложная функция в классе. Мы не можем просто сравнивать соответствующие компоненты двух векторов так как они имеют тип float, а значит может возникнуть ошибка из-за округления чисел. Мы будем сравнивать вектора с точностью до трёх знаков после десятичной запятой.

код на языке c++
bool operator== (vector3& v)
{
  if (fabs(x-v.x) < EPSILON)
    if (fabs(y-v.y) < EPSILON)
      if (fabs(z-v.z) < EPSILON)
        return true;
  return false;
}

Рассмотрим два вектора v1=[3.001, 2.3267, 1.182] и v2=[3.001, 2.2368, 1.182]. Равны ли эти векторы. Мы можем с уверенностью заявить: нет, не равны. Но компьютер не может дать однозначный ответ (особенно если речь будет идти не о трёх знаках после запятой, а большем количестве). Для того, чтобы компьютеру было легче сравнивать вектора мы вводим константу EPSILON = 0.001. Теперь мы находим модуль (абсолютное значение) разности соответствующих компонент и сравниваем получившееся значение с EPSILON. Если модуль разности меньше EPSILON, то считаем, что компоненты векторов равны.

код на языке c++
fabs(3.001 - 3.001) < EPSILON;    // 1
 fabs(2.2367 - 2.2368) < EPSILON; // 1, так  как 0.0001 < 0.001
  fabs(1.182 - 1.182) < EPSILON;  // 1
    return true; // Два вектора равны

Сумма/разность двух векторов

Рассмотрим код метода суммы двух векторов:

код на языке c++
vector3 operator+ (vector3& v)
{
  return vector3(x+v.x,y+v.y,z+v.z);
}

Для суммы используется перегруженная операция +. Разность векторов реализуется аналогично.

Умножение/деление вектора на скаляр

Рассмотрим только деление вектора на скаляр:

код на языке c++
vector3 operator/ (float& a)
{
  float b = 1.0f / a;
  return vector3 (x*b,y*b,z*b);
}

Первый оператор - деление единицы на параметр метода. Дело в том, что деление выполняется намного медленнее чем умножение. Поэтому вместо того, чтобы выполнять три операции деления: x/a,y/a,z/a - мы вводим дополнительную переменную b и выполняем одно деление и три операции умножения.

Обратите внимание, что мы не проверяем возможность деления на ноль.

Операции деления/умножения вектора на скаляр позволяют уменьшить/увеличить длину вектора.

Перегруженные операции с присваиванием

В классе переопределены четыре операции с присваиванием: сумма/разность векторов и умножение/деление вектора на скаляр. Эти операции почти полностью повторяют те, что мы рассмотрели выше.

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

Следующие операции будут использоваться очень часто (особенно скалярное произведение). Код методов очень простой. Мы уже рассматривали формулы этих операций, когда изучали векторы.

Для скалярного произведение используется перегруженная операция *:

код на языке c++
float operator* (vector3& v)
{
  return x*v.x+y*v.y+z*v.z;
}

Для векторного произведения сложно подобрать перегруженную операцию, поэтому мы будем использовать имя метода cross:

код на языке c++
vector3 cross(vector3& v)
{
  return vector3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x);
}

Величина вектора

Довольно простой метод, который находит величину (длину) вектора:

код на языке c++
float mag()
{
  return sqrt(x*x+y*y+z*z);
}

Нормализация вектора

Напоминаю, что нормализованный вектор - вектор, длина которого равна единице. Формулу нахождения нормализованного вектора мы уже рассматривали, когда изучали векторы, поэтому код оставляю без комментариев:

код на языке c++
void normalize ()
{
  float magnitude = mag();
  if (magnitude > 0)
  {
    float invertedMag = 1 / magnitude;
    x *= invertedMag;
    y *= invertedMag;
    z *= invertedMag;
  }
}

Расстояние между двумя точками

Последний метод используется для нахождения расстояния между двумя точками. Формулу мы уже рассматривали.

код на языке c++
float distance (vector3& v) { float dx = x - v.x; float dy = y - v.y; float dz = z - v.z; return sqrt(dx*dx + dy*dy + dz*dz); }

Вот в общем-то и всё.

В классе используется несколько стандартных математических функций: корень квадратный, модуль числа (абсолютное значение). Я не стал добавлять файл math.h, поэтому вы должны сделать это самостоятельно перед тем как включить файл vector3.h:

#include <math.h>
#include "vector3.h"