physic, collision detection and time in message loop

Started by
3 comments, last by anders211 8 years, 1 month ago

Hi

It is known that in order to have physics worked well we need to introduce some trick in main message loop:



void GUI::EnterMsgLoop()
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));

static DWORD lastTime = timeGetTime();

bool Run = true;
while(Run)
{
    if (::PeekMessage(&msg, 0, 0U, 0U, PM_REMOVE) == TRUE)
    {           
        if(msg.message == WM_QUIT)
            break;
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    else
    {
        DWORD currTime  = timeGetTime(); //[ms]
        float timeDelta = (static_cast<float>(currTime - lastTime))*0.001f;

        if(timeDelta > 0.25f)
            timeDelta = 0.25f;
        lastTime = currTime; 
        while(timeDelta>0)
        {
            float minTime = min(timeDelta, d3d::MIN_DELTA_TIME); //MIN_DELTA_TIME = 1/60.00f = 0.0166 [s]
            Display(minTime); //here I do rendering of static object, physic update, collision check, rendering dynamic object like ball, human
            timeDelta -= minTime;
        }
        if( GetKeyState( VK_ESCAPE ) & 0x8000 )
            Run = false;
    }
}
}

With the above code I have problem with collision detection when velocity is big because when object is in the area of possible collision then:

1) rendering starts to last more than 0.0166 [s] so minimum time seed value starts to be used 0.0166 [s]

2) when ball with radius 0.11 [m] has speed 27 [m/s] then it can travel through 0.0166 [s] distance 0.5 m.

3) in some of my collision implementation stage there is done ray intersection between the center of the ball and object normal vector (I adopted Moller-Trumbore algorithm to my needs) and if distance is less than radius then collision is detected.

So the problem is that from time to time collision doesn't work because between 2 frames we have situation like in the figure:

[attachment=30983:undetectedCollision.JPG]

Ball instead of bounce, it penetrates object and travel further.

I modified d3d::MIN_DELTA_TIME to value 1/300 (0.003 [s]) and the problem dissapeared however the issue is now that there are big lags in my APP, it works much slower. So I suppose that this is wrong direction and I need to do something with collision algorithm which should detect dx=0.5[m] however I don't know what to do. If anybody know some book/article in the website about this problem I would be grateful for sharing. I would be grateful also for any hints.

After some thinking I will try to divide rendering and physic update/collision check into two separate methods and modify MIN_DELTA_TIME to 1/300 and I will check if there will be still lags during rendering:


...lastTime = currTime; 
timeFullDelta = timeDelta;
while(timeDelta>0)
{
    float minTime = min(timeDelta, d3d::MIN_DELTA_TIME);
    updatePhysicAndCheckCollision(minTime);
    timeDelta -= minTime;
}

Display(timeFullDelta);

...

Advertisement

This sounds like a tunneling problem. To solve this many games have either limited the maximum distance the ball can travel for any given timeslice, or made walls much thicker. Alternatively the simulation code can calculate time of impact to know when collision occur within a single timeslice.

The easiest solution will probably be to update the physics at a specific frequency and then tune the simulation so tunneling does not happen (or is very rare). If you fix this frequency to a constant it allows for finer-tuning to prevent tunneling. Graphics can then be run at a separate (perhaps variable) frequency as necessary. In order for the differences in frequencies to be rendered smoothly some interpolation between simulation states will likely need to occur.

Several things are wrong there.

As a start, you always tick your physics, regardless you got messages or not. They're not exclusive.

I'm very surprised you can use timeGetTime(), in my experience it is very gross-grained, ms at best. I found it inadeguate. At 60fps it'll be introducing about 5% deviation with ease.

Your time tick is wrong as you're clamping the maximum slowness you can have ignoring extra time. timeDelta = 0.25f; doesn't make any sense especially as...

... you appear to be ticking at a constant time step. Actually no, you don't, or perhaps I cannot tell. You tick at least 60 fps (too much for physics), unless your frame time is even lower, in which case you tick at that speed > 60fps. Now, the options are two, pick one:

  1. Your Display call correctly switches to interpolate mode internally, it which point it wouldn't make sense to select the values as you're doing OR
  2. you don't have a constant time step, which means you are all wrong.

You have correctly analyzed the problem and Randy partially elaborates on it with argumentation I don't quite support.

You can find all the documentation you want in the Bullet source code. You will find it's quite more involved than theory and I would suggest to scrap this whole thing in favor of something that's industry standard.

You will find that Bullet can perform sweeping tests for you when an object is 'too fast'. If you're doing this for fun, doing arbitrary sweeps will entertain you for a while.

Previously "Krohm"

OK I will use QueryPerformanceCounter. I think I found optimal solution.


...
lastTime = currTime;
timeFullDelta = timeDelta;
d3d::MIN_DELTA_TIME = timeDelta / (floor(getBallV * timeDelta / ballR) + 1);
while(timeDelta>0)
{
    float minTime = min(timeDelta, d3d::MIN_DELTA_TIME);
    updatePhysicAndCheckCollision(minTime);
    timeDelta -= minTime;
}

Display(timeFullDelta);

The aim is that ball cannot travel through one physic/collision check run more than dx = radius. So the above code takes care about this. And it is better that fixed physic update frequency because many processor time is saved.

I can also get rid of the problem by using just D3DPRESENT_INTERVAL_IMMEDIATE instead of D3DPRESENT_INTERVAL_ONE.

d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE;

The fact I am using interval_one makes that my deltaTime is 1/60=0.0166 [s] and with the ball speed 27m/s it can travel abou 0.5 [m] during one run. When I use IMMEDATE then my fps is currently 400 and problem doesn't exist. But I think I should use INTERVAL_ONE as 60 fps is OK for user and there is none need to make graphic card of user to work at higher rate (lifetime is probably reduced in this way).

Everything works correct with the below code:


void GUI::EnterMsgLoop()
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));


static DWORD lastTime = timeGetTime();


bool Run = true;
while(Run)
{
    if (::PeekMessage(&msg, 0, 0U, 0U, PM_REMOVE) == TRUE)
    {           
        if(msg.message == WM_QUIT)
            break;
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    else
    {
        DWORD currTime  = timeGetTime(); //[ms] TO DO: suggestion to use QueryPerformanceCounter timer
        float dt = (static_cast<float>(currTime - lastTime))*0.001f;
        if(dt > 0.25f) 
            dt = 0.25f;
        lastTime = currTime;


        float DT = dt;
        float min_dt = dt / (floor(GetBallSpeed()*dt/GetBallRadius()) + 1.00f);
        min_dt = min(min_dt, d3d::MIN_DELTA_TIME); //MIN_DELTA_TIME = 1/60.00f (maybe it is not needed but I keep)


        while(dt>0)
        {
             float minTime = min(dt, min_dt);
             Update(minTime);  //physic update of all movable objects like ball, human, collision detection adn collision response service
             dt -= minTime;
        }


        Display(DT); //rendering


        if( GetKeyState( VK_ESCAPE ) & 0x8000 )
            Run = false;
     }
}
}

This topic is closed to new replies.

Advertisement