Framerate independent movement

i get some strage results and don't know why. i'm using a constant value that i add to y every frame and multiply it with timePerFrame to jump. then i subtract grav+=gravityConstant*timePerFrame (getTimePerFrame is called once in my update method, i re-use the returned value). on high fps (>1000), my character jumps about 1 cm high. on 200, he jumps ~1 meter, and on 10 fps, he jumps about 500 meters.

same phenonema applies to friction. 1000 fps + key release = character stops instantly.  10 fps + key release = character walks like on ice.

Shouldn't movement be independent of framerate anyway, as long as you move based on the amount of time passed?  In your update method, you should be getting the amount of time since the last update, and so you're updating based on time, not on frames.

that's exactly what i'm doing. or at least i think that's what i'm doing. doesn't timer.getTimePerFrame give me the amount of time the last frame needed for rendering?

You should, instead, get the time since the last render or update, rather than the time it takes per render.  This way you can be time dependent, and instead framerate-dependent.



Afaik the update and render methods for GameState, SimpleGame, etc are passed the time since the last render (from Javadocs tpf - The elapsed time since last frame.) so you can use that.  The variable name "tpf" might be misleading to make you think it's the time per frame, so I usually call it timePassed or something.



For instance, if a cannonball is fired, (assuming no gravity or drag, and constant velocity for simplicity) you would update its position based on the time since it was last updated.  Something like distance += velocity*timePassed.  The method is called repeatedly, with small values of timePassed so you would get a nice smooth movement.  Like you noticed, if you update based on the length of time a frame takes, you'll get jerky and unpredictable movements, because one frame might take longer than another to render.



Sorry if I'm a bit long-winded. 

I think you are better off modeling the whole physics, with mass and all…

Like this:



public class Jumper {

   private float m;
   private float v0;
   private float g;
   
   private float maxH;
   
   private float v;
   private float x;
   private boolean inJump;
   
   /**
    * @param m mass
    * @param v0 initial velocity
    * @param g gravity constant
    */
   public Jumper(float m, float v0, float g) {
      this.m = m;
      this.v0 = v0;
      this.g = g;
      maxH = v0*v0/(2*m*g);
   }
   
   public void jump() {
      v = v0;
      x = 0;
      inJump = true;
   }
   
   /**
    * @param t time
    */
   public void update(float t) {
      if (inJump) {
         float newV = v - m*g*t;
         x += (newV + v)*t/2;
         v = newV;
         
         if (x > maxH) {
            x = maxH;
         } else if (x < 0) {
            x = 0;
            inJump = false;
         }
      }
   }
   
   public float getHeight() {
      return x;
   }
   
   public float getMaxHeight() {
      return maxH;
   }
}

I think you are better off modeling the whole physics, with mass and all...
Like this:



that one was completely off topic. the problem is that
y+=(movement-constant*timer.getTimePerFrame()) results in a faster acceleration if the fps is high.
according to the javadoc, it should return values so that no matter how fast or slow the method is called repeatedly, the movement should stay the same:
    /**
    * Returns the time, in seconds, between the last call and the current one.
    *
    * @return Time between this call and the last one.
    */
    public abstract float getTimePerFrame();


If you suggest that the timer is broken, you can easily test that:



import com.jme.util.Timer;

public class TimerTest {

   /**
    * @param args
    */
   public static void main(String[] args) {
      // let jvm catch up after loading all the classes
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) { }
      
      Timer timer = Timer.getTimer();
      long startTime = System.currentTimeMillis();
      long checksum = 0;
      
      for (int i = 0; i < 500; i++) {
         timer.update();
         checksum += timer.getTimePerFrame()*1000;
         
         try {
            Thread.sleep(10);
         } catch (InterruptedException e) { }
      }
      
      long totalTime = System.currentTimeMillis() - startTime;
      System.out.println("Time in milliseconds: " +
            "timer time = " + checksum + ", system time = " +
            totalTime + " difference = " + (totalTime - checksum));
   }

}



Just start it up and wait for 5 seconds, it will add upp all the calls to timer.getTimePerFram() and then compare the result with the build-in jvm system timer. It gives about 30 milliseconds deviation on my system, which is normal because the we start and end our measurments at slightly different times. You can experiment with different pauses between calls to the timer update, simulating lower or higher framerate.

