Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

OpenGL: Камера - часть 3 - Управление мышью






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


Файл 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
Единственное, что нужно изменить - спрятать курсор.


// Найдите функцию 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();


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







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




Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.