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.