Kinematic character controller interpolation
Posted: Thu Oct 24, 2013 5:55 pm
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):
And in your rendering loop:
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.
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());
}
};
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);
}
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.