Kinematic character controller interpolation

nsf
Posts: 1
Joined: Thu Oct 24, 2013 2:21 pm

Kinematic character controller interpolation

Post by nsf »

It's not even a question, more like I'm sharing how I've fixed a problem with btKinematicCharacterController stuttering. In case if anyone else is wondering. Perhaps it's worth adding to some guide, I don't know. Feel free to repost it, I don't have a blog.

The problem with btKinematicCharacterController is that it doesn't take advantage of bullet's MotionState interpolation system. When you run your physics with fixed timestep, occasionally it can miss a frame, and the result is visible stutter. In my case I was using SDL's built-in timer, which reports time with a millisecond resolution and for 60 FPS it will report 16 or 17. Which means a slight desync between bullet's internal time and my vsync-based time. The solution is rather simple - keep previous character position and interpolate between it and the current one manually. The code is rather minimal. First we need a btKinematicCharacterController hack that keeps the previous, current positions and bullet's local time (the code is C++11):

Code: Select all

class MyCharacterController : public btKinematicCharacterController {
public:
        float local_time {0.0f};
        Vec3 prev_pos;
        Vec3 cur_pos;

        MyCharacterController(btPairCachingGhostObject *ghost, btConvexShape *shape, btScalar step):
                btKinematicCharacterController(ghost, shape, step) {}

        void updateAction(btCollisionWorld *world, btScalar delta) override {
                btKinematicCharacterController::updateAction(world, delta);
                local_time += delta;
                prev_pos = cur_pos;
                cur_pos = ToVec3(m_ghostObject->getWorldTransform().getOrigin());
        }
};
And in your rendering loop:

Code: Select all

void Update(float delta) {
        btworld->stepSimulation(delta, 10); // will run character's updateAction zero or more times
        if (player->local_time > local_time) {
                // here we simply reset the time if we can, to avoid floating point precision issues
                player->local_time -= local_time;
                local_time = 0.0f;
        }
        local_time += delta; // this local_time is vsync-based time
        float diff = 1.0f - (local_time - player->local_time) / (1.f / 60.f); // 'player' is our MyCharacterController
        camera.position = player->prev_pos * diff + player->cur_pos * (1.0f - diff);
}
That fixes the stutter. Well, there is no protection from giant 'delta', but if 'delta' is within bullet's limit (10 * fixedTimeStep in that case), it will work just fine. Stepping the simulation will shorten the gap between vsync time and bullet's time to a value less than fixedTimeStep and the interpolation works correctly because of that.

Also I would like to add a couple of things. If you do collision detection using a ray that starts from a camera - use the actual ghost object's position instead of interpolated one. Another thing that is worth mentioning is that you don't have to lag/interpolate the camera's orientation.

I hope it's useful for somebody at least. :)