How to fix time step in box2d so that sprites move smoothly? - c++

The following is my timestep:
void step(float dt){
static double UPDATE_INTERVAL = 1.0f/60.0f;
static double MAX_CYCLES_PER_FRAME = 5;
static double timeAccumulator = 0;
timeAccumulator += dt;
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL))
{
timeAccumulator = UPDATE_INTERVAL;
}
int32 velocityIterations = 3;
int32 positionIterations = 2;
while (timeAccumulator >= UPDATE_INTERVAL)
{
timeAccumulator -= UPDATE_INTERVAL;
this->world->Step(UPDATE_INTERVAL,
velocityIterations, positionIterations);
this->world->ClearForces();
}
}
While the game as such works great regardless of the framerate, it causes sprites to "tremble", or "stutter" even when the framerate is 60fps!
I think this is because each frame the sprite moves by a different amount, because each frame the number of times the while loop executes is different.
What is a better way to fix the time step ? I have read a number of articles on fixing the time step, and am very confused. The above "works" except for the stutter.
EDIT: Just to clarify, the tremble is very small! People with poor eyesight don't notice it, but it makes a game look very low budget if you look carefully. It makes the game look not smooth.

you don't clamp your timeAccumulator correctly:
timeAccumulator += dt;
if (timeAccumulator > (MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL))
{
timeAccumulator = UPDATE_INTERVAL;
}
should be
timeAccumulator = std::min(dt + timeAccumulator, MAX_CYCLES_PER_FRAME * UPDATE_INTERVAL);
Concerning the stepping: you'll need to interpolate the last full step with the next full step according to the left-over in timeAccumulator. There was a tutorial on this by Allan Bishop though the original site is no longer reachable (blog.allanbishop.com). Here's how his smooth step (only that last one with the fractional step) looks like:
// for the remaining timeAccumulator < UPDATE_INTERVAL
fixedTimestepAccumulatorRatio = timeAccumulator / UPDATE_INTERVAL;
private function smoothStates():void
{
const oneMinusRatio:Number = 1.0 - fixedTimestepAccumulatorRatio;
//Makes it easier to wrap up the Box2D body in another object so we can track other variables.
for each (var body:Body in bodies)
{
var box2Dbody = body.box2DBody;
texture.position.x = fixedTimestepAccumulatorRatio * box2Dbody.GetPosition().x + oneMinusRatio * body.previousPosition.x;
texture.position.y = fixedTimestepAccumulatorRatio * box2Dbody.GetPosition().y + oneMinusRatio * body.previousPosition.y;
texture.rotation = box2Dbody.GetAngle() * fixedTimestepAccumulatorRatio + oneMinusRatio * body.previousAngle;
}
}

Related

C++ / SDL2 - Ball bouncing/glitching together

