Третья часть уроков о камере. Теперь мы научимся направлять взгляд мышью!

Файл 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(xvVector.x, yvVector.y, zvVector.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

Единственное, что нужно изменить — спрятать курсор.

// Найдите функцию CreateMyWindow(),
// в ней найдите строку
ShowCursor(TRUE);// И замените TRUE на FALSE.
// Должно получиться:
ShowCursor(FALSE);      // Скрываем курсор

 

Файл camera.cpp

В нём будет немало изменений: мы добавим математический функционал.

// Так как мы добавили конструкторы в класс CVector3, нам нужно изменить
// обьявления векторов в классе камеры.
// Первое — в конструкторе класса камеры, теперь там должно быть: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()…

// Из функции CheckForMovement уберём обработку клавиш влево-вправо,
// так как их задачи теперь выполняет мышь.
// Также к клавишам 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();

 

Вот вроде бы и всё. Попробуйте скомпилировать программу и поуправлять взглядом.

Исходные коды к уроку