{{notification.text}}

MirGames

Давайте, во-первых, уясним, что такое камера, а точнее, что она должна уметь.

Камера – точечный зритель 3D сцены. Отсюда и функции камеры: перемещение, вращение, обновление и автоматическое вычисление view-матрицы D3D.

Frustum culling (отсечение по усеченной пирамиде) совершенно зависит от положения и угла поворота камеры, поэтому должно обновляться вместе с обновлением камеры. Кроме того, отнесение frustum culling к камере позволяет производить некоторую оптимизацию построения viewing frustum (пирамиды обзора) используя промежуточные (побочные) данные генерации view-матрицы.

Такие внутренние singleton объекты движка, как камера, к которым нет прямого интерфейса «сверху» (т.е. извне движка: из основной части программы или из скрипта) я помещаю в пространство имен: конструкторы/деструкторы для таких объектов обычно не нужны, сами они не являются базовыми или наследниками других классов…

Поэтому пространство имен является удобным и быстрым заменителем ООП для таких объектов. Все сейчас станет понятно.

Итак, интерфейс для камеры будет таким:

#pragma once

//Тут ваш модуль для работы с DirectX, подключающий соотв. заголовки и 
//библиотеки, если такой есть, или просто подключение Direct3D и D3DX...

//Интерфейс (как public секция класса)
namespace D3DCamera
{
//2 типа камеры – перемещающаяся по плоскости XZ (Y – вектор «вверх»)
//                  и свободно перемещающаяся
         enum CameraType {CT_LANDOBJECT, CT_AIRCRAFT};

         //Обновление камеры (генерация и установка view-матрицы D3D)
         void RefreshCamera();

         //Перемещение
         void MoveLeftRight(float units);        //left/right
         void MoveUpDown(float units);      //up/down
         void MoveForwBack(float units); //forward/backward
         //Вращение
         void RotateRight(float angle);  //rotate on right vector
         void RotateUp(float angle);          //rotate on up vector
         void RotateLook(float angle);      //rotate on look vector
         
         //Аксессоры
         //Set
         void SetPosition(D3DXVECTOR3 pos);
         void SetDirection(D3DXVECTOR3 look=D3DXVECTOR3(0.0f, 0.0f, 1.0f),
 D3DXVECTOR3 up=D3DXVECTOR3(0.0f, -1.0f, 0.0f));
         void SetPosAndDir(D3DXVECTOR3 pos, D3DXVECTOR3 look, D3DXVECTOR3 up);
         void SetCameraType(CameraType cameraType);
         void SetDefaultDir();
         
         //Get
         void GetViewMatrix(D3DXMATRIX* V);
         D3DXMATRIX* GetInverseViewMatrix();
         D3DXMATRIX* GetDefaultViewMatrix();
 D3DXMATRIX* GetProjectionMatrix();
         const D3DXVECTOR3& GetPosition();
         const D3DXVECTOR3& GetRight();
         const D3DXVECTOR3& GetUp();
         const D3DXVECTOR3& GetLook();

         //Для frustum-culling
         bool PointInFrustum(float x, float y, float z);
         bool SphereInFrustum(float x, float y, float z, float radius);
         //Проверку параллелепипеда я не реализовывал – пока не было нужно
         //Это сделать не сложно, в Интернете по этой теме полно статей
};

Это все мы помещаем в “D3DCamera.h” – это и будет наш интерфейс, доступный при подключении модуля камеры (#include “D3DCamera.h”)

А вот такая реализация (код с подробными комментариями):

(Везде: IDirect3DDevice9* Device – основной device (интерфейс видеоадаптера))

#include "D3DCamera.h"

//Как “private” секция класса
namespace D3DCamera
{
         //"Private" данные (можно инициализировать значениями по-умолчанию)
         CameraType _cameraType=CT_AIRCRAFT;
         D3DXVECTOR3 _right=D3DXVECTOR3(1.0f, 0.0f, 0.0f);
         D3DXVECTOR3 _up=D3DXVECTOR3(0.0f, -1.0f, 0.0f);
         D3DXVECTOR3 _look=D3DXVECTOR3(0.0f, 0.0f, 1.0f);
         D3DXVECTOR3 _pos=D3DXVECTOR3(0.0f, 0.0f, -5.0f);
         D3DXMATRIX _defaultViewMatrix, _projectionMatrix,
                                 _invViewMatrix; //Полезно, например, при расчете
                                                         //world-матрицы для биллбоардов

         //Viewing frustum___________________________________________________
         struct FrustumPlane
         {
                 D3DXVECTOR3 m_normal;
                 float m_distance;

                 inline void Normalise() 
                 {
                         float denom = 1 / sqrtf((m_normal.x*m_normal.x) + m_normal.y*m_normal.y) + (m_normal.z*m_normal.z));
                         m_normal.x = m_normal.x * denom;
                         m_normal.y = m_normal.y * denom;
                         m_normal.z = m_normal.z * denom;
                         m_distance = m_distance * denom;
                 }

                 inline float DistanceToPoint(const D3DXVECTOR3& pnt) {
                         return D3DXVec3Dot(&m_normal, &pnt) + m_distance;
                 }
         } m_frustumPlanes[6];   //6 плоскостей viewing-frustum
         //__________________________________________________________________
};


