Skip to main content
gamedev.net
🔒 Locked

Integration timestep

Started by meo Apr 10, 2005 at 3:42 PM 8 replies 3.8k views
Original Post
meo
meo
hi there, I'm had a few day's ago a look at this paper : http://69.55.229.29/articles/Timestep.html I tried to implement this in my own engine and feel like getting some strange results. The problem is that everything seems going on in slowmotion now. There two possibilities: 1. It should go this slow but i'm not aware of it because i have no reference. 2. I'm doing something wrong. Ok this is what i have: first thing what i do is making a timestep.


float dt = 0.001;   //how do i determine the value of this ?
float timeElapsed = 0,
      currentTime = 0,
 	  accumul = 0;

void DoTimeStep()
{
  float newTime = time();
  float deltaTime = newTime - currentTime;
  currentTime = newTime;

  accumul += deltaTime;

  while (accumul >= dt)
  {
    UpdateObjects(dt);
    timeElapsed += dt;
    accumul -= dt;
  }


RenderObjects();
}


Maybe my dt has to be higher, but when i make it higher as 0.001 the movement of my objects stutter. So i think i have here the right value. The second thing what i do is my integration.

//Euler
void CObject::Update(float dt)
{

AddForce(CVector(0,-10,0)*m_mass);  //add gravitation

m_accel     = m_force / m_mass;		
m_veloc    += m_accel * dt;
m_position += m_veloc*dt;

m_force = CVector(0,0,0);      //reset forces
}

//Verlet
void CObject::Update2(float dt)
{
float dt2 = dt*dt;
AddForce(CVector(0,-10,0)*m_mass);  //add gravitation

m_accel = m_force / m_mass; 
CVector temp = m_position;
CVector m_position += (m_position - m_prevPos) + m_accel * 2 * dt2;
m_prevPos = temp;

m_force = CVector(0,0,0);        //reset forces
  
}