I was trying to write some ball bouncing program in C++ using SDL2. I had a hard time getting the velocity exchange correct, but it works pretty neat so far. The only problem I have right now is that the balls are sometimes glitching/stucking together and after some seconds they release themself again.
That is my update() function which gets called every frame:
void Game::update() {
updateFPS();
checkBallCollision();
updateCanCollide();
int newtime = SDL_GetTicks();
int diff = newtime - lasttime;
if (diff > 10)
diff = 10;
for (Ball *ball : balls) {
ball->x = ball->x + ball->velocity->x * (float) diff / 100;
ball->y = ball->y + ball->velocity->y * (float) diff / 100;
checkBorderCollision(ball);
}
lasttime = newtime;
}
I guess that the balls are getting to close and don't bounce at the border of the balls. Therefore I tried to give every ball a boolean canCollide which is always true except a ball is colliding. Then it stays false until the two balls aren't overlapping anymore.
Here are my checkBallCollision() and updateCanCollide() functions:`
void Game::updateCanCollide() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (ballArr[i]->canCollide)
continue;
bool updatedCollide = true;
for (int k = i + 1; k < length; k++) {
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
updatedCollide = false;
}
}
ballArr[i]->canCollide = updatedCollide;
}
}
// do all collision checks and update the velocity
void Game::checkBallCollision() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (!ballArr[i]->canCollide)
continue;
for (int k = i + 1; k < length; k++) {
if (!ballArr[k]->canCollide)
continue;
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
// ball1 and ball2 are colliding
// update the velocity of both balls
float m1 = ball1->radius * ball1->radius * 3.14159;
float m2 = ball2->radius * ball2->radius * 3.14159;
Vector2D *v1 = new Vector2D(ball1->velocity->x, ball1->velocity->x);
Vector2D *v2 = new Vector2D(ball2->velocity->x, ball2->velocity->x);
ball1->velocity->x = ((v1->x * (m1 - m2) + 2 * m2 * v2->x) / (m1 + m2));
ball1->velocity->y = ((v1->y * (m1 - m2) + 2 * m2 * v2->y) / (m1 + m2));
ball2->velocity->x = ((v2->x * (m2 - m1) + 2 * m1 * v1->x) / (m1 + m2));
ball2->velocity->y = ((v2->y * (m2 - m1) + 2 * m1 * v1->y) / (m1 + m2));
ball1->canCollide = false;
ball2->canCollide = false;
}
}
}
}
The proper fix
The main problem is that you are letting the balls overlap each other, then update their velocities. However, if the next time step is shorter than the previous one, it can be that after updating their positions, they are still overlapping. Then you think they are colliding again, and update their velocities, but this will most likely cause then to move closer together again. This explains why they get stuck.
The proper wait to solve this is to calculate the exact point in time that two moving balls collide. This can be done analytically, for example by treating time as a third dimension, and then calculating a line-sphere intersection. If this happens during the time step, you advance the time up to the point that the collision happens, then update the velocities, and then perform the rest of the step. If you have more than two balls, then be aware that you can have more than two balls colliding all with each other in the same timestep. This is also solvable, just calculate all the time points that collisions happen, select the earliest one, update velocities at that point, and then recalculate the collision times, and so on until there are no collisions in the time step.
The workaround
Your workaround might fix two balls sticking to each other, but the result is not physically accurate. It breaks down when you start increasing the density of balls, since at some point the chance will be very high that at least one ball of a pair that should collide was in a collision in the previous timestep, and then they will all just start passing through each other all the time.
Another issue is that you have to check every possible pair of balls in updateCanCollide(), which is not efficient. There is a simpler and more common workaround to this problem: when two balls collide, after updating their velocities, immediately update their positions as well such that the balls are no longer colliding. You can try to calculate exactly how much to move them so they no longer overlap, or if you don't want to involve mathematics, you can just have a while loop to do a small step until they no longer overlap.
Other issues in your code
Note that there are also some other thing in your code that you could improve:
Don't new a temporary Vector2D, just declare it on the stack. If for some reason this is not possible, at least delete v1 and v2 afterwards.
You don't need to call abs() if you are going to square the result anyway.
Use std::hypot() to calculate the distance.
Did you write Vector2D yourself or is it from a library? If the latter, maybe it already has functions to reflect two 2D vectors? If the former, consider using a library like GLM, even if you are not using OpenGL.
Use a proper value of π. A simple, portable solution is to declare static constexpr pi = std::atan(1) * 4.

Changing code inside an unexecuted if block alters my program

I am making a physics-based where balls (gdi+ ellipses) will collide. There are 4 conditions I need to program to make the collisions work correctly. To do this I'm using if statements.
I noticed when debugging that in some cases, the code inside the if block is NOT executed as if the condition was not met - however - changing the code inside that if block will still alter the way my program works.
I've extensively debugged to ensure this is what's happening. I've also switched up the way I'm writing my if statements to ensure the logic is correct, but the problem persists.
When the game is started, I add a couple of balls to a vector.
/**
* Start a new game
*/
void CGame::NewGame()
{
this->mBalls.clear();
shared_ptr<CBall> cueBall = make_shared<CBall>(this);
cueBall->SetX(410.0);
cueBall->SetY(379.0 - (25 / 2) - 110);
cueBall->SetVelocityX(0);
cueBall->SetVelocityY(160.0);
this->mGameItems.push_back(cueBall);
shared_ptr<CBall> ball1 = make_shared<CBall>(this);
ball1->SetX(420.0);
ball1->SetY(379.0 - (25 / 2));
this->mGameItems.push_back(ball1);
}
They are then drawn on the screen by iterating through the vector CGame.mGameItems and calling a Draw function on each one.
/**
* Draw the ball with as a circle
* \param graphics The GDI+ graphics context to draw on
*/
void CBall::Draw(Gdiplus::Graphics* graphics)
{
SolidBrush colorBrush(Color(255, 1000, 1000, 1000));
graphics->FillEllipse(&colorBrush, Rect(this->GetX(), this->GetY(), this->diameter, this->diameter));
}
The redrawing and updating of the window is done in CChildView::OnPaint.
/** This function is called to draw in the window.
*
* This function is called in response to a drawing message
* whenever we need to redraw the window on the screen.
* It is responsible for painting the window.
*/
void CChildView::OnPaint()
{
CPaintDC paintDC(this); // device context for painting
CDoubleBufferDC dc(&paintDC); // device context for painting
Graphics graphics(dc.m_hDC); // Create GDI+ graphics context
mGame.OnDraw(&graphics);
// TODO: Add your message handler code here
// Do not call CWnd::OnPaint() for painting messages
if (mFirstDraw)
{
mFirstDraw = false;
SetTimer(1, FrameDuration, nullptr);
/*
* Initialize the elapsed time system
*/
LARGE_INTEGER time, freq;
QueryPerformanceCounter(&time);
QueryPerformanceFrequency(&freq);
mLastTime = time.QuadPart;
mTimeFreq = double(freq.QuadPart);
}
/*
* Compute the elapsed time since the last draw
*/
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
long long diff = time.QuadPart - mLastTime;
double elapsed = double(diff) / mTimeFreq;
mLastTime = time.QuadPart;
mGame.Update(elapsed);
}
To handle collisions of the objects, a function calls a function called Update() on a CGame object which uses the following code to check if a collision happened.
/** Handle updates for animation
* \param elapsed The time since the last update
*/
void CGame::Update(double elapsed)
{
// Collisions of balls
for (shared_ptr<CBall> ball1 : this->GetBalls())
{
for (shared_ptr<CBall> ball2 : this->GetBalls())
{
double centerXBall1 = ball1->GetX() + ball1->GetDiameter() / 2;
double centerYBall1 = ball1->GetY() + ball1->GetDiameter() / 2;
double centerXBall2 = ball2->GetX() + ball2->GetDiameter() / 2;
double centerYBall2 = ball2->GetY() + ball2->GetDiameter() / 2;
double distance = sqrt((pow((centerXBall2 - centerXBall1), 2) +
pow((centerYBall2 - centerYBall1), 2)));
if (ball1 != ball2 && (distance <= ball1->GetDiameter() || distance <= ball2->GetDiameter()))
{
// The slope of the line that passes through the center of both
// balls
double slopeThroughCenters =
(centerYBall2 - centerYBall1) / (centerXBall2 -
centerXBall1);
// The angle between a vertical and the deflection direction of
// ball 1
double angleOfVelocity1 = 0.0;
// The angle between a horizontal line and the deflection
// direction of ball 2
double angleOfVelocity2 = 0.0;
double complimentaryAngle = atan(slopeThroughCenters);
if (centerXBall1 > centerXBall2 && centerYBall1 > centerYBall2)
{
angleOfVelocity1 = PI / 2 - complimentaryAngle;
angleOfVelocity2 = (PI / 2 - complimentaryAngle) + PI / 2;
}
double initialVelocityBall1 = sqrt(pow(ball1->GetVelocityX(), 2) +
pow(ball1->GetVelocityY(), 2));
double initialVelocityBall2 = sqrt(pow(ball2->GetVelocityX(), 2) +
pow(ball2->GetVelocityY(), 2));
double finalVelocityBall1 = ((ball1->GetMass() - ball2->GetMass()) * initialVelocityBall1) /
(ball1->GetMass() + ball2->GetMass());
double finalVelocityBall2 = (2 * ball1->GetMass() * initialVelocityBall1) /
(ball1->GetMass() + ball2->GetMass());
double velocityXBall1 = finalVelocityBall2 * cos(angleOfVelocity1);
double velocityYBall1 = finalVelocityBall2 * sin(angleOfVelocity1);
double velocityXBall2 = finalVelocityBall2 * cos(angleOfVelocity2);
double velocityYBall2 = finalVelocityBall2 * sin(angleOfVelocity2);
ball1->SetVelocityX(velocityXBall1);
ball1->SetVelocityY(velocityYBall1);
ball2->SetVelocityX(velocityXBall2);
ball2->SetVelocityY(velocityYBall2);
}
}
}
for (auto ball : this->mBalls)
{
ball->Update(elapsed);
}
}
When I run the debugger, the if block:
if (centerXBall1 > centerXBall2 && centerYBall1 > centerYBall2)
{
angleOfVelocity1 = PI / 2 - complimentaryAngle;
angleOfVelocity2 = PI - complimentaryAngle;
}
Doesn't seem to be executed, however, if I change the variable's values, for example,
angleOfVelocity1 = PI;
angleOfVelocity2 = PI;
My program actually changes according to the values of those variables. How is this possible if the debugger shows that the if block isn't executed in the first place?
Sorry if my code isn't minimalistic and concise enough, but I did not want to leave out anything important for solving this problem.

Basic gravity implementation in OpenGL

I'm trying to implement jumping in my OpenGL game. My left and right 'velocity' and 'friction' is working well, but I'm not able to get my jump working in a nice arc. The player gets stuck in the air:
void keyboard() {
//Move Left
if (GetAsyncKeyState(keyA)) {
playerVelX -= 3;
}
//Move Right
if (GetAsyncKeyState(keyD)) {
playerVelX += 3;
}
//Jump
if (GetAsyncKeyState(keySpace)) {
playerVelY += 3;
}
}
void position() {
//Move on x and y axis
playerX += playerVelX;
playerY += playerVelY;
//Slow down respective axes
playerVelX *= friction;
playerVelY *= gravity;
}
while(gameRunning) {
keyboard();
position();
}
I think I've been looking at it too long. Does anyone have experience implementing gravity like this? Thanks
Your formula for applying gravity is wrong. Remember that F=m.a, v=v_0+ a*t, x=x_0 +v_0*t + 0.5*a*t^2. But the usual approach is to use Euler's method for numerical integration:
gravity=9.81;
velocity+=gravity*deltaT;
position+=velocity*deltaT;
In your code it would look like:
playerVelX *= friction;
playerVelY -= gravity;//If Y point up, so gravity acts against that.
playerX += playerVelX;
playerY += playerVelY;
If you want to have deterministic and stable simulation (which you should) then you need to have deltaTime - how much time passed since last update. See this excellent article Fix Your Timstep! . Also the code above applies the gravity constantly,even when not jumping, so be aware of that.
Summarizing all inaccuracies:
Obvious mistake m * dv/dt = -m * g so dv = -g * dt so playerVelY -= gravity * dt
In the jump, further repulsion is impossible, so
if (GetAsyncKeyState(keySpace) && (playerY <= groundLevel)) {
playerVelY += 3;
}
Gravity should not act if the player is on the surface.
if (playerY < groundLevel)
{
playerY = groundLevel;
playerVelY = 0;
}
More realistic to take into account the reaction force.
Your model playerVelX *= friction; corresponds to viscous friction. The player will never stop. Are you sure you need viscous, not dry friction?
dt should be defined as real time between updates otherwise, the result will depend on the current refresh rate. You need something like
void position() {
static double lastTime = getCurrTime();
double currTime = getCurrTime();
double dt = currTime - lastTime;
playerX += playerVelX * dt;
playerY += playerVelY * dt;
if (playerY < groundLevel)
{
playerY = groundLevel;
playerVelY = 0;
}
//Slow down respective axes
playerVelX *= exp(-friction * dt); // playerVelX *= (1 - friction * dt) for small dt
playerVelY -= gravity * dt;
lastTime = currTime;
}

Updating time and position using GetTickCount() in C++

Problem: I am attempting to update time using getTickCount without avail.
Situation: I currently obtain the timedifference using getTickCount and pass it into world update method parameters. However within the update method where I update the position, a large value is passed in (even though I have divided by 1000) and so the position adds an odd 4000 to the position vector.
Code below:
Simulation.CPP:
int Simulation::simControlLogic(HWND hWnd, keyEvent event)
{
/* TO DO: add relevant code */
if (event != QUIT)
{
previousTime = 0;
frameStartTime = GetTickCount();
if (previousTime == 0)
previousTime = frameStartTime;
timeDifference = (frameStartTime - previousTime) / 1000.0f; // this generates the difference between the last and current time
world.update(event, &graphics, timeDifference); // update parameters of virtual world
gameLoopDelay(frameStartTime);
simDisplayFrame(hWnd); // display frame
previousTime = frameStartTime; //the current time is set to the previous time for the next loop step
}
return 1;
}
looking into the world Update method:
int WorldData::update(keyEvent kEvent, GraphicsM * pGraphicsModule, float timeStep)
{
//updates the particle
for (int i = 0; i < 3; i++)
{
particles[i].Update(kEvent, pGraphicsModule, timeStep);
}
return 1;
}
looking into particles Update method:
void ParticleModel::Update(keyEvent kEvent, GraphicsM * pGraphicsModule, float timeStep)
{
move(timeStep);
}
looking into move method:
void ParticleModel::move(float timeStep)
{
velocity.y = 0.5F;
velocity.x = 0.5F;
acceleration.x = 0.0F;
acceleration.y = 0.0F;
pos.x += velocity.x * timeStep; //here is the problem. I get a large value e.g 79637.1788 causing pos.x to be ridiculously large
pos.y += velocity.y * timeStep; //here is the problem. I get a large value e.g 79637.1788 causing pos.y to be ridiculously large
}
After moving previousTime to the simulator constructor initializing it to 0, it worked. Thanks for replies

How do I keep the jump height the same when using delta time?

I'm using delta time so I can make my program frame rate independent.
However I can't get the jump height it be the same, the character always jumps higher on a lower frame rate.
Variables:
const float gravity = 0.0000000014f;
const float jumpVel = 0.00000046f;
const float terminalVel = 0.05f;
bool readyToJump = false;
float verticalVel = 0.00f;
Logic code:
if(input.isKeyDown(sf::Keyboard::Space)){
if(readyToJump){
verticalVel = -jumpVel * delta;
readyToJump = false;
}
}
verticalVel += gravity * delta;
y += verticalVel * delta;
I'm sure the delta time is correct because the character moves horizontally fine.
How do I get my character to jump the same no matter the frame rate?
The formula for calculating the new position is:
position = initial_position + velocity * time
Taking into account gravity which reduces the velocity according to the function:
velocity = initial_velocity + (gravity^2 * time)
NOTE: gravity in this case is not the same as the gravity.
The final formula then becomes:
position = initial_position + (initial_velocity + (gravity^2 * time) * time
As you see from the above equation, initial_position and initial_velocity is not affected by time. But in your case you actually set the initial velocity equal to -jumpVelocity * delta.
The lower the frame rate, the larger the value of delta will be, and therefore the character will jump higher. The solution is to change
if(readyToJump){
verticalVel = -jumpVel * delta;
readyToJump = false;
}
to
if(readyToJump){
verticalVel = -jumpVel;
readyToJump = false;
}
EDIT:
The above should give a pretty good estimation, but it is not entirely correct. Assuming that p(t) is the position (in this case height) after time t, then the velocity given by v(t) = p'(t)', and the acceleration is given bya(t) = v'(t) = p''(t)`. Since we know that the acceleration is constant; ie gravity, we get the following:
a(t) = g
v(t) = v0 + g*t
p(t) = p0 + v0*t + 1/2*g*t^2
If we now calculate p(t+delta)-p(t), ie the change in position from one instance in time to another we get the following:
p(t+delta)-p(t) = p0 + v0*(t+delta) + 1/2*g*(t+delta)^2 - (p0 + v0*t + 1/2*g*t^2)
= v0*delta + 1/2*g*delta^2 + g*delta*t
The original code does not take into account the squaring of delta or the extra term g*delta*t*. A more accurate approach would be to store the increase in delta and then use the formula for p(t) given above.
Sample code:
const float gravity = 0.0000000014f;
const float jumpVel = 0.00000046f;
const float limit = ...; // limit for when to stop jumping
bool isJumping = false;
float jumpTime;
if(input.isKeyDown(sf::Keyboard::Space)){
if(!isJumping){
jumpTime = 0;
isJumping = true;
}
else {
jumpTime += delta;
y = -jumpVel*jumpTime + gravity*sqr(jumpTime);
// stop jump
if(y<=0.0f) {
y = 0.0f;
isJumping = false;
}
}
}
NOTE: I have not compiled or tested the code above.
By "delta time" do you mean variable time steps? As in, at every frame, you compute a time step that can be completely different from the previous?
If so, DON'T.
Read this: http://gafferongames.com/game-physics/fix-your-timestep/
TL;DR: use fixed time steps for the internal state; interpolate frames if needed.