When you are convinced that the timer is working and open towards looking for other solutions, check that you are calling timer.update() only once every frame. Another possibility is that your physics simulation depends on the updating frequency. Some formulas are more sensitive to the low update rate than others. Especially the formulas involving forces and momentum. Happens to me very often... So from my experience doing full physical simulation without cutting corners and picking good formulas that are less frame-rate dependent gets the job done. Hope now its relevant... :P

By the way, if you don't call timer.update() once every frame, then timer.getTimePerFrame() will return 0, then

y+=(movement-constanttimer.getTimePerFrame())

becomes

y+=(movement-constant
0)

y+=(movement-0)

y+=movement



So the higher frame rate will increase "y" faster… because you are adding the same constant but you are adding it more often.

lex, what are you talking about? you're completely offtopic, at least as I figured it out



they were talking about how update() method passes time passed since last frame variable and you must calculate with it.



update(float timePassed) … or something like that?

Well, yeah… that is what they are talking about!

And that timer test proves that there is nothing wrong with the timer. Timer does what it supposed to do… and you can use that timer with your physics, there is nothing wrong with that either. What I'm saying is that maybe he should look into making sure that the timer is updated, and double check his physics code. That little Jumper class works, I've tested it before posting… with the Timer.


i get some strage results and don't know why.

As I understand this is the topic.

The topic is: why is HamsterofDeath's jumping method not working. So I do the following:
1) ...give you example that I have personally tested and I know is working, you can use it as is, or use it for troubleshooting.
2) ...give a short test to verify that the timer class is working, and you can see how its working and what it is timing by looking at the test.
3) ...give a possible scenario of the mentioned code not working, with a description how it would happen and what would happen.

Now, if the goal is to make a certain physical simulation working, tell me if I am getting closer to actually having it working and then tell me if I am offtopic... grrrr  :D

sry, just go on, you're not so off topic

yeah you're advices are good, working, but it felt like they were straying from the original timing question (the physics stuff and timer stuff). The initial though was that he uses update's passed argument, not Timer, which I'm surely works fine, but not in his code. So to make this not go off topic:



@hamsterofdeath, try update()'s passed time argument or try fixing your code so you get proper timing from Timer


There is actually something wrong with the Timer: the total time stays consistent with the system time, however, when fps drops, the timer seems to stretch time, and when fps increases, the timer compresses time…



Here is a little demo of a jumping ball. it changes fps every 5 seconds form extremely low fps to extremely high fps. The demo prints out timer total time and system time to System.out.

