I am making keyboard controls for a camera, and I am experiencing issues regarding the innacuracy of floats. I have the variables "float Xvelocity" and "float Zvelocity", I increment these velocities by adding or subtracting the acceleration speed every time the respective direction keys are being held (A, D, W or S).
However, everything I try to make the float go towards zero simply work in very strange ways. I thought it would be as simple as:
if (keys[GLFW_KEY_A] & xVelocity >-speed)
xVelocity -= acceleration / deltaTime;
if (keys[GLFW_KEY_D] & xVelocity < speed)
xVelocity += acceleration / deltaTime;
if (keys[GLFW_KEY_W] & zVelocity >-speed)
zVelocity -= acceleration / deltaTime;
if (keys[GLFW_KEY_S] & zVelocity < speed)
zVelocity += acceleration / deltaTime;
if (!keys[GLFW_KEY_A] & xVelocity<0.f) {
xVelocity+=deceleration / deltaTime;
}
if (!keys[GLFW_KEY_D] & xVelocity>0.f) {
xVelocity-=deceleration / deltaTime;
}
if (!keys[GLFW_KEY_W] & zVelocity<0.f) {
zVelocity+=deceleration / deltaTime;
}
if (!keys[GLFW_KEY_S] & zVelocity>0.f) {
zVelocity-=deceleration / deltaTime;
}
origin -= right * xVelocity;
origin -= vec3(front.x, 0.f, front.z) * zVelocity;
This doesn't make my camera completely stop moving! It does decelerate, but it rarely becomes exactly zero, thus it keeps slowly moving after releasing the key, either towards the direction it was going before or away from it. Can someone explain why doesn't this deceleration work as I intended, (probably because of float inaccuracy, but maybe it's another reason) and how may I proceed to fix it?
This is not a floating-point accuracy issue unless your acceleration, deltaTime, and velocity are calibrated so that, mathematically, velocity reaches zero exactly after an integer number of steps. More likely, the problem is simply that your velocity is not an exact multiple of acceleration/deltaTime, even allowing for floating-point issues. For example, suppose velocity is 3.7 and acceleration/deltaTime is .2. Then velocity will step through 3.5, 3.3, 3.1,… .5, .3, .1, and −.1.
So velocity never becomes zero because it crosses right through it. When velocity is at .1 units, you can never decelerate by .2 units; an object stops when its velocity reaches zero.
A solution is to detect this and clamp the result to zero:
if (!keys[GLFW_KEY_A] & xVelocity<0.f)
{
xVelocity = xVelocity >= - deceleration / deltaTime ? 0 : xVelocity + deceleration / deltaTime;
}
Related
I have these functions:
void apply_gravity(double delta){
velocity.y+=9.8*100*delta;
}
void move_body(double delta){
location.x+=velocity.x*delta;
location.y+=velocity.y*delta;
}
void processPhysics(double delta){
apply_gravity(delta);
move_body(delta);
if(location.y>=SCREENY){
velocity.y=-coefficient_of_restitution*velocity.y;
}
}
delta is the time elapsed between two calls to the function.
velocity contains two parts, x and y which represent the increment per second to the location's x and y.
coefficient of restitution represents how much of the original velocity the body retains after collision.
Basically, here is what I want this code to do:
Accelerate downwards by 9.8*100px per second. When the body goes below a limit (SCREENY px), it should bounce back, just like if it is hitting a floor. The collision should be perfectly elastic, and for now, SCREENY does not vary at all.
The code perfectly works for most of the times. BUT, sometimes, instead of "bouncing", the body just passes through the "floor". Basically, it seems that velocity.y does NOT negate even when the body crosses SCREENY. The comparision (location.y>=SCREENY) just fails at random times. Sometimes it works, sometimes it doesn't.
That should not happen.
What is going wrong here?
Your object is still allowed to pass through the floor, only updating the velocity. However, in the next frame, the object may still be in the floor, and have its velocity negated a second time (and third, and fourth). I imagine it would look like it gets stuck and vibrates a bit of a distance within the wall. If the object is within the wall, you should instead calculate where it should be if it had bounced, and update the position AND velocity accordingly. Not just the velocity.
A possible solution could look like:
void apply_gravity(double delta){
velocity.y += 9.8 * 100 * delta;
}
void move_body(double delta){
location.x += velocity.x * delta;
location.y += velocity.y * delta;
}
void processPhysics(double delta){
apply_gravity(delta);
move_body(delta);
if(location.y > SCREENY) // change to > from >= (eliminates edge case)
{
location.y = SCREENY - coefficient_of_restitution * (location.y - SCREENY); // update position as well, accounting for loss of velocity due to bounce
velocity.y = -coefficient_of_restitution * velocity.y;
}
}
The above solution will yield an error if the object is moving greater than one screen length in a single physics tic, so be careful about high speeds. You'll need a slightly more complicated algorithm to handle those situations.
I'm currently trying to get a form of gravity (it doesn't need to be EXACTLY gravity, no realism required) into my platformer game, however I'm stumbling over logic on this.
The following code is what I use when the up arrow or W is pressed, (jumping)
if (grounded_)
{
velocity_.y -= JUMP_POWER;
grounded_ = false;
}
In my Player::Update() function I have
velocity_.y += GRAVITY;
There's more in that function but it's irrelevant to the situation.
Currently the two constants are as follows: GRAVITY = 9.8f; and JUMP_POWER = 150.0f;
The main issue I'm having with my gravity that I cannot find the proper balance between my sprite being able to make his jumps, and being way too floaty.
Long story short, my questions is that my sprite's jumps as well as his regular falling from one platform to another are too floaty, any ideas on how to scale it back to something a tad more realistic?
Instead of thinking in terms of the actual values, think in terms of their consequences.
So, the initial velocity is -jump_power, and the acceleration gravity. A little calculus gives
y = -Height = -jump_power * t + 1/2 * gravity * t^2
This assumes a small time step.
Then, the
time_in_flight = 2 * time_to_vertex = jump_power/gravity
and the vertex is
height(time_to_vertex) = jump_power^2/(4 * gravity)
Solving these, and adjusting for time step and fixing negatives
jump_power = (4 * height / time) * timestep_in_secs_per_update
gravity = (2 * jump_power / time) * timestep_in_secs_per_update
That way, you can mess with time and height instead of the less direct parameters. Just use the equations to gravity and jump_power at the start.
const int time = 1.5; //seconds
const int height = 100 //pixels
const int jump_power = (4 * height / time) * timestep_in_secs_per_update;
const int gravity = (2 * jump_power / time) * timestep_in_secs_per_update;
This is a technique from maths, often used to rearrange a family of differential equations in terms of 'dimensionless' variables. This way the variables won't interfere when you try to manipulate the equations characteristics. In this case, you can set the time and keep it constant while changing the power. The sprite will still take the same time to land.
Of course 'real' gravity might not be the best solution. You could set gravity low and just lower the character's height while they are not grounded.
You need think unit system correctly.
The unit of the gravity is meter per second squared. ( m/(s*s) )
The unit of a velocity is meter per second. ( m/s )
The unit of a force is Newton. ( N = kg*m/(s*s) )
Concept example:
float gravity = -9.8; // m/(s*s)
float delta_time = 33.333333e-3f; // s
float mass = 10.0f; // Kg
float force = grounded_ ? 150.0f : gravity; // kg*m/(s*s)
float acceleration = force / mass; // m/(s*s)
float velocity += acceleration * delta_time; // m/s
float position += velocity * delta; // m
It is based on the basic Newton's Equation of motion and Euler's Method.
I feel this is a difficult question to articulate, so I have illustrated on this graph (I am using SDL in C++).
Each square represents a pixel on the screen, I want the red pixel to move at the same speed regardless of direction.
If the speed is 8 pixels/sec then after 1 second:
If the user input is right OR down the pixel will arrive at the position marked in blue
If the user input is right AND down it will arrive at the position marked green.
In both cases the pixel has been displaced by 8 pixels, however.. The euclidean distance between red and blue = 8.00 and red and green = 11.31. I want the pixel to arrive at yellow instead.
So I have tried to correct this by declaring a constant speed, then I divide this by the actual displacement, giving me a number I use to multiple the X and Y coordinates and travel back along the trajectory, limiting my speed.
The code looks sorta like this (I have commented the area of interest):
float velX = 0, velY = 0, currentX, currentY;
int time = 0, speed = 300;
//Events
void handleInput(){
if( event.type == SDL_KEYDOWN ){
switch( event.key.keysym.sym ){
case SDLK_UP: {velY -= speed;} break;
case SDLK_DOWN: {velY += speed;} break;
case SDLK_LEFT: {velX -= speed;} break;
case SDLK_RIGHT: {velX += speed;} break;
}
}
else if( event.type == SDL_KEYUP ){
//do the opposite
}
}
//Logic
void move(){
//float dist = sqrt( (velX*velX) + (velY*velY) );
//
//if(dist > 0){
// velX *= speed / dist;
// velY *= speed / dist;
//}
currentX += velX * (get_delta_ticks(&time) / 1000.f);
currentY += velY * (get_delta_ticks(&time) / 1000.f);
set_delta_ticks(&time);
}
//Render
void Player::render(){
apply_surface(currentX, currentY, spriteSheet, screen, ¤tClip);
}
So here is my question, I am new to programming games and I'm unsure if this is the CORRECT way to be doing movement.. It seems a bit inefficient in ways, should I be trying to deduce the position based on an angle and the length of the hypotenuse instead? I don't know very much about trigonometry but of course I am keen to learn.
Separate the logical position from the display position.
The logical position will probably need to use floating-point coordinates, and you'll round them to integer pixel coordinates for the display position. You can even do anti-aliasing with this if you want to smooth the movement.
So:
right would have logical unit vector (x,y)=(1.0,0.0)
down would have logical unit vector (x,y)=(0.0,-1.0)
down+right would have logical unit vector (x,y)=(1/sqrt(2),-1/sqrt(2))
every 1/8th of a second, you add the unit vector to your current logical location, and select which pixel to draw. Obviously you can choose different units and update frequencies, but this will give the numbers you asked for.
You need to get the speed in a 2D Space. To get it you have to do a sqrt with both speeds.
curSpeed = sqrt( ( velX * velX ) + (velY * velY ) );
The point is: You counted 8-x and 8-y key press events, which lead to a shortest distance from the origin of v=sqrt(8*8+8*8)=11.31, exactly as you observed.
You should be aware, that, within the time you are measuring, either 8 (only x OR y) or 16 (x plus y) key press events might be sampled, resulting in different "speeds", where speed=number_of_key_events/period_of_time
If you want to travel to the "yellow" spot, there should be only 6 X key press events plus 6 Y key press events in the same period of time in which you sampled the 8 key presses in one of the basic directions.
So there is nothing wrong with your code, and, as the other posters pointed out, your euclidian speed can be calculated using the euclidian distance divided by the sampling period, resulting in v=8 or v=11.31, respectively.
I would start with different user controls: namely absolute speed and direction.
Given speed velAbs and the angle theta, you have
velX = velAbs * cos(theta);
velY = velAbs * sin(theta);
When updating the position, it is typically most convenient to decompose the absolute speed in its X and Y components, update the X and Y positions for the given time interval dt
currentX = velX * dt;
currentY = velY * dt;
whereas for collision impact computations the absolute speed is more relevant.
This will avoid your yellow/green problem because maximum throttle in both the X and Y directions will get you to green. Just let the user set the throttle from 0 to 8 and also set a direction, then you will get to yellow or blue.
Well, it looks like most people forgot about analog input...
Anyway, It should work like this:
velX, velY are floats within [-1.0..1.0] range.
In case of digital input (keyboard, dpad), pressing "left" sets velX to -1, pressing "right" sets velX to 1, etc.
However, if you use analog stick, you put floating point values, where velX == 1.0 corresponds to rightmost position of analog stick, velX == -1.0 corresponds to leftmost position, and so on.
maxSpeed is maximum game movement speed, also float.
With all this in mind, you could calculate next position of object like this:
void move(){
float curVelX = velX, curVelY = velY;
float moveSquared = (curVelX*curVelX + curVelY*curVelY);
if (moveSquared > 1.0f){
float d = sqrtf(moveSquared);
curVelX /= d;
curVelY /= d;
}
currentX += curVelX * maxSpeed * (get_delta_ticks(&time) / 1000.f);
currentY += curVelY * maxSpeed * (get_delta_ticks(&time) / 1000.f);
set_delta_ticks(&time);
}
It seems a bit inefficient in ways,
Look, you have ONE object. When you'll have few hundreds of thousands of them, then you can start worrying about efficiency.
should I be trying to deduce the position based on an angle and the length of the hypotenuse instead?
If your object is torpedo-like and can slowly turn left/right and accelerate/decelerate (you can steer it a bit and make it go faster/slower), then you probably need to store movement direction and linear movement speed.
If your object is some kind of flying orb or rolling ball that can go in any direction it wants, then you should use method similar to the one I described. Have separate velocity for x/y and limit maximum linear velocity using sqrtf.
I'm trying to make an asteroids clone and so far I've gotten my ship to fly. However it's speed is also dependent on FPS. So to mitigate that I've read that I'd have to multiply my control variables with deltaTime (time between frames if i gathered right). However when I tried implementing that the ship refused to move. I thought that it's due to possible implicit rounding to 0 (converting to int?) but there are no warnings issued. What am I doing wrong?
Here's how the code looks:
sf::Vector2f newPosition(0,0);
sf::Vector2f velocity(0,0);
float acceleration = 3.0f;
float angle = 0;
float angularVelocity = 5;
float velDecay = 0.99f;
sf::Clock deltaClock;
window.setFramerateLimit(60);
while (window.isOpen())
{
sf::Time deltaTime = deltaClock.restart();
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed || ((event.type == sf::Event::KeyPressed) && (event.key.code == sf::Keyboard::Escape)))
window.close();
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up))
{
if(velocity.x < 10)velocity.x += acceleration * deltaTime.asSeconds();
if(velocity.y < 10)velocity.y += acceleration * deltaTime.asSeconds();
angle = player.getRotation() - 90;
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down))
{
if(velocity.x > 0)velocity.x -= acceleration * deltaTime.asSeconds();
else velocity.x = 0;
if(velocity.y > 0)velocity.y -= acceleration * deltaTime.asSeconds();
else velocity.y = 0;
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left))
{
player.rotate(-angularVelocity);
}
if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right))
{
player.rotate(angularVelocity);
}
newPosition.x = player.getPosition().x + (velocity.x * cos(angle * (M_PI / 180.0))) * deltaTime.asSeconds();
newPosition.y = player.getPosition().y + (velocity.y * sin(angle * (M_PI / 180.0))) * deltaTime.asSeconds();
player.setPosition(newPosition);
velocity.x *= velDecay;
velocity.y *= velDecay;
window.clear();
window.draw(background);
window.draw(player);
window.draw(debugText);
window.display();
}
I cannot run your code, thus I cannot be 100% sure, but the following does not look correct:
Calculation of Velocity
velocity.x = (player.getPosition().x + (acceleration * cos(angle * (M_PI / 180.0)) * deltaTime.asSeconds()));
velocity.y = (player.getPosition().y + (acceleration * sin(angle * (M_PI / 180.0)) * deltaTime.asSeconds()));
Try changing it to something like this:
velocity.x = (player.getVelocity().x + (acceleration * cos(angle * (M_PI / 180.0)) * deltaTime.asSeconds()));
velocity.y = (player.getVelocity().y + (acceleration * sin(angle * (M_PI / 180.0)) * deltaTime.asSeconds()));
This is utilizing the simple physics equation vf = vi + a*t but in a x,y component fashion. I believe using Position.x and Position.y would totally throw off that equation.
Note: syntax wise, the code I gave you might not work. Put whatever code into player.getVelocity().x that will get you the current velocity of the player in the X direction. Do the same for the Y direction.
Setting of the new position
I cannot be sure if player.setPosition(velocity); is proper or not. If the setPosition function takes care of doing the following:
newPosition.x = oldPosition.x + (velocity.x/dt)
both in the x and y direction, then that should work.
But if it is simply doing:
newPosition.x = velocity.x
Then I believe this will be wrong and result in improper simulation.
Overall
There could be other mathematical errors in your code. Especially with how you are calculating acceleration. I did not double check this and at the moment I do not have time to. If you make the adjustments I mentioned and its still not working, throw me a comment and I can try looking more when I have the time. I have a game of my own to go work on right now.
Edit1:
Your code looks a lot better. From here, I would add code that changes your acceleration. Say hitting the w key will turn on "thrusters" which give you an acceleration of 2. The acceleration will degrade(go back towards zero)over TIME when no key is being pressed. Not per frame. Before you were multiplying by .99 per frame. Which means acceleration could be zero in half a second if you are getting 120 fps (totally possible in a simple game like this). You need to have it degrade based on your dt variable. Once it hits zero however, you will still have a positive velocity. Usually this velocity is degraded over time due to gravity, but being in space gravity would be very small compared to what you find on earth (-9.8m/s or -32 ft/s). So perhaps you could implement a gravity falloff on your velocity which is also calculated in time
OR
you could ignore gravity and allow them to hit the S key and apply a negative acceleration (-2) and then apply that to your velocity as you have done. This would allow for negative values to degrade your velocity and could be thought of as your ships turning on thrusters in the opposite direction.
Of course you can cheat as the game developer and prevent your velocity from ever going below zero(if you want the player to only move forward) And when you detect a velocity that is negative, set Velocity to 0 and acceleration to 0.
Note: the acceleration "degrading" will happen for both positive and negative, and it will "degrade" towards zero. You will have to tinker with these values and play test to see what feels right. Should acceleration degrade 1 per second? Should you even use the value of 2 and -2 as the acceleration values I mentioned earlier? 2 and -2 might work, but maybe 3 and -3 are better? These are all questions you get to answer yourself through testing it out.
I hope this gives you some more ideas and helps solve your question fully! Let me know how it goes.
My particle system's physics update function seems to be incorrect. I'm aiming for all the particles to be attracted towards the mouse.
The particles move towards the mouse pointer just as expected, until they go very near. When they are near, they speed up so much, that they fly far away from the pointer and never return.
Here's the update function:
void updateParticle(particle& p,double time){
const double G=0.000000000066726;
const double POINTERMASS=1000000000000;
double squareDistance=pow(p.coords.x-pointerDevice.x,2)+pow(p.coords.y-pointerDevice.y,2)+pow(p.coords.z-pointerDevice.z,2);
if(squareDistance<0.001)
squareDistance=0.001;//to fix the possible division by zero
coords_3d_f accelerationVector={p.coords.x-pointerDevice.x,p.coords.y-pointerDevice.y,p.coords.z-pointerDevice.z};
accelerationVector=vector_scalar_multiplication(vector_unit(accelerationVector),((G*POINTERMASS)/squareDistance));
accelerationVector=vector_scalar_multiplication(accelerationVector,time);
p.velocity=vector_addition(p.velocity,accelerationVector);
p.coords.x-=p.velocity.x*time;
p.coords.y-=p.velocity.y*time;
p.coords.z-=p.velocity.z*time;
}
When the squareDistance is constant, the program looks OK, but I know it's false.
So, what am I doing wrong?
Force is inversely proportional to the square of the distance, so as the distance approaches 0, force (and acceleration) approach infinity. In other words, if the particles get very close, they also get very fast.
If you want to be physically accurate, make your pointer-object have a finite size, so that particles bounce off of it.
If you don't need to be accurate, you can make the force decrease when the particles are very close.
It's very simple: when particles get in touch with the mouse pointer squareDistance becomes 0 and produces undefined behavior for your particles by ((G*POINTERMASS)/squareDistance) because dividing by zero is illegal.
This might work better for you:
if (squareDistance >= 1.0) // 1.0 is the zero tolerance for your context of pixel distances
{
// proceed normally
accelerationVector=vector_scalar_multiplication(vector_unit(accelerationVector),((G*POINTERMASS)/squareDistance));
accelerationVector=vector_scalar_multiplication(accelerationVector,time);
}
else
{
// no acceleration
accelerationVector=/*{0, 0}*/;
}
When your particle gets very close to the mouse pointer the particle is going to have a very high velocity. When this velocity is multiplied by the time this is when the particle will jump very far away.
You can try to fix this by setting a maximum velocity.
Simulating the equation of motion involve the integration of a function at finite intervals, and so one can only approximate the function. This give rise to instability in the system. An easy and fast solution is to use a fixed-step verlet integration:
void integrate(particle& p, double t2, const particle& mouse)
{
// universal gravitational constant
const double G = 0.000000000066726;
// artificial drag
// set it to 1.0 to not have any drag
// set it to 0.0 to not have any momentum
const double drag = 0.99;
// get direction and distance between the particle and the mouse
dvec3 dir = p.pos - mouse.pos;
double dist2 = dot(dir, dir);
double dist = sqrt(dist2);
dir /= dist;
// calculate relative acceleration vector
dvec3 a = -dir * G * (p.mass + mouse.mass) / dist2;
// verlet integration
dvec3 tmp = p.pos;
p.pos += (p.pos - p.prev_pos) * drag + a * t2;
p.prev_pos = tmp;
}
void update(particle& p, double elapsed, const particle& mouse, double& accumulator)
{
// fixed timestep (arbitrary)
const double timestep = 1.0 / 120.0;
const double timestep2 = timestep * timestep;
// "accumulate" time
accumulator += elapsed;
// "consume" time
while(accumulator > timestep)
{
// perform integration
integrate(p, timestep2, mouse);
accumulator -= timestep;
}
}
Note: It use the GLM math library for clarity.