Direct3D9 game: Spaceship camera - c++

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.

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

Related

Smooth movement of player SFML

void update(RenderWindow& window)
{
if (Keyboard::isKeyPressed(Keyboard::W))
{
dy = -0.3;
}
if (Keyboard::isKeyPressed(Keyboard::A))
{
dx = -0.3;
}
if (Keyboard::isKeyPressed(Keyboard::S))
{
dy = 0.3;
}
if (Keyboard::isKeyPressed(Keyboard::D))
{
dx = 0.3;
}
x += dx;
y += dy;
dx = dy = 0;
EntitySprite.setPosition(x, y);
window.draw(EntitySprite);
}
When the movement is described in such a code, the player moves angularly: either to the left, or up, or to the right, or down, or diagonally at an angle of 45 degrees by pressing two buttons together, for example S and D. Can this angle be made smoother so that the movement itself was carried out not only to the left, right, diagonally, etc.? My knowledge of geometry is not enough here, so I ask for your help).
In your code, the user input directly modifies the player position. This is probably the reason why the player movement looks so abrupt to you. Technically, in your code, the user input is what is determining the player velocity at any given moment.
A more realistic approach would be to let the player have a velocity property instead –
that represents the player position's rate of change – and then have the player position updated only through this velocity, not directly from the user input. Instead, the velocity would be what is directly modified by the input, but not entirely determined by the current input as it will also depend on its previous value.
Following this approach, the user input is used to calculate the player acceleration in each call to update(). This acceleration – the rate of change of the velocity – is used to update the player velocity directly. Finally, the player velocity is, in turn, used to update the player position.
The following code implements this approach by introducing the velocity_ data member and the acceleration local variable:
void update(RenderWindow& window) {
sf::Vector2f acceleration;
// adjust this at will
const float dAcc = 0.3f;
// set acceleration
if (Keyboard::isKeyPressed(Keyboard::W))
acceleration.y -= dAcc;
if (Keyboard::isKeyPressed(Keyboard::A))
acceleration.x -= dAcc;
if (Keyboard::isKeyPressed(Keyboard::S))
acceleration.y += dAcc;
if (Keyboard::isKeyPressed(Keyboard::D))
acceleration.x += dAcc;
// update velocity through acceleration
velocity_ += acceleration;
// update position through velocity
x += velocity_.x;
y += velocity_.y;
// apply damping to the velocity
velocity_ = 0.99f * velocity_;
EntitySprite.setPosition(x, y);
window.draw(EntitySprite);
};
This way, the player possesses some kind of inertia, and its movement looks smoother.
Note that you may want to have some damping for the velocity as in:
velocity_ = 0.99f * velocity_;
This will resemble the effect of drag forces.
with help of that man I did it like this, but some better)
void update(RenderWindow& window)
{
float decceleration = 0.3;
if (Keyboard::isKeyPressed(Keyboard::W))
{
accelerationY -= decceleration;
}
if (Keyboard::isKeyPressed(Keyboard::S))
{
accelerationY += decceleration;
}
if (Keyboard::isKeyPressed(Keyboard::A))
{
accelerationX -= decceleration;
}
if (Keyboard::isKeyPressed(Keyboard::D))
{
accelerationX += decceleration;
}
dx += accelerationX;
dy += accelerationY;
speed = sqrt(dx * dx + dy * dy);
if (speed > maxSpeed)
{
dx *= maxSpeed / speed;
dy *= maxSpeed / speed;
}
x += dx;
y += dy;
dx *= 0.9;
dy *= 0.9;
accelerationX = 0;
accelerationY = 0;
EntitySprite.setPosition(x, y);
window.draw(EntitySprite);
}

How to create bounded bounce animation? (SFML)