void D3DCamera::RefreshCamera()
{
         D3DXMATRIX V, Matrix;
         GetViewMatrix(&V);      //Вычисление view-матрицы (см. ниже)
         Device->SetTransform(D3DTS_VIEW, &V);   //Установка этой матрицы

         D3DXMatrixInverse(&_invViewMatrix, 0, &V); //Обратная для view матрица

         //Получаем комбинированную матрицу view*projection
         D3DXMATRIXA16 matComb;
         D3DXMatrixMultiply(&matComb, &V, &_projectionMatrix);

         //Левая плоскость viewing frustum
         m_frustumPlanes[0].m_normal.x = matComb._14 + matComb._11; 
         m_frustumPlanes[0].m_normal.y = matComb._24 + matComb._21; 
         m_frustumPlanes[0].m_normal.z = matComb._34 + matComb._31; 
         m_frustumPlanes[0].m_distance = matComb._44 + matComb._41;

         //Правая плоскость viewing frustum
         m_frustumPlanes[1].m_normal.x = matComb._14 - matComb._11; 
         m_frustumPlanes[1].m_normal.y = matComb._24 - matComb._21; 
         m_frustumPlanes[1].m_normal.z = matComb._34 - matComb._31; 
         m_frustumPlanes[1].m_distance = matComb._44 - matComb._41;

         //Верхняя плоскость viewing frustum
         m_frustumPlanes[2].m_normal.x = matComb._14 - matComb._12; 
         m_frustumPlanes[2].m_normal.y = matComb._24 - matComb._22; 
         m_frustumPlanes[2].m_normal.z = matComb._34 - matComb._32; 
         m_frustumPlanes[2].m_distance = matComb._44 - matComb._42;

         //Нижняя плоскость viewing frustum
         m_frustumPlanes[3].m_normal.x = matComb._14 + matComb._12; 
         m_frustumPlanes[3].m_normal.y = matComb._24 + matComb._22; 
         m_frustumPlanes[3].m_normal.z = matComb._34 + matComb._32; 
         m_frustumPlanes[3].m_distance = matComb._44 + matComb._42;

         //Ближняя плоскость viewing frustum
         m_frustumPlanes[4].m_normal.x = matComb._13; 
         m_frustumPlanes[4].m_normal.y = matComb._23; 
         m_frustumPlanes[4].m_normal.z = matComb._33; 
         m_frustumPlanes[4].m_distance = matComb._43;

         //Дальняя плоскость viewing frustum
         m_frustumPlanes[5].m_normal.x = matComb._14 - matComb._13; 
         m_frustumPlanes[5].m_normal.y = matComb._24 - matComb._23; 
         m_frustumPlanes[5].m_normal.z = matComb._34 - matComb._33; 
         m_frustumPlanes[5].m_distance = matComb._44 - matComb._43;
}


void D3DCamera::MoveLeftRight(float units)
{
         //Только по xz плоскости для landobject типа камеры
         if (_cameraType==CT_LANDOBJECT)
         _pos+=D3DXVECTOR3(_right.x, 0.0f, _right.z)*units;
         else
                 _pos+=_right*units;
}


void D3DCamera::MoveUpDown(float units)
{
         //Только для aircraft камеры
         if (_cameraType==CT_AIRCRAFT)
         _pos+=_up*units;
}


void D3DCamera::MoveForwBack(float units)
{
         //Только по xz плоскости для landobject типа камеры
         if (_cameraType==CT_LANDOBJECT)
         _pos+=D3DXVECTOR3(_look.x, 0.0f, _look.z)*units;
         else
                 _pos+=_look*units;
}


void D3DCamera::RotateRight(float angle)
{
         D3DXMATRIX T;

         D3DXMatrixRotationAxis(&T, &_right, angle);
         
         //Вращение _up и _look векторов вокруг _right вектора
         D3DXVec3TransformCoord(&_up, &_up, &T);
         D3DXVec3TransformCoord(&_look, &_look, &T);
}


void D3DCamera::RotateUp(float angle)
{
         D3DXMATRIX T;
         
         if (_cameraType==CT_LANDOBJECT)
                 D3DXMatrixRotationY(&T, angle); //Вращение всегда вокруг оси Y для
   //landobject камеры
         else
                 D3DXMatrixRotationAxis(&T, &_up, angle);        //Вращение вокруг
//собственного _up вектора для aircraft камеры
         
         //Вращение _right и _look в-ров вокруг _up или y-axis соотв.
         D3DXVec3TransformCoord(&_right, &_right, &T);
         D3DXVec3TransformCoord(&_look, &_look, &T);
}


void D3DCamera::RotateLook(float angle)
{
         //Только для aircraft камеры
         if (_cameraType==CT_AIRCRAFT)
         {
                 D3DXMATRIX T;
                 
                 D3DXMatrixRotationAxis(&T, &_look, angle);
                 
                 //Rotate _up and _right around _look vector
                 D3DXVec3TransformCoord(&_right,&_right, &T);
                 D3DXVec3TransformCoord(&_up,&_up, &T);
         }
}


