Moving Around a 3D World

  CU_Camera.zip (716.2 KiB, 5,945 hits)

In this tutorial, we’ll learn how to move around in a 3D world. We’ll use DirectInput to retrieve user movement data and then translate and rotate the camera based on this input. The camera style that we will implement is a classic first person shooter camera. The user will be able to move forward and backwards and strafe left and right. We’ll also add mouse look so users can look around with the mouse.

A camera is really just composed of a single matrix: an inverted world transform matrix (a.k.a. a view matrix). The view matrix controls where in 3D space to place our viewpoint and what coordinates to look at, so it makes sense that a camera can be represented by a view matrix. Why is a view matrix an inverted world transform matrix? Say you want to move to the right. Believe it or not, the camera is always positioned at the origin of a 3D coordinate system, pointing down the positive Z-axis. So, to move to the right we have to move everything in our world to the left. This has the same effect as moving a “camera” to the right. Luckily, there is a function in the D3DX library that creates this matrix for us: D3DXMatrixLookAtLH. All we need to pass in is a position, a view target, and an up vector.

If you look at the matrix that D3DXMatrixLookAtLH creates, you’ll notice that it is created with 4 different vectors: a right vector, an up vector, a look vector, and a position. These vectors respectively refer to the camera’s local X-axis, local Y-axis, local Z-axis, and position in world space. Therefore, it makes sense to store these vectors separately since we transform the camera with respect to these individual vectors. For example, to look up, the up and look vectors are rotated about the right vector. To move right, we just move in the direction of the right vector. To calculate the new view matrix, we just call D3DXMatrixLookAtLH with the stored position and up vectors along with a target vector that can be calculated by adding the look vector to the position vector.

Moving and strafing the camera is really simple since we’re storing the camera’s right, up, and look vectors. To move forward and backward, we just move in the direction of the look vector. To strafe left and right, we move in the direction of the right vector.

In order to implement mouse look properly, we need to be able to rotate around an arbitrary axis in world space as shown below. The arbitrary axes that we will rotate about are the right, up, and look vectors of the camera. The D3DX library has a function that creates this rotation matrix for us: D3DXMatrixRotationAxis. We just tell it what vector to rotate around and how many radians to rotate and we get a rotation matrix in return. Once we have this rotation matrix, we apply it to the right, up, and look vectors with D3DXVec3TransformNormal.

Arbitrary Axis

To implement the camera, we’ll created a new class, CCamera, that encapsulates all the view data, which includes a view matrix and projection matrix.

#include "stdafx.h"

class CCamera
{
public:
    CCamera();
    void CreateProjectionMatrix( float fov, float aspect, float nearPlane, float farPlane );
    void MoveForward( float units );
    void Strafe( float units ); 
	void MoveUp( float units );    
	
	void Yaw( float radians );
	void Pitch( float radians );
	void Roll( float radians );
    void Update();

    void SetPosition( D3DXVECTOR3* pPosition );
    void SetLookAt( D3DXVECTOR3* pLookAt );
    void SetFOV( float fov )            { CreateProjectionMatrix( fov, m_aspect, m_nearPlane, m_farPlane ); }
    void SetAspectRatio( float aspect ) { CreateProjectionMatrix( m_fov, aspect, m_nearPlane, m_farPlane ); }
    void SetNearPlane( float plane )    { CreateProjectionMatrix( m_fov, m_aspect, plane, m_farPlane ); }
    void SetFarPlane( float plane )     { CreateProjectionMatrix( m_fov, m_aspect, m_nearPlane, plane ); }
    void SetMaxVelocity( float maxVelocity ) { m_maxVelocity = maxVelocity; }
    void SetInvertY( BOOL invert )           { m_invertY = invert; }
    void SetMaxPitch( float maxPitch )       { m_maxPitch = maxPitch; }

    D3DXMATRIX* GetViewMatrix()        { return &m_view; }
    D3DXMATRIX* GetProjectionMatrix()  { return &m_projection; }
    D3DXVECTOR3* GetPosition()         { return &m_position; }
    D3DXVECTOR3* GetLookAt()           { return &m_lookAt; }
    float GetFOV()                     { return m_fov; }
    float GetAspectRatio()             { return m_aspect; }
    float GetNearPlane()               { return m_nearPlane; }
    float GetFarPlane()                { return m_farPlane; }
    float GetMaxVelocity()             { return m_maxVelocity; }
    BOOL  GetInvertY()                 { return m_invertY; }
    float GetPitch()                   { return m_pitch; }
    float GetYaw()                     { return m_yaw; }
    float GetMaxPitch()                { return m_maxPitch; }

private:
    D3DXMATRIX  m_view;      
    D3DXMATRIX  m_projection;
    D3DXVECTOR3 m_right;     
    D3DXVECTOR3 m_up;        
    D3DXVECTOR3 m_look;      
    D3DXVECTOR3 m_position;  
    D3DXVECTOR3 m_lookAt;    
    D3DXVECTOR3 m_velocity;  
    float       m_yaw;       
    float       m_pitch;     
    float       m_maxPitch;
    float       m_maxVelocity;
    float       m_fov;       
    float       m_aspect;    
    float       m_nearPlane; 
    float       m_farPlane;  
    BOOL        m_invertY;
    BOOL        m_enableYMovement;
};