I have to move a ball in an angle in an open SFML and keep it within the window size (Like the DVD thing), but my current function makes it to the bottom and doesn't "bounce". It slides across the bottom and stops once it reaches the other corner. The initial position is (1,1)
void Bubble::updatePosition() {
if( isTopBottom() ){
do{
_x += .1;
_y += -.2;
}while( !isTopBottom() );
}
else if( isLeftRight() ){
do{
_x += -.1;
_y += .2;
}while( !isLeftRight() );
}
else{
_x += .1;
_y += .2;
}
_bubble.setPosition(_x, _y);
}
the isLeftRight, isTopBottom are bools that check if they have reached the edges
Simple Solution
Use velocities and manipulate those on collision; then, use the velocity to update the position.
Check each edge separately and decide on one relevant velocity component based on that.
e.g. (following your values)
// Positions:
float x = 1.f;
float y = 1.f;
// Velocities:
float vx = 0.1f;
float vy = 0.2f;
// ... then, inside loop:
// Check collisions (and adjust velocity):
if (x < 0.f)
vx = 0.1f;
else if (x > 640.f)
vx = -0.1f;
if (y < 0.f)
vy = 0.2f;
else if (y > 640.f)
vy = -0.2f;
// update position (still inside loop):
x += vx;
y += vy;
Cleaner Solution
This is the same as the simple solution above but, since you tagged SFML, you can use SFML vectors to keep the two components together. Also modified variable names to be more clear. Pulled out the size of the window and the velocity amounts from being hard-coded into the logic as well:
const sf::Vector2f windowSize(640.f, 640.f);
const sf::Vector2f velocityAmount(0.1f, 0.2f);
sf::Vector2f position(1.f, 1.f);
sf::Vector2f velocity = velocityAmount;
// ... then, inside loop:
// Check collisions (and adjust velocity):
if (position.x < 0.f)
velocity.x = velocityAmount.x;
else if (position.x > windowSize.x)
velocity.x = -velocityAmount.x;
if (position.y < 0.f)
velocity.y = velocityAmount.y;
else if (position.y > windowSize.y)
velocity.y = -velocityAmount.y;
// update position (still inside loop):
position += velocity;
You should notice that the velocity is the values that are added to the position on each iteration of the loop and that velocity does not change when it is not considered colliding with an edge.
Initial Problem
The initial problem you had is it always moves in the same direction (towards the bottom-right) if it is not hitting an edge. This means that it'll never be allowed to rise above the bottom edge (or away from right edge).

OpenGL First person camera

I'm trying to do a first cam person using OPENGL. But here's my problem.
Let me introduce you my code.
First of all, I have this function that allows me to get the mouse X and Y position:
case WM_MOUSEMOVE:
CameraManager.oldMouseX = CameraManager.mouseX;
CameraManager.oldMouseY = CameraManager.mouseY;
CameraManager.mouseX = GET_X_LPARAM(lParam);
CameraManager.mouseY = GET_Y_LPARAM(lParam);
mousePoint.x = CameraManager.mouseX - CameraManager.oldMouseX;
mousePoint.y = CameraManager.mouseY - CameraManager.oldMouseY;
mousePoint.z = 0.f;
CameraManager.lookAt(mousePoint);
App.onRender();
break;
Here I get the difference between the old mouse position and the new one (just because I want to know when I have to increase/decrease the angle). Then, I call the lookAt function on my CameraManager.
Here I do the following:
if (point.x > 0.f) {
camAngle.x -= 0.3f;
}
else if (point.x < 0.f) {
camAngle.x += 0.3f ;
}
float radiansX = MathUtils::CalculateRadians(camAngle.x);
//eye.x = sinf(radiansX);
//center.x += sinf(radiansX);
//center.z += (-cosf(radiansX));
Update();
And then my update does:
glLoadIdentity();
gluLookAt(eye.x, eye.y, eye.z,
center.x, center.y, center.z,
up.x, up.y, up.z);
I've read a lot of stuff on how to refresh the eye and center, but I couldn't get anything to work.
Any advices?

How to scale the rotation of a quaternion

I am trying to do the equivalent of multiplying the velocity by the time between frames. I would imagine that doing this for quaternions would be done by raising them to a power. I have code to rotate an object based on my mouse movements. It has a main loop running at one frame rate and a physics loop running at a fixed frame rate. Here is the relevant part of the main loop:
glfwPollEvents();
Input::update();
window.clear(0,0,0,1);
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().x, glm::vec3(0,1,0));
rigidBody.angularVelocity *= glm::angleAxis(0.001f * Input::deltaMouse().y, glm::vec3(1,0,0));
if(Input::getKey(Input::KEY_A))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_D))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(1,0,0);
}
if(Input::getKey(Input::KEY_W))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_S))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,0,1);
}
if(Input::getKey(Input::KEY_LCONTROL))
{
rigidBody.velocity -= float(Time::getDelta()) * glm::vec3(0,1,0);
}
if(Input::getKey(Input::KEY_LSHIFT))
{
rigidBody.velocity += float(Time::getDelta()) * glm::vec3(0,1,0);
}
Here is the relevant part of the physics loop:
for(int i = 0; i < *numRigidBodies; i++)
{
rigidBodies[i].transform->getPos() += rigidBodies[i].velocity;
rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
}
rigidBodies[0].angularVelocity = glm::quat();
rigidBodies[0].velocity = glm::vec3();
This works fine, but when I try raising angular velocity to a power with glm::pow, the object rotates randomly and does not follow my mouse. I realize I could do this with a line of code like
rigidBodies[i].transform->getRot() *= glm::angleAxis((float)Time::getFixedDelta() * glm::angle(rigidBodies[i].angularVelocity), glm::axis(rigidBodies[i].angularVelocity));
but this seems needlessly complicated for the task. What is causing this issue, and how can I fix it?
Not sure exactly how to do it with the API you're using, but basically, you would use Quaternion::Slerp(). Slerp means "spherical linear interpolation".
Something like this(pseudocode) should work:
auto& rot = rigidBodies[i].transform->getRot();
auto goal = rigidBodies[i].angularVelocity * rot;
rot = rot.slerp(rot, goal, Time::deltaTime);
Edit:
I should note that this is not how I would approach this problem. I would just store the rotation around the X and Y axis as scalars and construct a new quaternion from them each frame.
Please excuse the sloppy pseudo code:
// previous x and y positions, could probably be set in MouseDown event
float lastX = ...;
float lastY = ...;
float xRotation = 0;
float yRotation = 0;
float rotationSpeed = 1.0;
void OnMouseMove(float x, float y) {
float dx = x - lastX;
float dy = y - lastY;
lastX = x;
lastY = y;
xRotation += dy * rotationSpeed * Time::deltaTime;
yRotation += dx * rotationSpeed * Time::deltaTime;
rigidBodies[i].transform->getRot() = eulerQuat(xRotation, yRotation, 0);
}
Turns out angular velocity is usually represented as a 3d vector where the direction is the axis and the magnitude is the angular speed. Replace this line of code:
rigidBodies[i].transform->getRot() *= rigidBodies[i].angularVelocity;
with this:
if(rigidBodies[i].angularVelocity != glm::vec3())
rigidBodies[i].transform->getRot() *= glm::quat(rigidBodies[i].angularVelocity * float(Time::getFixedDelta()));
and the physics system works as expected. The if check makes sure that angular speed is not 0.