I tried both euler and verlet integration but they give me the same results. Can anybody shed some light on this ? thanks.
John Schultz
John Schultz
The code looks OK, however dt should be 1/60, 1/100, 1/120, etc., not 1/1000 (too small for a game application (will get slow very fast when many items are active).

The *2 on the Verlet update line should be removed.

This Verlet update form might be more clear:
m_veloc     = m_position - m_prevPos + m_accel * dt2;m_prevPos   = m_position;m_position += m_veloc;


You should also add a MaxStep and a counter to make sure the simulation doesn't get permanently behind (which would cause it to slow down):

  sumDT += simTime.deltaSeconds();  const flt SimDeltaTime = 1.f/60.f; // 60Hz.  physicsSystem.setDeltaTime(SimDeltaTime);  const int MaxSimSteps = 5;  // Arbitrary: set as needed.  int simStep = 0;  while (sumDT >= SimDeltaTime && simStep < MaxSimSteps) {    physicsSystem.setCurrentTime(world.currentTime);    physicsSystem.timeStep();    world.currentTime += SimDeltaTime;    sumDT             -= SimDeltaTime;    simStep++;  } // while  if (simStep == MaxSimSteps) sumDT = 0.f;
meo
meo
Yes you're right dt should be 0.01, but in my case this is not the problem. My scene is not that
complicated and don't see any difference between 1/100 and 1/1000. Though the rendering when
using 1/1000 is looking a little bit more smooth.

Using a MaxSimSteps could solve my problem, still have to find the time to try this.
I see you use a max of 5 steps. But again how do i find my MaxSimSteps?

And maybe another question, which integration should i use, euler or verlet ?
I read verlet is more stable and is used more frequently in game engines.
I can't see any difference switching between these two in my engine.
John Schultz
John Schultz
Quote:
Original post by meo
Yes you're right dt should be 0.01, but in my case this is not the problem. My scene is not that
complicated and don't see any difference between 1/100 and 1/1000. Though the rendering when
using 1/1000 is looking a little bit more smooth.

Using a MaxSimSteps could solve my problem, still have to find the time to try this.
I see you use a max of 5 steps. But again how do i find my MaxSimSteps?

And maybe another question, which integration should i use, euler or verlet ?
I read verlet is more stable and is used more frequently in game engines.
I can't see any difference switching between these two in my engine.


To get the smoothest rendering possible, the simulation rate must match the refresh rate (or a multiple). For example, if the refresh rate is 60Hz, run the simulation at 60Hz, 120Hz, or 180Hz, etc.

A more complicated method requires you to store the previous positions/orientations and linearly interpolate between the previous and current positions/orientations. See the example in the time step link you previously referenced.

This will work for position (a Vec3) as well as orientation (most efficient if stored as a quaternion). As long as your sim rate closely matches the refresh rate, you can just lerp the quaternion (as opposed to the more expensive slerp). If you allow a super-slow-mo mode, you can switch between lerp and slerp as needed. This is probably the simplest solution when your game is tuned for a specific dt and you must run on arbitrary hardware.

Determining MaxSimSteps: if your dt is 1/100, and MaxSimSteps is 5, then your effective rate is 5*dt, 5/100 = 1/20. The frame rate can go even lower, depending on CPU load. If you set MaxSimSteps to lower numbers, the simulation will slow down more quickly (and recover more quickly). Thus, you should tune the MaxSimSteps to allow the simulation to slow down in worst case conditions only (e.g. high simulation scene action). By limiting to MaxSimSteps and throwing away sumDT, you'll allow the simulation to recover quickly after the "intense scene action". If your simulation is constantly hitting MaxSimSteps (after tuning), you'll need to optimize your simulator or decrease complexity. MaxSimSteps is a safety net: it should not be hit very often. If MaxSimSteps is hit, you can use this event to turn off less important parts of the simulation to more quickly recover (anything to reduce CPU load).

Verlet vs. Euler

[Edited by - John Schultz on April 11, 2005 12:53:49 PM]
meo
meo
Thanks for your explanation.

Well i think i found wat the problem was. Aparantly
my deltaTime was sometimes too low what caused
to skip the while routine and thus skipping a timestep.

Now i have something like this. I capped my deltaTime so
that at least one timestep is performed every frame.

void DoTimeStep(){  float newTime = time();  float deltaTime = newTime - currentTime;  currentTime = newTime;int maxTimeSteps = 0;//cap deltaTimeif (deltaTime>0.1)   deltaTime = 0.1;if (deltaTime<0.01)   deltaTime = 0.01;  accumul += deltaTime;  while (accumul >= dt && maxTimeSteps <= 5)  {    UpdateObjects(dt);    timeElapsed += dt;    accumul -= dt;    maxTimeSteps++;  }RenderObjects();}
John Schultz
John Schultz
Clamping delta time to a maximum value is a good idea (works similarly to counting MaxSimSteps and throwing away sumDT). Clamping delta time to a minimum value will cause the simulation to speed up when the frame rate is high. In some cases, rendering too fast can cause problems with sound playback (graphics taking too many cycles from sound mixer, etc.). Thus, you might try not rendering when there is no physics step, and even adding a Sleep(0) (thread yield) for this condition.

Your framerate will be locked to your simulation rate, and no cycles will be wasted rendering a frame when nothing has changed (you would turn this off when running a max-performance benchmark). Also check to make sure no physics/motion-updates are going on in your graphics code.

Another (advanced) option is to run the physics in a thread, and use this trick:

DWORD WINAPI physicsThread(LPVOID data) {  // 100Hz  while (WaitForSingleObject(physicsThreadEvent,10) == WAIT_TIMEOUT) {    physicsSystem.timeStep(); // A counter in physicsSystem indicates an update has occurred.                              // The renderer only renders a frame after an update. Otherwise, it should Sleep(0).                              // The renderer can also be notified with an Event.  } // while} // physicsThreadvoid startPhysicsThread(void) {  if (!physicsThreadEvent) {    physicsThreadEvent  = CreateEvent(0,false,false,"PhysicsThreadEvent");    physicsThreadHandle = CreateThread(0,0,physicsThread,0,0,&physicsThreadID);    mainThreadHandle    = GetCurrentThread(); // In case you need it.//    SetThreadPriority(physicsThreadHandle,THREAD_PRIORITY_TIME_CRITICAL);  } // if} // startPhysicsThreadvoid stopPhysicsThread(void) {  if (physicsThreadEvent) {    SetEvent(physicsThreadEvent);    WaitForSingleObject(physicsThreadHandle,INFINITE);    CloseHandle(physicsThreadHandle);    CloseHandle(physicsThreadEvent);    physicsThreadEvent  = 0;    physicsThreadHandle = 0;    mainThreadHandle    = 0;  } // if} // stopPhysicsThread
meo
meo
Indeed rendering frames when there was no timestep caused me
some weird problems in the behaviour of colliding objects.
So doing only a render when there is a timestep solved this problem.
I still have to look into using threads for this, thanks for your example.

What i'm still wondering about is if it's a good idea to
clamp delta time to a minimum value ? This makes every frameupdate
to do at least one physics step when sometimes there has none to be done
(delta time is too low).
This makes my elapsedTime to go faster than real-world time. So a second
in my sim is not a real second. Won't this cause any problems when
i'll implement rigid body/ragdoll physics ?
John Schultz
John Schultz
Quote:
Original post by meo
Indeed rendering frames when there was no timestep caused me
some weird problems in the behaviour of colliding objects.
So doing only a render when there is a timestep solved this problem.
I still have to look into using threads for this, thanks for your example.

What i'm still wondering about is if it's a good idea to
clamp delta time to a minimum value ? This makes every frameupdate
to do at least one physics step when sometimes there has none to be done
(delta time is too low).
This makes my elapsedTime to go faster than real-world time. So a second
in my sim is not a real second. Won't this cause any problems when
i'll implement rigid body/ragdoll physics ?


1. Do not clamp deltaTime to a minimum value: yes, it will cause time to speed up. If it appears to fix a problem, then something is happening in your rendering (or other) code that affects your physics: make sure nothing related to physics happens outside of your Physics::timeStep() code.

2. If you want to experiment with threading, make sure you can easily switch back to a non-threaded version (compile time or run time). It's much harder to develop and debug threaded code. An advantage to working with threaded physics in the beginning is that you can deal with thread issues at design time. Adding threading later can be more challenging. During development, you'll want to be able to easily switch to non-threaded for debugging.
meo
meo
I think you answered all my questions,
thanks again for your help.
Gaffer
Gaffer
If you visit the site again you can now download the example source code for the articles. This example source should show you how to do the timestep article technique perfectly, cheers

http://www.gaffer.org/articles

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.