The CCamera class has all the necessary values and methods to implement a basic camera including position, target, projection values, rotation values.

#include "..\include\stdafx.h"
#include "..\include\CCamera.h"

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Default constructor
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
CCamera::CCamera()
{
    m_maxPitch        = D3DXToRadian( 89.0f );
    m_maxVelocity     = 1.0f;
    m_invertY         = FALSE;
    m_enableYMovement = TRUE;
    m_position        = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
    m_velocity        = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );  
    m_look            = D3DXVECTOR3( 0.0f, 0.0f, 1.0f );
    CreateProjectionMatrix( D3DX_PI / 3.0f, 1.3f, 0.1f, 1000.0f );
    Update();
} 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Creates the projection matrix.
Parameters:
[in] fov - Field of view
[in] aspect - Aspect ratio
[in] near - Near plane
[in] far - Far plane
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::CreateProjectionMatrix( float fov, float aspect, float nearPlane, float farPlane )
{
    m_fov       = fov;
    m_aspect    = aspect;
    m_nearPlane = nearPlane;
    m_farPlane  = farPlane;
    D3DXMatrixPerspectiveFovLH( &m_projection, m_fov, m_aspect, m_nearPlane, m_farPlane );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Moves the camera forward and backward
Parameters:
[in] units - Amount to move
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::MoveForward( float units )
{
    if ( m_enableYMovement )
    {
       m_velocity += m_look * units;
    }
    else
    {
        D3DXVECTOR3 moveVector( m_look.x, 0.0f, m_look.z );
        D3DXVec3Normalize( &moveVector, &moveVector );
        moveVector *= units;
        m_velocity += moveVector;
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Moves the camera left and right
Parameters:
[in] units - Amount to move
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::Strafe( float units )
{
    m_velocity += m_right * units;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Moves the camera up and down
Parameters:
[in] units - Amount to move
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::MoveUp( float units )
{
    if ( m_enableYMovement )
    {
        m_velocity.y += units;
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Yaw the camera around its Y-axis.
Parameters:
[in] radians - Radians to yaw.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::Yaw( float radians )
{
    if ( radians == 0.0f )
    {
        return;
    }
    D3DXMATRIX rotation;
    D3DXMatrixRotationAxis( &rotation, &m_up, radians );
    D3DXVec3TransformNormal( &m_right, &m_right, &rotation );
    D3DXVec3TransformNormal( &m_look, &m_look, &rotation );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Pitch the camera around its X-axis.
Parameters:
[in] radians - Radians to pitch.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::Pitch( float radians )
{
    if ( radians == 0.0f )
    {
        return;
    }

    radians = (m_invertY) ? -radians : radians;
    m_pitch -= radians;
    if ( m_pitch > m_maxPitch )
    {
        radians += m_pitch - m_maxPitch;
    }
    else if ( m_pitch < -m_maxPitch )
    {
        radians += m_pitch + m_maxPitch;
    }

    D3DXMATRIX rotation;
    D3DXMatrixRotationAxis( &rotation, &m_right, radians );
    D3DXVec3TransformNormal( &m_up, &m_up, &rotation );
    D3DXVec3TransformNormal( &m_look, &m_look, &rotation );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Roll the camera around its Z-axis.
Parameters:
[in] radians - Radians to roll.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::Roll( float radians )
{
    if ( radians == 0.0f )
    {
        return;
    }
    D3DXMATRIX rotation;
    D3DXMatrixRotationAxis( &rotation, &m_look, radians );
    D3DXVec3TransformNormal( &m_right, &m_right, &rotation );
    D3DXVec3TransformNormal( &m_up, &m_up, &rotation );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Updates the camera and creates a new view matrix.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::Update()
{
    // Cap velocity to max velocity
    if ( D3DXVec3Length( &m_velocity ) > m_maxVelocity )
    {
        m_velocity = *(D3DXVec3Normalize( &m_velocity, &m_velocity )) * m_maxVelocity;
    }

    // Move the camera
    m_position += m_velocity;
    // Could decelerate here. I'll just stop completely.
    m_velocity = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
    m_lookAt = m_position + m_look;
    
    // Calculate the new view matrix
    D3DXVECTOR3 up = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
    D3DXMatrixLookAtLH( &m_view, &m_position, &m_lookAt, &up );

    // Set the camera axes from the view matrix
    m_right.x = m_view._11;  
    m_right.y = m_view._21;  
    m_right.z = m_view._31;  
    m_up.x = m_view._12;
    m_up.y = m_view._22;
    m_up.z = m_view._32;
    m_look.x = m_view._13;
    m_look.y = m_view._23;
    m_look.z = m_view._33;

    // Calculate yaw and pitch
    float lookLengthOnXZ = sqrtf( m_look.z * m_look.z + m_look.x * m_look.x );
    m_pitch = atan2f( m_look.y, lookLengthOnXZ );
    m_yaw   = atan2f( m_look.x, m_look.z );
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Updates the camera and creates a new view matrix.
Parameters:
[in] pPosition - New position
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::SetPosition( D3DXVECTOR3* pPosition )
{
    m_position.x = pPosition->x;
    m_position.y = pPosition->y;
    m_position.z = pPosition->z;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
Summary: Updates the camera and creates a new view matrix.
Parameters:
[in] pPosition - New target
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CCamera::SetLookAt( D3DXVECTOR3* pLookAt )
{
    m_lookAt.x = pLookAt->x;
    m_lookAt.y = pLookAt->y;
    m_lookAt.z = pLookAt->z;
    D3DXVec3Normalize( &m_look, &(m_lookAt - m_position) );
}

The CCamera class performs all the calculations described at the beginning of the tutorial. To move, we increase a velocity vector in the direction that we want to move. When we want to rotate the camera, we create_ the rotation matrix with D3DXMatrixRotationAxis and then rotate the corresponding vectors with D3DXVec3TransformNormal. Notice that the pitch is constrained to a range that prevents the camera from looking straight up or straight down. Since we are implementing a first person camera, we don’t want to be able to rotate all the way around when we look up or down.

The view matrix is created in the CCamera::Update method. To move the camera, we simply add the velocity vector to the camera’s position. To create_ the view matrix we call D3DXMatrixLookAtLH. Notice that the up vector we pass in to this method is the world up vector (0.0f, 1.0f, 0.0f), instead of the camera’s up vector. We do this because we are implementing a first person camera where up is always (0.0f, 1.0f, 0.0f). If we were to use the camera’s up axis, we would create_ a space ship type of camera where we could roll around wherever we wanted to.

After the view matrix is created we extract the new camera axes from it and calculate the camera’s pitch and yaw. The right, up, and look vectors are respectively located in the 1st, 2nd, and 3rd columns of the view matrix. The pitch value is calculated by projecting the look vector onto the XZ-plane and then using the atan2f function to get the angle between the XZ-plane and the Y value of the camera’s look vector. The yaw is calculated by getting the angle between the X and Z values of the camera’s look vector.

Since the projection matrix also affects how we view geometry, the CCamera class contains its own projection matrix, which is formed with D3DXMatrixPerspectiveFovLH.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Summary: Responds to key presses
Parameters:
[in] xDelta - Change in mouse x-axis since last frame
[in] yDelta - Change in mouse y-axis since last frame
[in] zDelta - Change in mouse z-axis since last frame
[in] pMouseButtons - Mouse button states
[in] pPressedKeys - Keyboard keys that are pressed and not locked
[in] elapsedTime - Time elapsed since last frame
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CGameApp::ProcessInput( long xDelta, long yDelta, long zDelta, BOOL* pMouseButtons, BOOL* pPressedKeys, float elapsedTime )
{
    float cameraSpeed = 20.0f;
    if ( pMouseButtons[0] )
    {
        m_camera.Yaw( xDelta * elapsedTime * 1.8f);
        m_camera.Pitch( yDelta * elapsedTime * 1.8f );
    }
    if ( pPressedKeys[DIK_W] )
    {
        m_camera.MoveForward( cameraSpeed * elapsedTime );
    }
    if ( pPressedKeys[DIK_A] )
    {
        m_camera.Strafe( -cameraSpeed * elapsedTime );
    }
    if ( pPressedKeys[DIK_S] )
    {
        m_camera.MoveForward( -cameraSpeed * elapsedTime );
    }
    if ( pPressedKeys[DIK_D] )
    {
        m_camera.Strafe( cameraSpeed * elapsedTime );
    }
}

Since we implemented or DirectInput method in the last tutorial, we can easily move the camera around with keyboard and mouse input.

3 thoughts on “Moving Around a 3D World”

noName September 1, 2011 at 4:45 am

Very good lesson. This lesson help me to resolve some of my problems with in-game movement…..

alwyn September 24, 2011 at 6:54 am

Awesome! Thanx a lot mate, clean, neat and well documented.

Lukás September 20, 2013 at 2:02 pm

Hi chad. I have some problem with my CCamera class. When i run my app, i can see my terrain but i can’t move. so i decide to copy your CCamera class into my project, but it also don’t working. DirectInput is working fine so i really don’t know what’s the problem. Can u look on that pls? Here is my code: http://www.datafilehost.com/d/efe09503 Thanks

Comments are closed.

Leave A Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

<code> For inline code.
[sourcecode] For multiline code.