Chad Vernon
  • Home
  • Reel/Resume
  • Work
  • Resources
  • About
  • Contact
sideBar

Search

Categories

  • CG
  • cvxporter
  • Maya
    • API
    • Plug-ins
  • Personal

Archives

  • September 2011
  • June 2011
  • March 2011
  • December 2010
  • November 2010
  • September 2010
  • August 2010
  • July 2010
  • May 2010
  • April 2010
  • March 2010
  • December 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • June 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • September 2008
  • August 2008
  • July 2008
  • June 2008
  • May 2008
  • April 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007

Rss

  • Main Entries RSS
  • Comments RSS
Home » Resources » DirectX 9 » Moving Around a 3D World

Moving Around a 3D World

  CU_Camera.zip (716.2 KiB, 3,780 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.

2 Responses to “Moving Around a 3D World”

Subscribes to this topic Comment RSS or TrackBack URL

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

noName on September 1st, 2011 at 4:45 am

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

alwyn on September 24th, 2011 at 6:54 am

Leave A Reply

Allowed tag : <blockquote>, <p>, <code>, <em>, <small>, <ul>, <li>, <ol>, <a href=>..

 Username

 Email Address

 Website

Sticky: Always double check your comment before posting Please Note: Comment Moderation Maybe Active So There Is No Need To Resubmit Your Comments
Home » Resources » DirectX 9 » Moving Around a 3D World

© 2011 Chad Vernon