В предыдущем уроке мы разобрались как установить камеру в любую точку 3D пространства. Следующий этап - это дать возможность пользователю управлять ей. Управление будет свободным - пользователь может двигать ее во всех направлениях. Управление камерой будет осуществляться 2 устройствами ввода: клавиатурой мы будем изменять позицию, а мышью направление обзора. Это очень похоже на то, что предлагают шутеры от первого лица. Этот урок сфокусируется на клавиатуре, а следующий - на мышке.
Мы добавим поддержку 4 клавиш в привычном виде. Вспомним, что наша камера определяется ее позицией, вектором направления и верхним вектором. Когда мы используем клавиатуру меняется только позиция. Мы не можем наклонить или повернуть камеру, а значит, что векторы вверх и направления останутся не низменными.
Для использования клавиатуры нам потребуется другой API GLUT'a: glutSpecialFunc(). Эта функция записывает нашу для вызова, если будет нажата "специальная" клавиша. В группу специальных клавиш входят: F{1..12}, стрелки и PAGE-UP / PAGE-DOWN / HOME / END / INSERT. Если вам нужны обычные кнопки (символы и цифры) используйте glutKeyboardFunc().
Функционал камеры инкапсулирован в классе камеры. Этот класс хранит параметры камеры и может их изменять основываясь на событиях движения, которые он получит. Атрибуты камеры будут перехвачены классом конвейера, а тот уже и создаст матрицу преобразований из них.
Camera.h
class Camera
{
public:
Camera();
Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
bool OnKeyboard(int Key);
const Vector3f& GetPos() const
const Vector3f& GetTarget() const
const Vector3f& GetUp() const
private:
Vector3f m_pos;
Vector3f m_target;
Vector3f m_up;
};
Это объявление класса камеры. Он хранит 3 свойства, которые характеризуют камеру - позиция, направление и верхний вектор. Так же добавлены 2 конструктора. По умолчанию просто располагает камеру в начале координат, направляет ее в сторону уменьшения Z, а верхний вектор устремляет в "небо" (0,1,0). Но есть возможность создать камеру с указанием значений атрибутов. OnKeyboard() доставляет события клавиатуры в класс. Он возвращает значение типа bool, который указывает, воспринято ли камерой событие. Если клавиша подходит (одна из указывающих направление), возвращаемое значение - true. Если нет - false. Таким способом можно создать цепь клиентов, которые будут получать события и останавливаться, когда один из них воспользуется этим событием.
Camera.cpp:42
bool Camera::OnKeyboard(int Key)
{
bool Ret = false;
switch (Key) {
case GLUT_KEY_UP:{
m_pos += (m_target * StepSize);
Ret = true;
} break;
case GLUT_KEY_DOWN:{
m_pos -= (m_target * StepSize);
Ret = true;
} break;
case GLUT_KEY_LEFT:{
Vector3f Left = m_target.Cross(m_up);
Left.Normalize();
Left *= StepSize;
m_pos += Left;
Ret = true;
} break;
case GLUT_KEY_RIGHT:{
Vector3f Right = m_up.Cross(m_target);
Right.Normalize();
Right *= StepSize;
m_pos += Right;
Ret = true;
} break;
}
return Ret;
}
Эта функция двигает камеру согласно событиям клавиатуры. GLUT определил несколько макросов, обозначающих клавиши направления и этот оператор выбора основывается на них. К сожалению, тип этих макросов просто 'int', а не enum.
Движение вперед и назад проще. Так как это движение вдоль вектора направления, нам нужно только прибавить или вычесть вектор направления из вектора позиции. Вектор направления остается без изменений. Заметим, что прежде чем прибавлять / вычитать вектор направления, мы умножаем его на константу названую 'StepSize' (размер шага). Мы делаем это для всех направлений. Размер шага позволяет удобно изменять скорость (мы можем внести этот параметр в класс). Для удобства мы можем представить размер шага в виде единицы длины векторов (т.к. мы должны быть уверены, что вектор направления и верхний вектор являются векторами единичной длины).
Перемещение в бок немного сложнее. Оно вдоль вектора, перпендикулярного плоскости, которую образуют вектор направления и вектор вверх. Плоскость делит трехмерное пространство на 2 части и у нас 2 вектора, перпендикулярных этой плоскости, и они противоположно направлены. Один мы можем назвать левым, а другой правым. Они получаются векторным произведением вектора направления и вектора вверх. В зависимости от порядка векторов, получаются соответственно левый или правый вектора. После получения нужного вектора, мы нормируем его и увеличиваем на размер шага и, наконец, прибавляем к вектору позиции (что совершит перемещение влево или вправо). И снова вектора вверх и направления остались без изменений.
Заметим, что в этой функции были использованы несколько новых операторов, такие как '+=' и '-=', которые были добавлены в класс Vector3f.
main.cpp:96
static void SpecialKeyboardCB(int Key, int x, int y){
GameCamera.OnKeyboard(Key);
}
static void InitializeGlutCallbacks(){
glutDisplayFunc(RenderSceneCB);
glutIdleFunc(RenderSceneCB);
glutSpecialFunc(SpecialKeyboardCB);
}
Здесь мы регистрируем новую функцию обратного вызова для получения специальных событий клавиатуры. Функция отправляет клавишу и позицию курсора в момент нажатия кнопки. Мы игнорируем координаты курсора и отправляем событие в нашу камеру, которая уже объявлена как глобальная переменная.
main.cpp:78
p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());
Ранее мы передавали в класс конвейера жестко заданные вектора, характеризующие поведение камеры. Теперь эти атрибуты камеры посылаются из класса камеры.