Bullet algorithm having trouble with rotation on the X

Here is what I'm trying to do. I'm trying to make a bullet out of the center of the screen. I have an x and y rotation angle. The problem is the Y (which is modified by rotation on the x) is really not working as intended. Here is what I have.
float yrotrad, xrotrad;
yrotrad = (Camera.roty / 180.0f * 3.141592654f);
xrotrad = (Camera.rotx / 180.0f * 3.141592654f);
Vertex3f Pos;
// get camera position
pls.x = Camera.x;
pls.y = Camera.y;
pls.z = Camera.z;
for(float i = 0; i < 60; i++)
{
//add the rotation vector
pls.x += float(sin(yrotrad)) ;
pls.z -= float(cos(yrotrad)) ;
pls.y += float(sin(twopi - xrotrad));
//translate camera coords to cube coords
Pos.x = ceil(pls.x / 3);
Pos.y = ceil((pls.y) / 3);
Pos.z = ceil(pls.z / 3);
if(!CubeIsEmpty(Pos.x,Pos.y,Pos.z)) //remove first cube that made contact
{
delete GetCube(Pos.x,Pos.y,Pos.z);
SetCube(0,Pos.x,Pos.y,Pos.z);
return;
}
}
This is almost identical to how I move the player, I add the directional vector to the camera then find which cube the player is on. If I remove the pls.y += float(sin(twopi - xrotrad)); then I clearly see that on the X and Z, everything is pointing as it should. When I add pls.y += float(sin(twopi - xrotrad)); then it almost works, but not quite, what I observed from rendering out spheres of the trajector is that the furthur up or down I look, the more offset it becomes rather than stay alligned to the camera's center. What am I doing wrong?
Thanks
What basically happens is very difficult to explain, I'd expect the bullet at time 0 to always be at the center of the screen, but it behaves oddly. If i'm looking straight at the horizon to +- 20 degrees upward its fine but then it starts not following any more.
I set up my matrix like this:
void CCubeGame::SetCameraMatrix()
{
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(Camera.rotx,1,0,0);
glRotatef(Camera.roty,0,1,0);
glRotatef(Camera.rotz,0,0,1);
glTranslatef(-Camera.x , -Camera.y,-Camera.z );
}
and change the angle like this:
void CCubeGame::MouseMove(int x, int y)
{
if(!isTrapped)
return;
int diffx = x-lastMouse.x;
int diffy = y-lastMouse.y;
lastMouse.x = x;
lastMouse.y = y;
Camera.rotx += (float) diffy * 0.2;
Camera.roty += (float) diffx * 0.2;
if(Camera.rotx > 90)
{
Camera.rotx = 90;
}
if(Camera.rotx < -90)
{
Camera.rotx = -90;
}
if(isTrapped)
if (fabs(ScreenDimensions.x/2 - x) > 1 || fabs(ScreenDimensions.y/2 - y) > 1) {
resetPointer();
}
}
You need to scale X and Z by cos(xradrot). (In other words, multiply by cos(xradrot)).
Imagine you're pointing straight down the Z axis but looking straight up. You don't want the bullet to shoot down the Z axis at all, this is why you need to scale it. (It's basically the same thing that you're doing between X and Z, but now doing it on the XZ vector and Y.)
pls.x += float(sin(yrotrad)*cos(xrotrad)) ;
pls.z -= float(cos(yrotrad)*cos(xrotrad)) ;
pls.y += float(sin(twopi - xrotrad));