void D3DCamera::SetPosition(D3DXVECTOR3 pos)
{
         _pos=pos;
}


void D3DCamera::SetDirection(D3DXVECTOR3 look, D3DXVECTOR3 up)
{
         _look=look;
         _up=up;
         D3DXVec3Cross(&_right, &_look, &_up);   //Ортогонализация: _right 
//вектор всегда перпендикулярен _look и _up в-рам
}


void D3DCamera::SetPosAndDir(D3DXVECTOR3 pos, D3DXVECTOR3 look, D3DXVECTOR3 
 up)
{
         _pos=pos;

         _look=look;
         _up=up;
         D3DXVec3Cross(&_right, &_look, &_up);
}


void D3DCamera::SetCameraType(CameraType cameraType)
{
         _cameraType=cameraType;
}


//Направление по умолчанию
void D3DCamera::SetDefaultDir()
{
         _look=D3DXVECTOR3(0.0f, 0.0f, 1.0f);
         _up=D3DXVECTOR3(0.0f, -1.0f, 0.0f);
         D3DXVec3Cross(&_right, &_look, &_up);
}


void D3DCamera::GetViewMatrix(D3DXMATRIX* V)
{
/*                              Важно:
         Из-за погрешности типа float при частом вращении камерой, ее вектора
         могут перестать быть ортогональны, что приведет к искажениям изображения
         при долгом использовании программы. Чтобы этого избежать мы будем 
 проводить ортогонализацию векторов камеры:
 */
         D3DXVec3Normalize(&_look, &_look);

         D3DXVec3Cross(&_up, &_look, &_right);
         D3DXVec3Normalize(&_up, &_up);

         D3DXVec3Cross(&_right, &_up, &_look);
         D3DXVec3Normalize(&_right, &_right);

         //Построение view-матрицы
         float x=-D3DXVec3Dot(&_right, &_pos);
         float y=-D3DXVec3Dot(&_up, &_pos);
         float z=-D3DXVec3Dot(&_look, &_pos);

         (*V)(0, 0)=_right.x;
         (*V)(0, 1)=_up.x;
         (*V)(0, 2)=_look.x;
         (*V)(0, 3)=0.0f;

         (*V)(1, 0)=_right.y;
         (*V)(1, 1)=_up.y;
         (*V)(1, 2)=_look.y;
         (*V)(1, 3)=0.0f;

         (*V)(2, 0)=_right.z;
         (*V)(2, 1)=_up.z;
         (*V)(2, 2)=_look.z;
         (*V)(2, 3)=0.0f;

         (*V)(3, 0)=x;
         (*V)(3, 1)=y;
         (*V)(3, 2)=z;
         (*V)(3, 3)=1.0f;
}

 D3DXMATRIX* D3DCamera::GetInverseViewMatrix()
{
         return &_invViewMatrix;
}

 D3DXMATRIX* D3DCamera::GetProjectionMatrix()
{
         return &_projectionMatrix;
}

 D3DXMATRIX* D3DCamera::GetDefaultViewMatrix()
{
         return &_defaultViewMatrix;
}


const D3DXVECTOR3& D3DCamera::GetPosition()
{
         return _pos;
}


const D3DXVECTOR3& D3DCamera::GetRight()
{
         return _right;
}


const D3DXVECTOR3& D3DCamera::GetUp()
{
         return _up;
}


const D3DXVECTOR3& D3DCamera::GetLook()
{
         return _look;
}

//Проверка точки на попадание в кадр, т.е. в viewing frustum
//      (Точка – в усеченной пирамиде, если она находится перед
//       каждой из плоскостей этой усеченной пирамиды)
bool D3DCamera::PointInFrustum(float x, float y, float z)
{
         for (int i=0; i<6; i++)
       if (m_frustumPlanes[i].m_normal[0]*x + m_frustumPlanes[i].m_normal[1]*y
  +  m_frustumPlanes[i].m_normal[2]*z + m_frustumPlanes[i].m_distance <= 0)
          return false;
    return true;
}


//Проверка сферы на попадание в кадр, т.е. в viewing frustum
//      (Сфера - не в усеченной пирамиде, если расстояние от ее центра
//       до каждой из плоскостей ус. пирамиды больше (или равно) радиусу
//       сферы). Это – приблизительная проверка, иногда она будет включать
//       в кадр объекты, находящиеся, на самом деле, за пределами viewing fr-m.
bool D3DCamera::SphereInFrustum(float x, float y, float z, float radius)
{
    for (int i=0; i<6; i++)
       if (m_frustumPlanes[i].m_normal[0]*x + m_frustumPlanes[i].m_normal[1]*y +
                   m_frustumPlanes[i].m_normal[2]*z + m_frustumPlanes[i].m_distance <= -radius)
          return false;
    return true;
}

Ну вот и все! Теперь камеру можно подключать там, где к ней нужен доступ и использовать.
30.07.05 05:52