Данный материал взят с сайта old.shatalov.su и является его зеркалом

Создаём компьютерную игру. Создание игр на C++/DirectX

Есть вопросы?
Ошибка на сайте?
рус eng esp
Внимание! Данный сайт не обновляется. Новая версия: shatalov.su

Реализация класса векторов

Дата создания: 2009-08-09 18:24:44
Последний раз редактировалось: 2012-02-08 09:52:33

Предварительные уроки: Векторы.

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

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

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

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

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

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

const float EPSILON = 0.001;

Она используется при сравнении двух векторов. Подробности ниже.

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

union
{
  struct
  {
    float x,y,z;
  };
  float v[3];
};

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

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 используется три конструктора:

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){}

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

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

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

vector3& operator= (vector3& v)
{
  x = v.x;
  y = v.y;
  z = v.z;
  return *this;
}

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

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

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, то считаем, что компоненты векторов равны.

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; // Два вектора равны

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

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

vector3 operator+ (vector3& v)
{
  return vector3(x+v.x,y+v.y,z+v.z);
}

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

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

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

vector3 operator/ (float& a)
{
  float b = 1.0f / a;
  return vector3 (x*b,y*b,z*b);
}

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

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

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

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

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

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

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

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

float operator* (vector3& v)
{
  return x*v.x+y*v.y+z*v.z;
}

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

vector3 cross(vector3& v)
{
  return vector3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x);
}

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

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

float mag()
{
  return sqrt(x*x+y*y+z*z);
}

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

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

void normalize ()
{
  float magnitude = mag();
  if (magnitude > 0)
  {
    float invertedMag = 1 / magnitude;
    x *= invertedMag;
    y *= invertedMag;
    z *= invertedMag;
  }
}

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

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

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"

vector3.h