400 likes | 587 Views
Creating a Flexible Camera Class. Bryan Duggan. Refresher on Vectors. u.v = (u x v x + u y v y + u z v z ) p = u x v = (u y v z - u z v y , u z v x - u x v z, u x v y - u y v x ). Identity Matrix. Identity Matrix.
E N D
Creating a Flexible Camera Class Bryan Duggan
Refresher on Vectors • u.v = (ux vx + uy vy + uz vz) • p = u x v = (uy vz - uz vy, uz vx - ux vz, ux vy - uy vx)
Identity Matrix • Identity Matrix • The identity matrix has the property that if A is a square matrix, then
Local Space • The co-ordinate system we define the objects triangle list • Build all models around their own co-ordinates • Construct models without regard to position and orientation
World Space • World Transform brings • Objects into the world • Sets up the relationship between objects in the scene • Translations rotations and scaling • May be a different transform for each object in the scene • Use: • IDirect3DDevice::SetTransform(D3DTS_WORLD, &matrix);
View Space • View Space transform: • Translates the camera to the origin, looking down the positive Z-Axis • We don’t need to calculate it!! DirectX has an API to do this: • D3DXMatrixLookAtLH( D3DXMATRIX * pOut , D3DXVECTOR3 * eye , D3DXVECTOR3 * at , D3DXVECTOR3 * up) • Up is usually (0,1,0) • Use: • IDirect3DDevice::SetTransform(D3DTS_VIEW, &matrix);
Backface culling • All polygons have 2 sides. A front (visible) side • And a back (invisible) side • DirectX can cull the backfaces to reduce processing • Triangles with vertices specified in a clockwise winding order are considered front facing • Use: _ device->SetRenderState(D3DRS_CULLMODE, value) Where value is: • D3DCULL_NONE • D3DCULL_CW • D3DCULL-CCW
Projection • Transforming a 3D Scene to a 2D one • Uses perspective projection • Far away objects are smaller • Defines our frustum
The projection matrix • Is complex to derive, but directX has an API to do it: D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.5f, // 90 - degree (float)_width / (float)_height, 1.0f, 1000.0f); _device->SetTransform(D3DTS_PROJECTION, &proj);
The Viewport transform • We can optionally transform to a viewport in the display window: typedef struct _D3DVIEWPORT9 { DWORD X; DWORD Y; DWORD Width; DWORD Height; float MinZ; float MaxZ; } D3DVIEWPORT9; We set the viewport using: _device->SetViewport(&vp)
FPS Camera • In an FPS, the camera may need to: • walk forwards and backwards (W, S keys) • strafe left and right (A, D keys) • fly/jump (space key?) • Look around (the mouse) • Pitch (look up and down) • Yaw (Look left and right) • Roll (tilt left and right)
A static camera can be programmed by… // Position and aim the camera. D3DXVECTOR3 position(-50.0f, 5.0f, -50.0f); D3DXVECTOR3 target(0.0f, 5.0f, 0.0f); D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX V; D3DXMatrixLookAtLH(&V, &position, &target, &up); device->SetTransform(D3DTS_VIEW, &V);
This is ok for • A fixed camera position, but not very flexible when we want to have a moving camera as most games
So lets create our own! • To model a camera we require 4 vectors: • _position • _look • _right • _up Must always be mutually orthogonal (perpendicular) to each other _up _look _right _position
Movement • To walk: • Position changes in units of the look vector • Making sure we don’t change the Y value!! (A person can look at the ground, but we don’t want to go in that direction) • To strafe: • Position changes in units of the right vector • To jump: • Position changes in the y direction • To look around: • On the X-Axis, we rotate around the y-axis (Yaw) • Rotate _right and _look • Up and down, we rotate on the _right vector (pitch) • Rotate _up and _look
walk: void MoveableEntity::walk(float units) { D3DXVECTOR3 newPos; newPos = _pos + D3DXVECTOR3(_look.x, 0.0f, _look.z) * units; BoundableEntity * boundableEntity = _world->collidesWith(&newPos); if ((!_collisionDetection) || (boundableEntity == NULL) || (boundableEntity == this)) { _moved = true; _pos = newPos; } }
strafe void MoveableEntity::strafe(float units) { D3DXVECTOR3 newPos; newPos = _pos + D3DXVECTOR3(_right.x, 0.0f, _right.z) * units; BoundableEntity * boundableEntity = _world->collidesWith(&newPos); if ( (!_collisionDetection) || (boundableEntity == NULL) || (boundableEntity == this)) { _moved = true; _pos = newPos; } }
yaw void MoveableEntity::yaw(float angle) { D3DXMATRIX T; D3DXMatrixRotationY(&T, angle); // rotate _right and _look around _up or y-axis D3DXVec3TransformCoord(&_right,&_right, &T); D3DXVec3TransformCoord(&_look,&_look, &T); _moved = true; }
pitch void MoveableEntity::pitch(float angle) { D3DXMATRIX T; D3DXMatrixRotationAxis(&T, &_right, angle); // rotate _up and _look around _right vector D3DXVec3TransformCoord(&_up,&_up, &T); D3DXVec3TransformCoord(&_look,&_look, &T); _moved = true; }
View Matrix • At some point, we need to make a view transform matrix • The view transform does 3 things: • Makes the camera 0, 0, 0. Everything in the world needs to be translated by the same amount • Makes the camera looks down the positive Z axis
So we need to… • Translate • Translate the camera to the origin (and everything else by the same amount) • Rotate • Align the right vector with the x-axis (and everything else by the same amount) • Align the up vector with the y-axis (and everything else by the same amount) • Align the look vector with the z-axis (and everything else by the same amount) • Our new view matrix combines those operations (multiplies the 4 matrices to generate a combined transformation)
Calculate the translation bit • To translate by -px, -py, -pz, we multiply by:
Calculate the rotation matrix Rotate the right vector so that it aligns with the x-axis Rotate the up vector so that it aligns with the y-axis Rotate the look vector so that it aligns with the z-axis
Solve for the matrix A • Since A is the same in all 3 transformations, we can write them together:
Solve for Matrix A • A is the inverse of B because: • BA = BB-1 = I • You get the inverse of a matrix by switching the rows and columns:
In code: // Build the view matrix: float x = -D3DXVec3Dot(&_right, &_pos); float y = -D3DXVec3Dot(&_up, &_pos); float z = -D3DXVec3Dot(&_look, &_pos); (_viewMatrix)(0,0) = _right.x; (_viewMatrix)(0, 1) = _up.x; (_viewMatrix)(0, 2) = _look.x; (_viewMatrix)(0, 3) = 0.0f; (_viewMatrix)(1,0) = _right.y; (_viewMatrix)(1, 1) = _up.y; (_viewMatrix)(1, 2) = _look.y; (_viewMatrix)(1, 3) = 0.0f; (_viewMatrix)(2,0) = _right.z; (_viewMatrix)(2, 1) = _up.z; (_viewMatrix)(2, 2) = _look.z; (_viewMatrix)(2, 3) = 0.0f; (_viewMatrix)(3,0) = x; (_viewMatrix)(3, 1) = y; (_viewMatrix)(3, 2) = z; (_viewMatrix)(3, 3) = 1.0f;
Mouse look • The aim • When the player moves the mouse left and right, we yaw • When the player moves the mouse up and down, we pitch • By how much is determined by the delta between the last mouse position and the current mouse position
Mouse look algorithm handleEvent() { If (event == MOUSE_MOVEMENT) { midX = width / 2 midY = height / 2 deltaX = currentX – midX deltaY = currentY – midY yaw(deltaY * modifier_heuristic) roll(deltaX * modifier_heuristic) set_cursor(midX, midY) } }
To trap mouse events • Windows calls the WinProc (the handler we registered to receive notifications of events • The framework calls: • handleEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) • When a mouse movement has occurred, we receive WM_MOUSEMOVE as the msg parameter
Firstly some Win32 API code to turn off the mouse case WM_SETCURSOR: // Turn off window cursor SetCursor(NULL); _device->ShowCursor(TRUE); return TRUE; // prevent Windows from setting cursor to window class cursor break;
Now to capture the mouse: case WM_MOUSEMOVE: midX = _width / 2; midY = _height / 2; POINT p; GetCursorPos(&p); xPos = p.x; yPos = p.y; if ((xPos == midX) && (yPos == midY)) { break; } deltaX = xPos - midX; deltaY = yPos - midY; _camera->yaw(((float)deltaX) / 100.0f); _camera->pitch(((float)deltaY) / 100.0f); SetCursorPos(midX, midY); break;
RTS Camera • Camera position is over the world • Enable the mouse cursor • When the cursor moves to the extremes of the window: • Pan left & right • Move the position in units of the right vector • Without affecting the Y • Forward & backwards • Move the position in units of the look vector • Without affecting the Y
Extra operations • Zoom in and out by using the scroll wheel? • Free Rotate by holding down the ALT key?
Some important Win32 Calls! • _device->ShowCursor(TRUE); • Turns on the cursor • GetWindowRect • To get the window size • ClipCursor • To keep the cursor within certain screen coordinates • GET_X_LPARAM(lParam); • GET_Y_LPARAM(lParam); • To get the X and Y of the cursor
Picking • If we enable the mouse, we need to know when the user clicks something, what it is • We need to translate from a screen position (x, y) to an (x, y, z) in our world