Because the system time and timer time are updated in different places, there is some difference between the two timers, and its normal. The problem is that the difference fluctuates within a range on ONE SECOND. Also you can visually see ball suddenly moving slower and then accelerating when switching to lower fps. When switching to higher fps the ball just snaps. This behavior might be system-specific (I'm on Linux using Java6).



import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.scene.Controller;
import com.jme.scene.Text;
import com.jme.scene.shape.Quad;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.TextureState;

public class JumperTest extends SimpleGame {

   private static class Jumper {
      private float m;
      private float v0;
      private float g;
      
      private float maxH;
      
      private float v;
      private float h;
      
      /**
       * @param m mass
       * @param v0 initial velocity
       * @param g gravity constant
       * @return initilaized jumper
       */
      public static Jumper fromSpeed(float m, float v0, float g) {
         Jumper jumper = new Jumper();
         
         jumper.m = m;
         jumper.v0 = v0;
         jumper.g = g;
         jumper.maxH = v0*v0/(2*m*g);
         
         return jumper;
      }
      
      /**
       * @param m mass
       * @param maxH maximum height of the jump
       * @param g gravity constant
       * @return initilaized jumper
       */
      public static Jumper fromMaxHeight(float m, float maxH, float g) {
         Jumper jumper = new Jumper();
         
         jumper.m = m;
         jumper.g = g;
         jumper.maxH = maxH;
         jumper.v0 = FastMath.sqrt(maxH*2*m*g);
         
         return jumper;
      }
      
      private Jumper() { }
      
      public void jump() {
         v = v0;
      }
      
      /**
       * @param t time
       */
      public void update(float t) {
         if (h > 0 || v != 0) {
            float newV = v - m*g*t;
            h += (newV + v)*t/2;
            v = newV;
            
            if (h > maxH) {
               h = maxH;
               v = 0;
            } else if (h < 0) {
               h = 0;
               v = 0;
            }
         }
      }
      
      public float getHeight() {
         return h;
      }
      
      public float getMaxHeight() {
         return maxH;
      }
   }
   
   public static void main(String[] args) {
      JumperTest game = new JumperTest();
      game.setDialogBehaviour(SimpleGame.ALWAYS_SHOW_PROPS_DIALOG);
      game.start();
   }
   
   
   private boolean limitFps;
   private float timerTime;
   private float nextSwitch = 5;
   private long systemTimeStart;
   
   private Text infoTest;
   
   @Override
   protected void simpleInitGame() {
      infoTest = Text.createDefaultTextLabel("infoTest");
      infoTest.setTextureCombineMode(TextureState.REPLACE);
      infoTest.setLocalTranslation(
            display.getWidth()/2 - 60,
            display.getHeight()/2, 0);
        fpsNode.attachChild(infoTest);
       
      float ballRadius = 2;
      
      Quad floor = new Quad("Floor", 100, 100);
      floor.setModelBound(new BoundingBox());
      floor.updateModelBound();
      Quaternion rot = new Quaternion();
      rot.fromAngles(90*FastMath.DEG_TO_RAD, 0, 0);
      floor.setLocalRotation(rot);
      floor.setLocalTranslation(0, -ballRadius, 0);
      rootNode.attachChild(floor);
      
      final Sphere ball = new Sphere("Ball", 15, 20, ballRadius);
      ball.setModelBound(new BoundingSphere());
      ball.updateModelBound();
      ball.addController(new Controller() {
         private static final long serialVersionUID = 6770682367312003510L;
         private Jumper jumper = Jumper.fromMaxHeight(10, 10, 9.8f);
         
         public void update(float time) {
            if (jumper.getHeight() == 0) jumper.jump();
            jumper.update(time);
            //jumper.update(Timer.getTimer().getTimePerFrame());
            ball.setLocalTranslation(0, jumper.getHeight(), 0);
         }
      });
      rootNode.attachChild(ball);   
      systemTimeStart = System.currentTimeMillis();
   }
   
   public void updateInput() {
      super.updateInput();
      
      timerTime += tpf;
      if (timerTime > nextSwitch) {
         limitFps = !limitFps;
         
         nextSwitch += 5;
         
         float sysTime = (System.currentTimeMillis() -systemTimeStart)/1000f;
         System.out.println("timerTime=" + timerTime + ", systemTime=" +
               sysTime + ", difference=" + (sysTime - timerTime));
      }
      
      if (limitFps) {
         try {
            Thread.sleep(50);
         } catch (InterruptedException e) { }
      }
   }

}

I remember there being some problems mentioned about timer smoothing in the past.



http://www.jmonkeyengine.com/jmeforum/index.php?topic=4264.0

http://www.jmonkeyengine.com/jmeforum/index.php?topic=2344.0



I thought they had been fixed though and of course I don’t know if this has anything to do with the problem to start with.

@lex … your test is the same for passed "t" argument as for "Timer.getTimePerFrame()" … strange, maybe internal update uses Timer also? Also only thing that is broken is transition between high fps and low fps. Anyway I'm noob at jme and this is way out of my league.

@Kova: Yes, the Timer class is used for all the timing needs in jme. Moreover, that class is a singleton, meaning that there is only one instance per jvm. So the variable tpf found in SimpleGame is actually set by calling Timer.getTimePerFrame() and all the methods of the form update(time) are exectuted with the value of time=Timer.getTimePerFrame() .



@Gentleman Hal: Thanx alot for the info… I found it extremely useful, because this timer issue threw me off completely. I guess the fact that the timer smoothes out the time is a good thing? The effect seems to manifests itself only when going from one extreme to the other, a game having a steady average fps would be unaffected and fps jumps are averaged out.

the time stretch still isn't an explanation for what i'm experiencing. it doesn't only happen when the framerate changes, it happens the whole time. what does explain it is:

A BUG!

this is the code:


movement -=constant*timer.getTimePerFrame();
y+=movement;



if we assume the "time per frame", according to javadoc, is exactly the time between now and the last call, and the difference is exactly 1 second, we get the movements: 0,-1,-2,-3 and so on
after 6 seconds, y is 0-1-2-3-4-5-6 = -21 (-> (n+1)*n/2, in this case (6+1)*(6/2))

if the tpf is 0.001
the same math applied (0,-0.001, -0.002 and so on) leaves y at exactly -500,5 (1001*500/1000) after 1 second instead of -1 like above.

this may look weird at first, so here'sa more imaginable example:
if the tpf is 0.5 instead of 1, after one second, we are at:
-0.5 (first tick) - 1 (second tick) = -1.5 instead of -1

i can fix this by, instead of adding gravConst*tfp to the movement, either recalculating the movement every time by using the difference between now and the time of the jump start and assume an arbitrary constant update rate or simply add gravConst* (((int)(tpf*1000)+1)*(int)(tpg*1000)/2)

(not tested)

i love/hate math
movement -=constant*timer.getTimePerFrame();
y+=movement;

I hate to say it, but that is mathematically/physically wrong...

What you are doing is you are treating movement as speed by applying decelration to it:
movement -=constant*timer.getTimePerFrame();
And then you treat movement as distance by adding it directly to your y-coordinate. You cannot treat the same variable as both speed and distance and expect to get a physical simulation to work.

The actual formula for the motion you are trying to do is:
float lastMovement = movement;
float time = timer.getTimePerFrame();
movement -=constant*time;
y+=(movement + lastMovement)*time/2;
Where movement is speed or velocity.

On the topic of having wrong physics, there should be no mass in the Jumper class... I messed that one up :)

