- when receiving update, calculate where the entity would be 100 ms from now, based on position/velocity
- now, subtract the current entity position from the projected entity position -- this is the movement vector for the next 100 ms
- divide by 100 ms to get the speed of movement
Yeah, snapping to extrapolation will generate some pretty bad jitter.
Have you used anything like this before?
// generic rigid body physics.
// simple linear / velocity vectors.
struct PhysicalBody
{
Vector position; // 3D position.
Vector velocity; // 3D velocity vector.
};
// a network update. Contains
// the remote state of the physical body,
// as well as the time it was sent from.
struct ControlPoint
{
PhysicalBody body; // body position and velocity.
float t; // time from when the body position was sent.
};
// calculate body position, extrapolating from a control point.
PhysicalBody extrapolate(ControlPoint cp, float t)
{
PhysicalBody body;
float dt = (t - cp.t); // extrapolation time.
body.position = cp.position + cp.velocity * dt; // extrapolate (predict) using the body velocity.
return body;
}
// interpolate between two bodies.
PhysicalBody interpolate(PhysicalBody a, PhysicalBody b, float u)
{
PhysicalBody c;
c.position = a.position + (b.position - a.position) * u; // simple linear interpolation.
c.velocity = a.velocity + (b.velocity - a.velocity) * u; // simple linear interpolation.
return c;
}
ControlPoint cp[2]; // the control points we used to extrapolate the remote player position.
float network_dt = 0.1f; // network update period (100ms, or whatever your send rate is).
float snap_speed = 0.9f; // how fast we snap to the new extrapolation. something sensible between [0.5f, 1.5f].
PhysicalBody player; // the predicted remote player position.
float time; // the current time.
// it's time to update the player position
// using two control points that we will extrapolate from.
void extrapolate_player()
{
// extrapolate from the two control points.
PhysicalBody a = extrapolate(cp[0], time); // extrapolate to current time using the oldest control point.
PhysicalBody b = extrapolate(cp[1], time); // extrapolate to current time using the latest control point.
// blend the two positions together.
// Once we're about ready to receive a new update
// (depending on snap_speed), we will be following
// the most up-to-date extrapolation.
float blend_dt = network_dt * snap_speed; // the network update rate, assuming it's constant. Add +/-10% in extra.
float u = m_blend_timer.get_elapsed_time() / blend_dt; // the blend factor between the two extrapolations.
u = clamp(u, 0.0f, 1.0f); // clamp u in range [0.0f, 1.0f].
// the new player position,
// blended between two extrapolations.
player = interpolate(a, b, u);
}
// we received our first network update.
// we can calculate extrapolations from here.
void receive_first_network_update(ControlPoint new_cp)
{
// set both control points to the same value.
cp[0] = new_cp;
cp[1] = new_cp;
// restart the blending.
m_blend_timer.start();
// use arbitrary value.
network_dt = 0.1;
}
// network sent us a new update.
// update exrapolation cache.
void receive_new_network_update(ControlPoint new_cp)
{
// measure the network update rate.
// although if the update rate is constant, network_dt could just be left alone.
network_dt = (new_cp.t - cp[1].t);
// use the current body position and time
// as the oldest extrapolation path.
cp[0].body = player;
cp[0].t = time;
// use the new control point as the latest extrpolation path.
cp[1] = new_cp;
// restart the blending.
m_blend_timer.start();
}
NOTE : like all network games, the time between machines should be synchronised to a reasonable degree. all times (local current time, and netowrk update times) should refer to the same 'clock'.