0
votes

I am working on a Direct3D9 space simulator game in which I need to create a camera that holds the position and point of view of the player's spaceship. For the moment I limited my code just for moving backward and forward, up and down as well as strafing. Below is my code and it has a problem. Everything is wrapped in a class and all vectors are initialized to D3DXVECTOR3(0.0f, 0.0f, 0.0f) in the constructor, excepting the LocalUp( D3DXVECTOR3(0.0f, 1.0f, 0.0f) ) and LocalAhead( D3DXVECTOR3(0.0f, 0.0f, 1.0f) ) and the floats are set to 0.0f;

D3DXVECTOR3 Position, LookAt ,PosDelta, PosDeltaWorld, WorldAhead, WorldUp, LocalUp, 
LocalAhead, Velocity;
D3DXMATRIX View, CameraRotation;
float SpeedX, SpeedY, SpeedZ;

void Update(float ElapsedTime)
{
    SpeedX = 0.0f;
    SpeedY = 0.0f;

    if(IsKeyDown('A'))
    {
        SpeedX = -0.02f;
        Velocity.x -= SpeedX;
    }
    if(IsKeyDown('D'))
    {
        SpeedX = 0.02f;
        Velocity.x += SpeedX;
    }
    if(IsKeyDown('X'))
    {
        SpeedZ += 0.01f;
        Velocity.z += SpeedZ;
    }
    if(IsKeyDown('Z'))
    {
        SpeedZ -= 0.01f;
        Velocity.z -= SpeedZ;
    }
    if(IsKeyDown('W'))
    {
        SpeedY = 0.02f;
        Velocity.y += SpeedY;
    }
    if(IsKeyDown('S'))
    {
        SpeedY = -0.02f;
        Velocity.y -= SpeedY;
    }

    D3DXVec3Normalize(&Velocity, &Velocity);

    PosDelta.x = Velocity.x * SpeedX;
    PosDelta.y = Velocity.y * SpeedY;
    PosDelta.z = Velocity.z * SpeedZ;

    D3DXMatrixRotationYawPitchRoll(&CameraRotation, 0, 0, 0);
    D3DXVec3TransformCoord(&WorldUp, &LocalUp, &CameraRotation);
    D3DXVec3TransformCoord(&WorldAhead, &LocalAhead, &CameraRotation);
    D3DXVec3TransformCoord(&PosDeltaWorld, &PosDelta, &CameraRotation);

    Position += PosDeltaWorld;
    LookAt = Position + WorldAhead;

    D3DXMatrixLookAtLH(&View, &Position, &LookAt, &WorldUp);
}

The "D3DXMatrixPerspectiveFovLH" and "IDirect3DDevice9::SetTransform" function are called in another part of the application. As they are working fine I will no longer talk about them.

The problem is that whenever the Z-axis speed is quite big and I strafe and move laterally, separately or at the same time, the camera's Z-axis speed will decrease. Moreover, after the speed is almost 0 and then I press the key that increased the speed, the sense of the vector inverts, then comes back to normal. This also happens when changing the vector's sense at quite high speeds(e.g. Pressing X then immediately pressing 'Z'). Can anybody explain me why is this happening and how can I solve this problem?

I will also ask another question: how can I slowly decrease the strafe and Y-axis speed if no key is pressed? I want to have the inertia effect implemented in the game.

If there is anyone able to help me, please respond!

EDIT: NEW CODE:

void NewFrontiers3DEntityPlayer::OnFrameUpdate(float ElapsedTime)
{
State.SpeedX = 0.0f;
State.SpeedY = 0.0f;
if(IsKeyDown(State.Keys[CAM_STRAFE_LEFT]))
    State.SpeedX = -0.02f;
if(IsKeyDown(State.Keys[CAM_STRAFE_RIGHT]))
    State.SpeedX = 0.02f;
if(IsKeyDown(State.Keys[CAM_MOVE_FORWARD]))
{
    State.SpeedZ += 0.01f;
}
if(IsKeyDown(State.Keys[CAM_MOVE_BACKWARD]))
{
    State.SpeedZ -= 0.01f;
}
if(IsKeyDown(State.Keys[CAM_MOVE_UP]))
    State.SpeedY = 0.02f;
if(IsKeyDown(State.Keys[CAM_MOVE_DOWN]))
    State.SpeedY = -0.02f;

State.Velocity.x = State.SpeedX;
State.Velocity.y = State.SpeedY;
State.Velocity.z = State.SpeedZ;

D3DXVec3Normalize(&State.Velocity, &State.Velocity);

State.PosDelta.x = State.Velocity.x * ElapsedTime;
State.PosDelta.y = State.Velocity.y * ElapsedTime;
State.PosDelta.z = State.Velocity.z * ElapsedTime;

D3DXMatrixRotationYawPitchRoll(&State.CameraRotation, 0, 0, 0);
D3DXVec3TransformCoord(&State.WorldUp, &State.LocalUp, &State.CameraRotation);
D3DXVec3TransformCoord(&State.WorldAhead, &State.LocalAhead, &State.CameraRotation);
D3DXVec3TransformCoord(&State.PosDeltaWorld, &State.PosDelta, &State.CameraRotation);

State.Position += State.PosDeltaWorld;
State.LookAt = State.Position + State.WorldAhead;

D3DXMatrixLookAtLH(&State.View, &State.Position, &State.LookAt, &State.WorldUp);

return;
}

"State" is a structure that holds all the information about the camera.

1
Since you are using c++ i would recommend you to use classes instead of globalsQuest
I am using classes, I used globals because it is way much easier to post here.featherless biped

1 Answers

1
votes

I would guess your speed changes when you move in more than one direction at once because you normalize your velocity.

For example, moving in Z:

Velocity = (0, 0, 0.01)
Speed = (0,0, 0.01)
Normalized Velocity = (0, 0, 1)
PosDelta = (0, 0, 0.01)

and moving in X+Z:

Velocity = (0.02, 0, 0.01)
Speed = (0.02, 0, 0.01)
Normalized Velocity = (0.897, 0, 0.435)
PosDelta = (0.018, 0, 0.0044)    

Regarding your inversion of direction I'm guessing it may be related in part to your relatively strange method of using velocity/speed (see more below) and possibly due to the in-precision of floats. Regarding the last point what do you think the following code outputs (disregard potential compiler optimizations):

float test1 = 0f;

test1 += 0.1f;
test1 += 0.1f;
test1 += 0.1f;
test1 += 0.1f;
test1 += 0.1f;

test1 -= 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;
test1 -= 0.1f;

printf("%g\n", test1);

It (likely?) won't output the obvious answer of 0 since 0.1 cannot be exactly represented in base-2. It prints 1.49012e-008 on my system. What could be happening is you may be close to 0 but not exactly which may cause the coordinate inversion to appear. You can get rid of this by rounding speeds to a certain accuracy.

Your overall method of handling the velocity/speed/position is a little strange and may be the source of your difficulties. For example, I would expect Velocity to be a vector representing the velocity of the player and not a normalized vector as you have. I would do something like:

SpeedX = 0.0f;
SpeedY = 0.0f;
SpeedZ = 0.0f

if(IsKeyDown('A')) SpeedX += -0.02f;
if(IsKeyDown('D')) SpeedX += 0.02f;
...

Velocity.x += SpeedX;
Velocity.y += SpeedY;
Velocity.z += SpeedZ;

D3DXVECTOR3 NormVelocity;
D3DXVec3Normalize(&NormVelocity, &Velocity);

//Round velocity here if you need to
Velocity.x = floor(Velocity.x * 10000) / 10000.0f;
...

float FrameTime = 1;  //Use last frame time here

PosDelta.x = Velocity.x * FrameTime;
PosDelta.y = Velocity.y * FrameTime;
PosDelta.z = Velocity.z * FrameTime;

That gets rid of your velocity changing when moving in more than one direction. It also lets you properly compensate for changing frame rates if you set the FrameTime to be the time of the last frame (or a value derived from it). It also correctly stops the player from moving when they try to move in two opposite directions at once.

As for your last question regarding the decay of the Y-velocity there are a few ways to do it. You could simply do something like:

Velocity.y *= 0.7f;

every frame (adjust the constant to suit your needs). A more accurate model would be to do something like:

if (Velocity.y > 0) {
    Velocity.y -= 0.001f;    //Pick constant to suit
    if (Velocity.y < 0) Velocity.y = 0;
}

An even better way would be to use the last frame time to account for varying frame rates like:

Velocity.y -= FrameTime * 0.2f;    //Pick constant to suit