I hate to say it, but that is mathematically/physically wrong...


that may be true, but i don't care at all. what matters is that it "feels fine". i've developed a few games (none finished :(), and i always implemented gravity, jumps and acceleration in the movement -=constant*timer.getTimePerFrame();y+=movement;-way, and no one ever noticed anything weird. as long as the framerate is constant, there is nothing wrong with the formula - you will get an accelerated movement with constant acceleration.
it might not give you real world results, but you never saw people in the real world bunnyhop to become faster, did you?

float lastMovement = movement;
float time = timer.getTimePerFrame();
movement -=constant*time;
y+=(movement + lastMovement)*time/2;



no, i tried it. this one feels completely weird. i need a lot of acceleration to get moving, and if i don't set the friction very close to it, the character does not stop moving if i release the key. if i set the friction a bit higher, i move at a turtles speed. also, when running in circles, it feels like driving a car.

Sorry… i haven't noticed your edit earlier.

I am afraid I haven't explained that formula correctly:


float lastMovement = movement;
float time = timer.getTimePerFrame();
movement -=constant*time;
y+=(movement + lastMovement)*time/2;



I assumed (i know, never assume things :)) that the formula you were doing was only for jumping.
So the formula above is essentially

v0, lastMovement
v1 = v0 - g*t,  where v1 is the new velocity, g is the gravity constant and t is time
h1 = h0 + (v0 + v1)*t/2, so the new height is the old height plus the average velocity times time

This formula only describes vertical jumping motion, it doesn't describe friction or forward jumping motion or anything else. Only the vertical component of the jumping motion given no resistance of any kind (no air resistance and no friction...).

If you want to apply it for accurate 2d/3d/ jumping motion you first have to extract vertical speed component of your motion,
initialSpeed = wolrdUpVector.dotProduct(veolcityVector);
assuming you have a 3d velocity vector that is properly set. If you only manage your motion in 2d and want to add vertical jumping component, you can just set a constant initialVelocity every time jump occurs.

As for friction: you don't really want to apply friction to the vertical component of the jump. You can still keep the friction around for the other two components if you want to give players better control of their character while flying. Also you can tweak your jumping by adding some air resistance, but you will have to tweak the formula. If you do that, you probably need a different air resistance variable (not the same as the friction of your character with the ground).