Tuesday, 1 November 2011

Rotating objects/bullets about

This may sound a silly title (yes I agree), but I don’t have a better name for it yet. I imagine that someone reading this will say “Duh! You want to rotate the object, just add in a rotation matrix”, yes you are 100% correct.
But that’s not what I’m talking about.

The issue
In your normal FPS game, you use WSAD to move your player about, right. But when you turn your camera in a different direction WSAD still work, but W is still forward, its not on any axis its forward.
Take this chunk of code:
const float SPD=1.0f;
// move the player
if (KeyDown(‘A’))
     mPlayer.mPos.x-=SPD;
if (KeyDown(‘D’))
     mPlayer.mPos.x+=SPD;
if (KeyDown(‘W’))
     mPlayer.mPos.z+=SPD;
if (KeyDown(‘S’))
     mPlayer.mPos.z-=SPD

Yes its move the player about using WSAD, but how do we add in the turning?
const float RSPD=D2R(60); // 60 degrees per second
// turn the player
if (KeyDown(VK_LEFT))
     mPlayer.mTurn.x-=RSPD;  // yaw
if (KeyDown(VK_RIGHT))
     mPlayer.mTurn.x+=RSPD;  // yaw

This will turn the model, but when we press WSAD the player will still move in the same directions as before, it will not take into account the rotation that we have applied.


Using Maths
Now: some of you may remember your basic sine & cosine trigonometry.
And with a little work, you could change this into the proper formula needed for figuring out the correct movement direction.
However I have a quicker simpler solution: using the DirectX matrix code.

Using DirectX Maths
Consider this code:
// DO NOT COPY THIS CODE: it does not work
const float RSPD=D2R(60); // 60 degrees per second
// turn the player
if (KeyDown(VK_LEFT))
     mPlayer.mTurn.x-=RSPD;  // yaw
if (KeyDown(VK_RIGHT))
     mPlayer.mTurn.x+=RSPD;  // yaw
// get the rotation matrix
MATRIX rot=CreateRotationMatrix(mPlayer.mTurn);
// move the player
if (KeyDown(‘A’))
     mPlayer.mPos+=LEFT*rot; // move left
if (KeyDown(‘D’))
     mPlayer.mPos+=RIGHT*rot; // move right
if (KeyDown(‘W’))
     mPlayer.mPos+=FORWARD*rot; // move forward
if (KeyDown(‘S’))
     mPlayer.mPos+=BACK*rot; // move back

What we are doing here is rotating the player like normal. Then when we move the player, instead of moving in the X, Y or Z direction, we take the movement vector & rotate it based upon the players rotation matrix. That way the W is forward, no matter which way the player is facing.

The actual DirectX code is a little more complex than this, but not by much (you just need to know what the funtions are):

// This is valid code
const float RSPD=D2R(60); // 60 degrees per second
// turn the player (as usual)
// ....

// create a rotation matrix using the players turn
D3DXMATRIX rot;
D3DXMatrixRotationYawPitchRoll(&rot,mPlayer.mTurn.x,mPlayer.mTurn.y,mPlayer.mTurn.z);

D3DXVECTOR3 dir(0,0,0); // the direction to move
// update dir
if (KeyDown('A'))    dir.x--; // move left
if (KeyDown('D'))    dir.x++; // move right
if (KeyDown('W'))    dir.z++; // move forward
if (KeyDown('S'))    dir.z--; // move back

// now lets rotate 'dir' by the rotation matrix 'rot'
D3DXVECTOR3 rdir;  // rotated direction
D3DXVec3TransformCoord(&rdir,&dir,&rot);     // rdir=dir*rot

// finally add it to the players position
mPlayer.mPos+=rdir*SPD;

To simplify the coding a little, I captured all the key presses and updated the vector ‘dir’ before performing the final multiplication.
The main piece of code to note is the D3DXVec3TransformCoord which is used to multiply the matrix and vector together.

Using this idea elsewhere

Using this idea elsewhere:
Lets consider firing some shots:

if (player presses fire)
{
    CShot* ptr=new CShot();  // Create the CShot
    ptr->mVel=D3DXVECTOR3(-1,0,0);// set the velocity
    // etc,etc,etc
}

This code assumes the shot is going one way only.

But we can use the same concept to rotate the shots.

if (player presses fire)
{
    CShot* ptr=new CShot();  // Create the CShot
    D3DXVECTOR3 vel(0,0,-1);
    // get the rotation matrix
    D3DXMATRIX rot;
    D3DXMatrixRotationYawPitchRoll(&rot,mPlayer.mTurn.x,mPlayer.mTurn.y,mPlayer.mTurn.z);
    // rotate it
    D3DXVECTOR3 rvel;
    D3DXVec3TransformCoord(&rvel,&vel,&rot);       // rdir=dir*rot;   
    ptr->mVel=rdir; // set the velocity
    // etc,etc,etc
}

You can also use the same idea if you wanted the shot to be fired from a certain part of the model: measure the offset of the firing point, rotate by the matrix, fire shot from the players location + rotated offset.

Another good use is the third person camera. Here are the rough steps:
Pick the offset for the camera, say 5 units up, 20 units behind the player
Multiply this offset by the players rotation matrix
Set camera location=players location + rotated offset
Make the camera look directly at the player

Conclusion:
Its funny how a little bit of maths can make your game so much more interesting. When I first learned trigonometry, I learned it because I had to.
Once I started writing games, I found that all sorts of bits of math that I learned were actually really handy. In particular was trigonometry.

Happy coding
Mark

No comments:

Post a Comment