Третья часть уроков о камере. Теперь мы научимся направлять взгляд мышью!
Файл main.h
В этом уроке мы добавили функцию SetViewByMouse() в класс камеры. Это позволит управлять взглядом с помощью мыши, без чего сейчас не обойдется ни один 3д-шутер.
Класс CVector3 тоже расширен. Теперь у нас есть перезагруженный оператор!
В класс камеры мы добавили функцию, устанавливающую взгляд по позиции мыши.
// и операторы * и / для деления и умножения.class CVector3 {public:
// Дефолтный конструктор
CVector3() {}
// Конструктор, инициализирующий данные
CVector3(float X, float Y, float Z)
{
x = X; y = Y; z = Z;
}
// Перегружаем оператор +, чтобы прибавлять друг к другу векторы
CVector3 operator+(CVector3 vVector)
{
// Возвращаем +добавленный вектор
return CVector3(vVector.x + x, vVector.y + y, vVector.z + z);
}
// перегружаем оператор —
CVector3 operator—(CVector3 vVector)
{
return CVector3(x—vVector.x, y—vVector.y, z—vVector.z);
}
// Перегружаем оператор *
CVector3 operator*(float num)
{
return CVector3(x*num, y*num, z*num);
}
// Перегружаем оператор /
CVector3 operator/(float num)
{
return CVector3(x/num,y/num,z/num);
}
float x, y, z; // Просто float для X,Y,Z
};
// В обьявление класса CCamera добавьте новую функцию:
class CCamera {
public:
….
….
….
// Функция, устанавливающая взгляд по позиции мыши
void SetViewByMouse();
};
Файл init.cpp
Единственное, что нужно изменить — спрятать курсор.
// в ней найдите строку
ShowCursor(TRUE);// И замените TRUE на FALSE.
// Должно получиться:
ShowCursor(FALSE); // Скрываем курсор
Файл camera.cpp
В нём будет немало изменений: мы добавим математический функционал.
// обьявления векторов в классе камеры.
// Первое — в конструкторе класса камеры, теперь там должно быть:CVector3 vZero = CVector3(0.0, 0.0, 0.0); //Инициализируем вектор нашей позиции
// в нулевые координаты
CVector3 vView = CVector3(0.0, 1.0, 0.5); //Иниц. вектор взгляда
CVector3 vUp = CVector3(0.0, 0.0, 1.0); //Вектор верт.// Второе место — в начале функции PositionCamera(). Замените обьявление векторов на:
CVector3 vPosition = CVector3(positionX, positionY, positionZ);
CVector3 vView = CVector3(viewX, viewY, viewZ);
CVector3 vUpVector = CVector3(upVectorX, upVectorY, upVectorZ);
// И изменим функцию MoveCamera(), т.к. мы добавили операторы классу вектора:
void CCamera::MoveCamera(float speed)
{
CVector3 vVector = m_vView — m_vPosition;
m_vPosition.x += vVector.x * speed;
m_vPosition.z += vVector.z * speed;
m_vView.x += vVector.x * speed;
m_vView.z += vVector.z * speed;
}
// Теперь напишем несколько новых функций:
/////////////////////////////////////// CROSS \\\\\\\\\\\\\\\\\\\\\*
/////
///// Возвращает перпендикулярный вектор от 2х переданных векторов.
///// 2 любых пересекающихся вектора образуют плоскость, от котороый мы
///// и ищем перпендикуляр.
/////
/////////////////////////////////////// CROSS \\\\\\\\\\\\\\\\\\\\\*
CVector3 Cross(CVector3 vVector1, CVector3 vVector2)
{
CVector3 vNormal;
// Если у нас есть 2 вектора (вектор взгляда и вертикальный вектор),
// у нас есть плоскость, от которой мы можем вычислить угол в 90 градусов.
// Рассчет cross’a прост, но его сложно запомнить с первого раза.
// Значение X для вектора — (V1.y * V2.z) — (V1.z * V2.y)
vNormal.x = ((vVector1.y * vVector2.z) — (vVector1.z * vVector2.y));
// Значение Y — (V1.z * V2.x) — (V1.x * V2.z)
vNormal.y = ((vVector1.z * vVector2.x) — (vVector1.x * vVector2.z));
// Значение Z: (V1.x * V2.y) — (V1.y * V2.x)
vNormal.z = ((vVector1.x * vVector2.y) — (vVector1.y * vVector2.x));
// *ВАЖНО* Вы не можете менять этот порядок, иначе ничего не будет работать.
// Должно быть именно так, как здесь. Просто запомните, если вы ищите Х, вы не
// используете значение X двух векторов, и то же самое для Y и Z. Заметьте,
// вы рассчитываете значение из двух других осей, и никогда из той же самой.
// Итак, зачем всё это? Нам нужно найти ось, вокруг которой вращаться. Вращение камеры
// влево и вправо простое — вертикальная ось всегда (0,1,0).
// Вращение камеры вверх и вниз отличается, так как оно происходит вне
// глобальных осей. Достаньте себе книгу по линейной алгебре, если у вас
// её ещё нет, она вам пригодится.
// вернем результат.
return vNormal;
}
/////////////////////////////////////// MAGNITUDE \\\\\\\\\\\\\\\\\\\\\*
/////
///// Возвращает величину вектора
/////
/////////////////////////////////////// MAGNITUDE \\\\\\\\\\\\\\\\\\\\\*
float Magnitude(CVector3 vNormal)
{
// Это даст нам величину нашей нормали,
// т.е. длину вектора. Мы используем эту информацию для нормализации
// вектора. Вот формула: magnitude = sqrt(V.x^2 + V.y^2 + V.z^2) где V — вектор.
return (float)sqrt( (vNormal.x * vNormal.x) +
(vNormal.y * vNormal.y) +
(vNormal.z * vNormal.z) );
}
/////////////////////////////////////// NORMALIZE \\\\\\\\\\\\\\\\\\\\\*
/////
///// Возвращает нормализированный вектор, длинна которого==1,
///// это делает все рассчеты проще.
/////
/////////////////////////////////////// NORMALIZE \\\\\\\\\\\\\\\\\\\\\*
CVector3 Normalize(CVector3 vVector)
{
// Вы спросите, для чего эта ф-я? Мы должны убедиться, что наш вектор нормализирован.
// Вектор нормализирован — значит, его длинна равна 1. Например,
// вектор (2,0,0) после нормализации будет (1,0,0).
// Вычислим величину нормали
float magnitude = Magnitude(vVector);
// Теперь у нас есть величина, и мы можем разделить наш вектор на его величину.
// Это сделает длинну вектора равной единице, так с ним будет легче работать.
vVector = vVector / magnitude;
return vVector;
}
// Ну и наконец функция SetViewByMouse().
///////////////////////////////// SET VIEW BY MOUSE \\\\\\\\\\\\\\\\*
/////
///// Добавим ф-ю управления взглядом с пом. мышки
/////
///////////////////////////////// SET VIEW BY MOUSE \\\\\\\\\\\\\\\\*
void CCamera::SetViewByMouse()
{
POINT mousePos; // Это структура, хранящяя X и Y позиции мыши
// Оператор » >> 1″ — то же самое, что деление на 2, но о**енно быстрее 😉
int middleX = SCREEN_WIDTH >> 1; // Вычисляем половину ширины
int middleY = SCREEN_HEIGHT >> 1; // И половину высоты экрана
float angleY = 0.0f; // Направление взгляда вверх/вниз
float angleZ = 0.0f; // Значение, необходимое для вращения влево-вправо (по оси Y)
static float currentRotX = 0.0f;
// Получаем текущие коорд. мыши
GetCursorPos(&mousePos);
// Если курсор остался в том же положении, мы не вращаем камеру
if( (mousePos.x == middleX) && (mousePos.y == middleY) ) return;
// Теперь, получив координаты курсора, возвращаем его обратно в середину.
SetCursorPos(middleX, middleY);
// Теперь нам нужно направление (или вектор), куда сдвинулся курсор.
// Его рассчет — простое вычитание. Просто возьмите среднюю точку и вычтите из неё
// новую позицию мыши: VECTOR = P1 — P2; где P1 — средняя точка (400,300 при 800х600).
// После получения дельты X и Y (или направления), я делю значение
// на 1000, иначе камера будет жутко быстрой.
angleY = (float)((middleX — mousePos.x))/1000.0f;
angleZ = (float)((middleY — mousePos.y)) / 1000.0f;
static float lastRotX = 0.0f;
lastRotX = currentRotX; // Сохраняем последний угол вращения
// и используем заново currentRotX
// Если текущее вращение больше 1 градуса, обрежем его, чтобы не вращать слишком быстро
if(currentRotX > 1.0f)
{
currentRotX = 1.0f;
// врощаем на оставшийся угол
if(lastRotX != 1.0f)
{
// Чтобы найти ось, вокруг которой вращаться вверх и вниз, нужно
// найти вектор, перпендикулярный вектору взгляда камеры и
// вертикальному вектору.
// Это и будет наша ось. И прежде чем использовать эту ось,
// неплохо бы нормализовать её.
CVector3 vAxis = Cross(m_vView — m_vPosition, m_vUpVector);
vAxis = Normalize(vAxis);
// Вращаем камеру вокруг нашей оси на заданный угол
RotateView(1.0f — lastRotX, vAxis.x, vAxis.y, vAxis.z);
}
}
// Если угол меньше -1.0f, убедимся, что вращение не продолжится
else if(currentRotX < —1.0f)
{
currentRotX = —1.0f;
if(lastRotX != —1.0f)
{
// Опять же вычисляем ось
CVector3 vAxis = Cross(m_vView — m_vPosition, m_vUpVector);
vAxis = Normalize(vAxis);
// Вращаем
RotateView( —1.0f — lastRotX, vAxis.x, vAxis.y, vAxis.z);
}
}
// Если укладываемся в пределы 1.0f -1.0f — просто вращаем
else
{
CVector3 vAxis = Cross(m_vView — m_vPosition, m_vUpVector);
vAxis = Normalize(vAxis);
RotateView(angleZ, vAxis.x, vAxis.y, vAxis.z);
}
// Всегда вращаем камеру вокруг Y-оси
RotateView(angleY, 0, 1, 0);
}
Теперь мы способны вращать взгляд мышью! Отличное свойство для нашего класса, не так ли? И не так уж много кода.
Давайте я объясню, как всё работает. После каждого движения мыши мы помещаем курсор назад в центр экрана, но прежде, чем сделать это, мы получаем координаты нового положения курсора. Теперь, имея новые координаты курсора, мы можем простыми расчетами выяснить, в какую сторону вращать камеру. Давайте посмотрим пример:
Скажем, мы изменили разрешение на 640х480. Значит, середина у нас — 320 и 240.
‘*’ — позиция курсора. Назовём её P
————————————————————————
| | |
| | |
| | |
| | |
| | |
| * P1 (320, 240) |
| | |
| | |
| | |
| | |
| | |
| | |
————————————————————————
Теперь, когда мы передвигаем мышь, мы сохраняем позицию, куда она была передвинута.
Скажем, мы сдвинули мышь вверх и влево — наша новая позиция будет P2
————————————————————————
| |
| |
| |
| * P2 (319, 239) |
| |
| |
| * (320, 240) |
| |
| |
| |
| |
| |
| |
————————————————————————
Логика подсказывает, что мы должны передвинуть камеру немного влево и вверх, правильно? Конечно, правильно.
Ну, расчеты тут просты. Мы просто вычитаем P1 — P2. Это даст нам новые X и Y.
Это называется ВЕКТОР.
Что будет вектор данных двух точек?
(320 — 319, 240 -239) = (1,1)
Теперь у нас есть вектор (1,1). В нашем случае, когда мы работаем с градусами, это значение слишком велико. Сделаем его немного меньше. Я просто разделил на 1000.
Теперь у нас есть вектор (0.001. 0.001). Мы должны принять наше X-значение как Y-угловое вращение, НО нам нужно сделать его отрицательным. Так как мы вращаем на позитивное значение, камера сдвинется НАПРАВО, но нам-то нужно — НАЛЕВО.
Вы заметили, что мы добавили пару математических ф-ий в начало файла. Они помогут нам вычислить ось, вокруг которой нужно производить вращение. Получив, 90-градусный пепендикуляр от камеры, мы можем использовать его как ось для вращения.
Учтите, что с новыми расширениями CVector3 мы больше не можем написать CVector3 vVector = {0, 0, 0};
Должно быть: CVector3 vVector = CVector(0, 0, 0);
Это потому, что теперь у нас есть конструктор класса.
//***********************************************************************************
Файл main.cpp
Основные изменения коснулись функций MainLoop(), CheckForMovement(), и RenderScene()…
// так как их задачи теперь выполняет мышь.
// Также к клавишам UP и DOWN мы добавили W и S для движения вперед-назад.
// Изменённая функция:void CheckForMovement()
{
if((GetKeyState(VK_UP) & 0x80) || (GetKeyState(‘W’) & 0x80))
g_Camera.MoveCamera(kSpeed);if((GetKeyState(VK_DOWN) & 0x80) || (GetKeyState(‘S’) & 0x80))
g_Camera.MoveCamera(—kSpeed);
}
// В главном игровом цикле (while(1)) перед вызовом CheckForMovement() добавьте
// вызов g_Camera.SetViewByMouse():
while(1)
{
……………….
……………….
……………….
//Проверяем мышь и клавиши каждый кадр
g_Camera.SetViewByMouse(); //передвинуть мышью
CheckForMovement();
RenderScene();
……………….
……………….
}
// В функции RenderScene изменим отрисовку объектов: вместо треугольников нарисуем
// квадраты.
// Замените glBegin(GL_TRIANGLES); …………. glEnd(); на следующее:
glBegin (GL_QUADS);
for(float i = 0; i < 100; i += 5)
{
glColor3ub(255, 255, 0);
glVertex3f(—10 + i/5, i, 10 — i/5);
glColor3ub(255, 0, 0);
glVertex3f(—10 + i/5, i, —10 + i/5);
glColor3ub(0, 255, 255);
glVertex3f(10 — i/5, i, —10 + i/5);
glColor3ub(0, 0, 255);
glVertex3f(10 — i/5, i, 10 — i/5);
}
glEnd();
Вот вроде бы и всё. Попробуйте скомпилировать программу и поуправлять взглядом.