Gordonk was absolutely spot on. It now appears that I have silky smooth movement at 60fps. If I cap the frame rate at anything less than 60 I start to see serious stuttering, but I guess that's a separate issue to explore.
So here is the code I am using right now. It hasn't been optimized in any way, it was just to implement a fixed time step. I'm using the cocos2d-x framework. CCPoint is a simple class that contains two floats, x and y.
struct State
{
CCPoint statePosition; // position
CCPoint stateVelocity; // velocity
};
struct Derivative
{
CCPoint derivativePosition; // derivative of position: velocity
CCPoint derivativeVelocity; // derivative of velocity: acceleration
};
double getCurrentTimeInSeconds()
{
struct cc_timeval currentTime;
CCTime::gettimeofdayCocos2d(¤tTime, NULL);
return (currentTime.tv_sec) + (currentTime.tv_usec / 1000000.0);
}
void update(float dt)
{
if (GetAsyncKeyState(VK_LEFT) & 0x8000) {
pPlayer->movement = -1;
pPlayerSprite->setScaleX(1);
}
if (GetAsyncKeyState(VK_RIGHT) & 0x8000) {
pPlayerSprite->setScaleX(-1);
pPlayer->movement = 1;
}
applyPhysics(dt);
pPlayer->movement = 0;
}
void applyPhysics(float gameTime) {
const float newTime = getCurrentTimeInSeconds();
float deltaTime = newTime - currentTime;
currentTime = newTime;
if (deltaTime>0.25f)
deltaTime = 0.25f;
accumulator += deltaTime;
int loopCount = 0;
while (accumulator>=dt)
{
accumulator -= dt;
previous = current;
integrate(current, t, dt);
t += dt;
loopCount++;
}
State state = interpolate(previous, current, accumulator/dt);
pPlayerSprite->setPosition(state.statePosition);
}
State interpolate(const State &previous, const State ¤t, float alpha)
{
State state;
state.statePosition.x = current.statePosition.x*alpha + previous.statePosition.x*(1-alpha);
state.statePosition.y = current.statePosition.y*alpha + previous.statePosition.y*(1-alpha);
state.stateVelocity.x = current.stateVelocity.x*alpha + previous.stateVelocity.x*(1-alpha);
state.stateVelocity.y = current.stateVelocity.y*alpha + previous.stateVelocity.y*(1-alpha);
return state;
}
CCPoint acceleration(const State &state, float t)
{
CCPoint tempPoint = CCPointZero;
tempPoint.x = pPlayer->movement * Player::MoveAcceleration;
if (pPlayer->isOnGround) {
tempPoint.x *= Player::GroundDragFactor;
}
else {
tempPoint.x *= Player::AirDragFactor;
}
tempPoint.x = MathHelper::Clamp(tempPoint.x, -Player::MaxMoveSpeed, Player::MaxMoveSpeed);
return tempPoint;
}
Derivative evaluate(const State &initial, float t)
{
Derivative output;
output.derivativePosition.x = initial.stateVelocity.x;
output.derivativePosition.y = initial.stateVelocity.y;
CCPoint tempPoint = acceleration(initial, t);
output.derivativeVelocity.x = tempPoint.x;
output.derivativeVelocity.y = tempPoint.y;
return output;
}
Derivative evaluate(const State &initial, float t, float dt, const Derivative &d)
{
State state;
state.statePosition.x = initial.statePosition.x + d.derivativePosition.x*dt;
state.statePosition.y = initial.statePosition.y + d.derivativePosition.y*dt;
state.stateVelocity.x = initial.stateVelocity.x + d.derivativeVelocity.x*dt;
state.stateVelocity.y = initial.stateVelocity.y + d.derivativeVelocity.y*dt;
Derivative output;
output.derivativePosition.x = state.stateVelocity.x;
output.derivativePosition.y = state.stateVelocity.y;
CCPoint tempPoint = acceleration(state, t+dt);
output.derivativeVelocity.x = tempPoint.x;
output.derivativeVelocity.y = tempPoint.y;
return output;
}
void integrate(State &state, float t, float dt)
{
Derivative a = evaluate(state, t);
Derivative b = evaluate(state, t, dt*0.5f, a);
Derivative c = evaluate(state, t, dt*0.5f, b);
Derivative d = evaluate(state, t, dt, c);
const float dxxdt = 1.0f/6.0f * (a.derivativePosition.x + 2.0f*(b.derivativePosition.x + c.derivativePosition.x) + d.derivativePosition.x);
const float dxydt = 1.0f/6.0f * (a.derivativePosition.y + 2.0f*(b.derivativePosition.y + c.derivativePosition.y) + d.derivativePosition.y);
const float dvxdt = 1.0f/6.0f * (a.derivativeVelocity.x + 2.0f*(b.derivativeVelocity.x + c.derivativeVelocity.x) + d.derivativeVelocity.x);
const float dvydt = 1.0f/6.0f * (a.derivativeVelocity.y + 2.0f*(b.derivativeVelocity.y + c.derivativeVelocity.y) + d.derivativeVelocity.y);
state.statePosition.x = state.statePosition.x + dxxdt*dt;
state.statePosition.y = state.statePosition.y + dxydt*dt;
state.stateVelocity.x = state.stateVelocity.x + dvxdt*dt;
state.stateVelocity.y = state.stateVelocity.y + dvydt*dt;
}
I haven't tried applying any vertical force yet but hopefully it will work the same way.
Thanks again for helping me understand the acceleration method.