4
\$\begingroup\$

I have been trying to implement a simple physics system for a 2D space game I am making. I have it pretty much working, but I have encountered an issue with the way I apply friction. I have tried several different ways of solving it, with different guides found online, but my math skills are lacking and it's difficult for me to translate the solutions to fit my problem.

I made a unit test that shows my problem:

            //Simulate a low FPS.
            //Accelerate body1 with 100 pixels/second, for a deltaTime of 1 (A single frame that took 1 second)
            body1.accelerate(100, 1);
            //Performe the physics with the dame deltaTime.
            body1.doPhysics(1);

            //Simulate a higher fps.
            //Accelerate body2 10 times, with 100 pixels/second for a deltaTime of 0.1 (A single frame that took 1/10'th of a second.
            for(int j=0; j< 10; j++) {
                body2.accelerate(100, 0.1);
                body2.doPhysics(0.1);
            }

            Debug.Log("Straight movement result "+body1.getPosition()+" --- "+body2.getPosition());

Which gives the result: "Straight movement result [0.0, 50.0] --- [0.0, 39.31200662855]"

My acceleration does not seam to be a problem. I have a unit test like the other one, to test the acceleration, and it shows no problems.

public void accelerate(double acceleration, double deltaTime) {
        Vector2f accel = new Vector2f(0, (float)(acceleration));

        //If power is negative, we want to go backwards.
        if(acceleration < 0) 
            accel.setTheta(angle+90 - 180);
        else 
            accel.setTheta(angle+90);

        velocity.x += (accel.x * deltaTime);
        velocity.y += (accel.y * deltaTime);
    }

In the doPhysics method, is where I calculate the friction. If I remove the friction calculations, the two values printed from the unit test are equal.

private static final float friction = 1.0f;//0.96f;

    private void doPhysics(double deltaTime) {
        //Save the velocity before applying friction.
        Vector2d velBefore = new Vector2d(velocity);

        //Then apply friction, with deltaTime.
        velocity.x -= (velocity.x * friction) * deltaTime;
        velocity.y -= (velocity.y * friction) * deltaTime;

        //And then calculate the average of the friction before and after friction and factor in deltaTime
        double realVelX = ((velBefore.x + velocity.x) * 0.5) * deltaTime;
        double realVelY = ((velBefore.y + velocity.y) * 0.5) * deltaTime;

        //Add the calculated velocity.
        position.x += realVelX;
        position.y += realVelY;
    }

I hope someone can see what I am doing wrong. I would really like my physics to be frame-rate independent and simple enough for me to understand.

\$\endgroup\$
  • \$\begingroup\$ This may not be a complete answer, but when playing with friction or acceleration, multiplication and varying framerate, you must use the pow() function. Ex: Instead of x *= 1.2, use x *= pow(1.2, deltaTime). Just my little something to help you. \$\endgroup\$ – Alexandre Desbiens Jul 11 '14 at 14:32
  • \$\begingroup\$ Simply changing it to "velocity.x *= Math.pow(friction, deltaTime);" gives me a better result, with a much more stable difference. Now i get [0.0, 100.0] --- [0.0, 55.0]. (Or if i repeat the whole test 10 times in a row, [0.0, 5500.0] --- [0.0, 5050.0]) So i guess i need to do someting more. \$\endgroup\$ – Rasmus Øvlesen Jul 11 '14 at 15:00
  • \$\begingroup\$ Never mind that, it did not work that well. It looks like it might work, if applied correctly. \$\endgroup\$ – Rasmus Øvlesen Jul 11 '14 at 15:07
  • \$\begingroup\$ The math I gave you is very simple and applies to simple multiplication. You have a subtraction in your code, so my bet is that it would not work. Still, what I gave you might help, but we will have to find out how and where. \$\endgroup\$ – Alexandre Desbiens Jul 11 '14 at 15:11
3
\$\begingroup\$

Ok, think I got an answer for you. The short version: compute the friction force F = friction * velocity before you start the loop and apply it in do Physics() like this: v -= F * deltaTime.

