Перед тем как приступить, я попрошу вас самостоятельно перегрузить операции сложения, вычитания, умножения и деления для векторов. Движок, движок, движок... Итак, у нас на мази след. пункты:- Общая структура
- Вспомогательный класс по вычислениям
Общая структура Можно очень много спорить о том как должен быть построен движок; как будет эффективнее и на чём писать. Я предлагаю след. систему:- У нас есть профили игроков, в которых хранятся настройки управления, видео и т.д.
- Класс системный, который устанавливает связь между OpenGL, окном и нашими параметрами
- Также класс нашего игрового мира, в котором описаны объекты и осуществляется взаимосвязь между ними
Это очень общная структура. Неисключено, что понадобиться вводить ещё классы такого же общего уровня в будущем. Ну и, конечно, будет ещё тьма мелких классов, которые нам понадобятся для сухой и комфортной жизни 
Вспомогательный класс Так как мы собираемся работать с графикой, аналитической геометрией, то нам придётся часто производить различные геометрические операции, как то: поиск пересечения линии и плоскости, попадание точки в треугольник, получение нормали к плоскости и т.п. Следовательно нам надо их релизовать. Но вот вопрос: как это лучше сделать? Половина ответят - засунуть функции работы с векторами в класс векторов, а другая половина скажет: засунуть их в отдельный синглтон. Я солидарен со вторыми, хотябы потому что этот метод избавит на от бессмысленного копирования одних и тех же функций тысячи раз, при создании объектов. Итак создаём файл описания нашего вспомогательного класса aAux.cpp:
Код | //=============================================================================
#ifndef AAUX_H #define AAUX_H
//=============================================================================
#include <math.h> #include "aVector.h"
//=============================================================================
class aAux { private: aAux(); |
Нам не нужно чтобы была возможность создавать отдельный экземпляр класса, потому что он у нас является синглтоном, а значит надо как-то предостеречь вариант его создания. Что мы и делаем, записывая конструктор как private.
Код | public: inline float aaGetPi() {return pi;} //получить значение числа пи. ~3.14 inline float aaGetPid() {return pid;} |
Функции типа cos / sin оперируют углами не в градусах, а в радианах. Чтобы перевести градус в радины, нам надо умножить его на Пи и разделить на 180. Константа pid = pi/180.0f;
Код | inline float aaGetPidi() {return pidi;} |
Но иногда требуется обратное преобразование радиан в градусы, которое получается через умножение на 180 и деление на пи, pidi = 180.0f/pi;
Код | inline float aaGetMagnitude(const aVector& vec) {return sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);} |
Вообще, всем и каждому, кто мало знаком с аналитической геометрией / векторной алгеброй, я советую обзавестись рядом книг или же пошустрить инет на тему основных операций над векторами. Данная функция является одной из фундаметальных - поиск длинны вектора. Помним, что вектор - это направленный отрезок.
Код | inline float aaDotMulty(const aVector& vec, const aVector& vec2) {return (vec.x*vec2.x + vec.y*vec2.y + vec.z*vec2.z);} |
Это функция скалярного произведения двух векторов. В плане геометрического смысла, скалярное произведение - это длинна проекции первого вектора на второй. Запомните этот момент, он нам понадобится, когда будет писать функцию проекции вектора на вектор.
Код | inline float ator(float angle) {return (angle*pid);}// как я и говорил: иногда надо переводить угол из градусов в радианы. Именно этим и занимается функция inline float aaFloatAbs(float f) {return (f < 0.0 ? (-f):f);}// данная функция, как нетрудно понять, просто возвращает модуль числа. inline aVector aaGetVectorProjection(const aVector& vec, const aVector& vec2) {return (vec2* (/*тут вам надо подумать совсем немного*/) );} |
Читатель должен чётко понимать смысл всех геометрических преобразований с векторами. Допустим что вычитая один вектор из другого он получает третий, направление которого идёт от второго к первому. Я ещё раз настоятельно рекомендую обрадиться к учебникам, если вы до сих пор не знакомы с векторными операциями и что самое главное их геометрическим смыслом. Итак функция "aaGetVectorProjection(...)" - это функция которая должна вернуть проекцию первого вектора на второй. Как нам её получить? Мы пытаемся умножить вектор vec2 на число. Какое должно быть число? Подумайте.
Код | inline aVector aaVVVXYZ2pos(const aVector& i, const aVector& j, const aVector& k, const aVector& vec) {return ((i*vec.x) + (j*vec.y + k*vec.z));} |
Очень нужная функция. Давайте задумаемся о том, а что такое вообще "координаты"? x, y, z... Для того чтобы ответить на этот вопрос нам надо разобраться с тем, что такое базисы прямоугольной системы координат. Базисы - это три вектора, которые обозначают направление мировых осей . Самые первые базисы, с которыми все знакомятся: (1.0, 0.0, 0.0) - ось абсцисс, (0.0, 1.0, 0.0) - ось ординат, (0.0, 0.0, 1.0) - ось аппликат. Обозначим их i, j, k - соответственно. Возьмите в руки карандаш и лист бумаги, поставьте в любом месте точку - это будет начало отсчёта координат. Теперь проведите вектор (1.0, 0.0, 0.0) и остальные два. Что у вас получается? Три вектора, каждый из которых направлен перпендикулярно двум остальным. Теперь, запоминаем, что любая точка в трёхмерном пространстве это p = x*i + y*j + z*k. Т.е. получается, точка - это сумма базисных векторов, каждый из которых умножен на координату. А что бывает когда мы умножаем вектор, единичной длинны, на любое число? Правильно - получается вектор длинны равной этому числу. Следовательно получается, что координата - это некий коэффициент для абсциссы, который будет характеризовать её направление. А теперь, чтобы получить точку, в нашей заданной, векторами (абсциссами) i, j, k, прямоугольной системе координат нам надо умножить каждую из этих абсцисс, на каждую из координат: i - на x, j - на y, z - на k. Но ведь можно задать i, j, k не только (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)? Правильно - и тогда вы получите другую систему координат. Об этом мы ещё поговорим, когда выведем своё первое изображение на экран. Так вот, вернёмся к нашей функции "aaVVVXYZ2pos()" - что она делает, посмотрите и попытайтесь понять сами. Если не сможете - спрашивайте.
Код | inline aVector aaGetNormal(const aVector& vec, const aVector& vec2, const aVector& vec3) {return aaNormalize(aaCrossMulty((vec2 - vec), (vec3 - vec)));} |
Функция, как можно понять из названия возвращает нормаль к каким - то трём векторам. Три точки, три вектора - это минимум, с помощью которого можно конкретно задать плоскость в трёхмерном пространстве. В этой функции очень важен порядок вершин, потому что от него зависит то, в какую сторону будет направлен вектор нормали. И не только порядок но и расположение вершин. Перечислены они должны быть по часово стрелке от первой к третьей. Мы берём разницу первого вектора и второго + разницу первого и третьего - тем самым получаем два вектора, которые выходят из одной вершины. А как известно, векторное произведение дву векторов даёт третий вектор, который будет направлен перпендикулярно двум данным. Всё намного проще, если нарисовать треугольник на бумаге и слева направо - снизу вверх, по часовой стрелке, обозначить вершины vec, vec2, vec3. Теперь смотрим: "(vec2 - vec)": получится вектор, выходящий из vec в vec2; теперь "(vec3 - vec)" - и у нас вектор идущий из vec в vec3. А их векторное произведение - это нормаль, которая перпендикулярна плоскости листа бумаги и направлена от нас. Функцию "aaCrossMultiply()" мы с вами ещё не смотрели, но смысл её прост - она возвращает векторное произведение двух векторов, геометрический смысл которого - получение вектора перпендикулярного двум данным.
Код | inline bool aaVectorOnLine(const aVector& l1, const aVector& l2, const aVector& p) {/*здесь будет ваша реклама*/} |
Функция проверки лежит ли данная точка "p" на линии, заданной векторами "l1" и "l2". Напишите её сами, предварительно посмотрев описание функций "aaGetLength()" и aaCompareFloat Описание нижеследующих функций будет приведено в файле реализации aAux.cpp
Код | static aAux& aaGetInstance() { static aAux aux;
return aux; }//получаем наш синглтон
//uVector aVector aaCrossMulty(const aVector& vec, const aVector& vec2);//функция получения векторного произведения двух векторов aVector aaNormalize(const aVector& vec);//функция нормализации вектора float aaGetAngle(const aVector& v, const aVector& v2);//получение угла между векторами aVector aaRotateAroundVector(const aVector& v, const aVector& v2, float angle); //вращение вектора v, относительно вектора v2 на угол angle bool aaCompareFloat(float f1, float f2);//функция сравнения двух чисел с плавующей точкой float aaGetLength(const aVector& vec, const aVector& vec2);//получение расстояния от одной точки до другой bool aaCheckVectorIn(const aVector& p, const aVector& vec, const aVector& vec2, const aVector& vec2);//функция проверки лежит ли данная точка в заданном треугольнике aVector aaGetIntersection(const aVector& l1, const aVector& l2, const aVector& s1, const aVector& s2, const aVector& s3);//пересекает ли линия плоскость, заданную тремя точками
~aAux();
private: float pi; //наше число pi float pid; // (pi/180.0) float pidi; // (180.0/pi) };
//=============================================================================
#endif
//============================================================================= |
Остановились, отдышались... Переходим к файлу реализации aAux.cpp:
Код | //=============================================================================
#include <math.h> #include "../inc/aAux.h"
//=============================================================================
aAux::aAux() { pi = 3.1415926535f; //наши постоянные значения pid = 0.017453292f; pidi = 57.2957795f; }
//=============================================================================
aVector aAux::aaCrossMulty(const aVector& vec, const aVector& vec2) { /* Векторное произведение векторов, на выходе даёт вектор, перпендикулярный двум данным. */ aVector res;
res.x = (vec.y*vec2.z) - (vec.z*vec2.y); res.y = (vec.z*vec2.x) - (vec.x*vec2.z); res.z = (vec.x*vec2.y) - (vec.y*vec2.x);
return res; }
//=============================================================================
aVector aAux::aaNormalize(const aVector& v) { /* Нормализация вектора - это процесс получения длинны вектора и деление каждой координаты на неё. Необходимо для того, чтобы длинна вектора была не больше 1. Это необходимое условие, например, для нормалей. */ aVector vec(v); float m = aaGetMagnitude(vec);
if (aaCompareFloat(m, 0.0f)) return aVector(0.0f, 0.0f, 0.0f);
vec.x /= m; vec.y /= m; vec.z /= m;
return vec; }
//=============================================================================
float aAux::aaGetAngle(const aVector& v, const aVector& v2) { /* Получить угол между двумя векторами. Мы сначала нормализуем векторы, а затем получаем угол через арккосинус от длинны их скалярного произведения. осильте сами. */ float scal = 0.0f; //... ? return scal; }
//=============================================================================
aVector aAux::aaRotateAroundVector(const aVector& v, const aVector& v2, float angle) { /* Функция, которая поворачивает вектор v, относительно вектора v2 на угол angle. Что это значит? Чтобы разобраться, сделайте след.: нарисуйте на листе бумаги стандартные базисы. Если вы возьмёте ось абсцисс и повернёте на (-90/270) градусов относительно оси ординат, то получите ось аппликат. Пробуйте. */ aVector dx, dy, dz; aVector vec(v); aVector vec2(v2); float dot = aaDotMulty(vec, vec2); float angle2 = ator(angle);
dz.x = vec2.x * dot; dz.y = vec2.y * dot; dz.z = vec2.z * dot;
dx.x = vec.x - dz.x; dx.y = vec.y - dz.y; dx.z = vec.z - dz.z;
dy = aaCrossMulty(dx, vec2);
vec.x = dx.x*cos(angle2) + dy.x*sin(angle2) + dz.x; vec.y = dx.y*cos(angle2) + dy.y*sin(angle2) + dz.y; vec.z = dx.z*cos(angle2) + dy.z*sin(angle2) + dz.z;
return vec; }
//=============================================================================
bool aAux::aaCompareFloat(float f1, float f2) { /* Весьма важная функция: сравнение двух чисел с плавующей точкой. По причинам, которые я предлогаю назвать вам в этом топике, сам компьютер не может сравнить их достоверно. Поэтому приходится брать модуль разницы чисел и сравнивать с достаточно маленькой величиной. */ if (aaFloatAbs((f1) - (f2)) < 0.0001f) return 1;
return 0; }
//=============================================================================
float aAux::aaGetLength(const aVector& vec, const aVector& vec2) { /* Получение расстояния между точками похоже на получение длинны вектора. */ float vx = vec2.x - vec.x; float vy = vec2.y - vec.y; float vz = vec2.z - vec.z;
return sqrt(vx*vx + vy*vy + vz*vz); }
//=============================================================================
bool aAux::aaCheckVectorIn(const aVector& p, const aVector& vec1, const aVector& vec2, const aVector& vec3) { /* Проверка, находится ли данная вершина p, в треугольнике, заданном вершинами vec1, vec2, vec3. Чтобы понять процесс, нарисуйте на листе бумаги треугольник и точку (в нём, или вне его), после чего проведите от неё линии к каждой вершине треугольника. А теперь узнайте угол между каждой получившейся прямой и соседней к ней. Если их сумма ~= 360 градусов, то точка лежит внутри треугольника. */ aVector tmpVector2, tmpVector3, tmpVector4;
tmpVector2 = vec1 - p; tmpVector3 = vec2 - p; tmpVector4 = vec3 - p;
float angle = aAux::aaGetInstance().aaGetAngle(tmpVector2, tmpVector3)*aAux::aaGetInstance().aaGetPidi();
angle += aAux::aaGetInstance().aaGetAngle(tmpVector3, tmpVector4)*aAux::aaGetInstance().aaGetPidi(); angle += aAux::aaGetInstance().aaGetAngle(tmpVector4, tmpVector2)*aAux::aaGetInstance().aaGetPidi();
if ((angle >= 359.9f) && (angle <= 360.1f)) return 1;
return 0; }
//=============================================================================
aVector aAux::aaGetIntersection(const aVector& l1, const aVector& l2, const aVector& s1, const aVector& s2, const aVector& s3) { /* Проверяем: пересекает ли данная линия l1 - l2 плоскость, заданную тремя вершинами s1, s2, s3. Как это получается? Подумайте... */ aVector tmpVector = aaGetNormal(s1, s2, s3);
float r1 = aaDotMulty(tmpVector, (l1 - s1)); float r2 = aaDotMulty(tmpVector, (l2 - s1));
tmpVector = aaAdd(l1, aaMultiply((l2 - l1), -r1/(r2 - r1)));
if (aaVectorOnTriangleEdges(tmpVector, s1, s2, s3)) return tmpVector;
if ((r1*r2) < 0.0) return tmpVector;
return aVector(0.0f, 0.0f, 0.0f); }
//=============================================================================
aAux::~aAux() { //деструктор, он и в африке деструктор }
//============================================================================= |
Первых двух частей вам хватит на ближайшую недельку. Обдумайте всё, поиграйтесь с тем, что у нас поулчилось. В след. раз мы напишем класс работы с камерой и инициализируем приложение.
Да прибудет с вами великая сила |