Long version: the assumption you're making that it should work independent of frame time only works with constant acceleration, like the one you're applying in accelerate(). But the friction force is not a constant one: it varies at every moment in time and you're taking snapshots of it. When you're updating with a smaller time step deltaTime you're actually getting a more accurate answer than with the big time step (10x). But you want to get the same (less precise) answer in both cases and the way to do this is to consider the friction force only at the moments in time corresponding to the big step - basically consider the force constant over every 10 iterations with the small step. Hope you got the idea and I didn't confuse you.

Even more, you could write a simple equation for the case with just velocity affected by friction: v1 = v0 - dt * f * v0 = (1 - dt * f) * v0 which after n steps becomes vn = (1 - dt * f)^n * v0. This is clearly not equal to (1 - n * dt * f) * v0 which you get if you just update with an n times bigger step. Indeed a power is involved here, but I don't see any sense in using the pow() function in any way.

And just for my curiosity: why are you doing all those tricks with storing the old velocity and the average afterwards? It looks like some sort of trapezoid integration scheme but I don't see the point of it. Moreover why are you simulating two bodies at different time steps? In my opinion it's good practice to simulate all the bodies in the same loop with the same time step. You can scale the velocities directly if you want some bodies to move slower - if that's want you want to achieve.

And last I just want to mention that you call friction is really air drag or viscuous friction and it just may be what you want in 2d. The "real" friction is Coulomb friction which depends on the normal push, i.e. weight in your case: F = - mu * m * g. This one is constant - it decreases the velocity with fixed decrements instead of scaling it with <1 factor at every update (you'll have to check at zero so it doesn't reverse your body but just makes it stand still). Air drag never really gets your velocity to zero, but very close.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ What you describe is exactly my problem. The reason i simulate them at different timesteps, is because that is my unit test. I am trying to make my physics system, so that two computers with different framerate (timestep) will get mostly the same result. Or rather, physics on the server will behave the same, whether the server is stressed and has low fps or if its doing fine with high fps. What type of drag/friction it is, i don't know. I am just looking for a certain feeling in the game, where the ships will float on when turning or stopping the acceleration. \$\endgroup\$ – Rasmus Øvlesen Jul 11 '14 at 15:35
  • \$\begingroup\$ I see. Then it's going to be hard to apply the answer I gave you if you have 2 framerates that can be in any ratio. You will always get different results when running at variable timesteps and non-constant forces. My advice is to run physics with a fixed time step on all machines, e.g. 16 ms for an average FPS of 60 and just ignore if they appear slowing down when the frame rate drops. Other options include doing a number of fixed timesteps per frame and then adapt this number and sync it with other machines - should be easier working with integers. Or work only with constant forces :) \$\endgroup\$ – Mihai F Jul 11 '14 at 15:52
  • \$\begingroup\$ one client has to be in charge, to be essentially the server, of what went where when. Any client should be able to interpolate a rough estimate themselves, however, the server should constantly be correcting the client. That is how I understand this problem. Often a client is hosting will run a separate server-client along side their client-client and view the world like any other would, with interpolation. \$\endgroup\$ – user1695680 Jul 12 '14 at 2:49
  • \$\begingroup\$ I am having one client also run the server and the server decides where everything is. The reason i began looking into this, was that i was making calculations in my AI to predict how much it would move, before it was allowed to think again (I have a delay on my AI's so they don't run every frame). But i have worked around the issue for now. \$\endgroup\$ – Rasmus Øvlesen Jul 15 '14 at 14:33
  • 1
    \$\begingroup\$ Its imposible that nobody yet mentioned this gafferongames.com/game-physics/fix-your-timestep \$\endgroup\$ – v.oddou Oct 1 '15 at 1:42
3
\$\begingroup\$

So I have struggled to get this right and this is what I currently use. The point is to have a friction value that is dependent on time and therefor works on low and hi end devices similarly.

//*** Set friction value
friction = 5f;

//*** Calculate Fiction decay
xRatio = 1 / (1 + (Time.deltaTime * friction));

//*** Decay velocity
velocity *= xRatio;

//*** Set Position
_position += velocity * Time.deltaTime;

Its been working well for me for years, I hope it helps.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.