Page 1 of 1
Framerate-independent physics
Posted: Sun May 15, 2011 10:54 pm
by mh
I came across this article a short time ago:
http://gafferongames.com/game-physics/f ... -timestep/
The full info on what this does and how it works is available there; meanwhile here's the Quake version:
Code: Select all
void Host_ServerFrame (void)
{
static double accumulated = 0.0;
double alpha;
static double prevtime = 0.0;
const double delta = (1.0 / 72.0);
double new_frametime;
accumulated += host_frametime;
pr_global_struct->frametime = host_frametime;
SV_ClearDatagram ();
SV_CheckForNewClients ();
while (accumulated >= delta)
{
host_frametime = delta;
pr_global_struct->frametime = delta;
SV_RunClients ();
if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game))
SV_Physics ();
accumulated -= delta;
}
alpha = accumulated / delta;
// assign the remainder of the timestep to a new interpolated time
new_frametime = host_frametime * alpha + prevtime * (1.0 - alpha);
prevtime = host_frametime;
host_frametime = new_frametime;
pr_global_struct->frametime = host_frametime;
// send all messages to the clients
SV_SendClientMessages ();
}
Posted: Sun May 15, 2011 11:21 pm
by Baker
That article was a fun read.
Posted: Mon May 16, 2011 12:36 am
by qbism
Seems to work well in a bot DM running cl_maxfps 20, 72, and 999 (actual about 180).
Suggest trying delta = 1/min(cl_maxfps.value, 72) I know the point is framerate independent. But in software engine, physics rate should not really be more than framerate.
More elegant than the previous physics throttling solution. Interesting that SV_RunClients is now within the throttled loop.
Posted: Mon May 16, 2011 9:04 am
by mh
qbism wrote:More elegant than the previous physics throttling solution. Interesting that SV_RunClients is now within the throttled loop.
I picked that one up from the Q1 source - even more interesting that there was already similar code there in the #ifdef FPS_20 stuff.

Posted: Mon May 16, 2011 9:11 am
by mh
qbism wrote:Suggest trying delta = 1/min(cl_maxfps.value, 72) I know the point is framerate independent. But in software engine, physics rate should not really be more than framerate.
I'm actually thinking that 72 FPS is much too high for Quake. Back in 1996 72 FPS would have been very rare indeed. and the default intended physics behaviour would have been designed around maybe 20-30 FPS.
Not sure what the impact of a variable (or user-settable) delta might be though. That one would need serious testing before committing to the change.
Posted: Mon May 16, 2011 12:13 pm
by revelator
hehe i wasnt sure which of them to replace so i tried both.
strange thing though if i use it on the one after the #else viewmodels dissapear ?
Posted: Wed May 18, 2011 5:06 am
by r00k
Sweet!
I was just messing around with a frametime throttle for the particles the other day only to have irregular results. Higher maxfps, more particles are drawn and I was trying to calm this down.. without success.
Might be able to do something similar for particles....
ok off to read the article ....

Posted: Wed May 18, 2011 9:41 am
by mh
r00k wrote:Sweet!
I was just messing around with a frametime throttle for the particles the other day only to have irregular results. Higher maxfps, more particles are drawn and I was trying to calm this down.. without success.
Might be able to do something similar for particles....
ok off to read the article ....

Rocket trail particles are generated on the client every frame, so you need to either run CL_RelinkEntites at 72 FPS or (preferably) split off the particle generating code from it and run that at 72 FPS. Make sure that you check for cls.signon == SIGNONS before doing so, of course. And split off CL_LerpPoint so that it runs every frame rather than just at 72 FPS. And possibly a few other things I can't remember right now, but that's something I got working yesterday and it was painful with loads of implication elsewhere that you wouldn't expect.
Posted: Wed May 18, 2011 11:48 am
by Spike
higher = more? :o
for me, higher = none!
particles are spawned every 4qu using the original particle system. if you're generating a rocket trail from interpolated positions, if you have a high enough framerate the rocket will move less than the distance between two particles. because its not moved 4qu or so, you get no particle. for me, the cut off point is about 1k fps - no trails past that mark.
if its moving exactly 4qu instead of 7qu each frame then yes, you'll have more particles. you'll also get more if your framerate is slow enough to let the rocket move exactly 16qu.
The solution is to use better trail tracking - that is to store the distance moved beyond the previous particle so that the spawn rate is fully inclusive of that.
if your rocket trail code generates a particle at the *start* of every trail segment, then yeah, you'll always get at least $fps particles per second. the fix is the same even if the existing code is not.
unlike sv_phys, you need to do it per rocket, as they all have different speeds and trails are based upon distance, not time.
Posted: Thu May 19, 2011 7:56 am
by r00k
Some attempts rendered no trail yes. altering host_frametime more particles. I wanted a consistent number of particles for trails no matter how fast or slow they moved. Like you said, distance might be a good baseline to create an algorithm around.
Posted: Thu May 19, 2011 4:07 pm
by mh
Well the baseline ID Quake code already generates them based on distance:
Code: Select all
void R_RocketTrail (vec3_t start, vec3_t end, int type)
.
.
.
VectorSubtract (end, start, vec);
len = VectorNormalize (vec);
if (type < 128)
dec = 3;
else
{
dec = 1;
type -= 128;
}
while (len > 0)
{
len -= dec;
Distance is obviously not working correctly in this case.
What I think may be the root of Spike's problem is that the default QW timer is integer millisecond, so at really high FPS it doesn't have sufficient precision for measurements to remain valid. As soon as you go over 500 FPS you're effectively getting a delta time of 0ms. No movement = no distance = no particles.
Pulling the generation rate back to 72 FPS allows precision errors to largely (but not totally) cancel out instead of accumulate. Another option might be to use the trick I posted elsewhere of using QPC (or any other higher resolution timer) to fill in the gaps between milliseconds.
Posted: Thu May 19, 2011 5:14 pm
by qbism
1. Gravity fix the engine.
2. Where possible, replace framerate dependent effects with time-dependent effects (Distance is time dependent, but not always possible to replace)
3. Set physics to 20 fps.
---->Profit!
Posted: Thu May 19, 2011 6:13 pm
by mh
OK, I was talking rubbish. I've managed to reproduce Spike's error, and it's nothing to do with timer precision.
This description is for NQ, but I assume that QW does something reasonably similar.
On a frame when the server runs we get a full read on the client.
On a frame when it doesn't run we get no read on the client.
In both cases CL_RelinkEntities (which generates particle trails) runs.
If the client is running faster than the server, the entity remains on the client but it's new origin doesn't get updated. In order to generate a particle trail we need to:
- Copy current origin to old origin.
- Advance current origin by the lerp position determined by CL_LerpPoint.
- Generate particles between the two points.
However, if the client is running really really really fast, the difference between old origin and current origin will be negligible on successive client frames; at least for the purpose of generating particles.
Soooo... how about this?
- Check for particle trail effects in CL_ParseUpdate instead of in CL_RelinkEntities.
- if ((bits & U_ORIGIN1) || (bits & U_ORIGIN2) || (bits & U_ORIGIN3)) generate particles.
- use ent->msg_origins[0] and ent->msg_origins[1] as the start and end points (or should that be the other way around?)
Posted: Thu May 19, 2011 6:32 pm
by mh
OK, that works perfectly. It's also good for different values of host_framerate (or host_timescale if you have it).
Get rid of all the R_RocketTrail calls from CL_RelinkEntities and pop this at the very bottom (after the if (forcelink) block) of CL_ParseUpdate:
Code: Select all
// wait till here before parsing the particle trails as forcelink could update origins too
if (((bits & U_ORIGIN1) || (bits && U_ORIGIN2) || (bits & U_ORIGIN3)) && ent->model)
{
// start used oldorg in the original code so it should use msg_origins[1] here
if (ent->model->flags & EF_GIB)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 2);
else if (ent->model->flags & EF_ZOMGIB)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 4);
else if (ent->model->flags & EF_TRACER)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 3);
else if (ent->model->flags & EF_TRACER2)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 5);
else if (ent->model->flags & EF_ROCKET)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 0);
else if (ent->model->flags & EF_GRENADE)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 1);
else if (ent->model->flags & EF_TRACER3)
R_RocketTrail (ent->msg_origins[1], ent->msg_origins[0], 6);
}
Not sure what to do with R_EntityParticles yet; the function looks quite evil to my eyes. The particles are obviously only intended to last for one frame, but at high FPS they'll last for 10s or more frames. I'd add a pt_kill type which sets p->die to -1 and set their initial p->die to something high, then maybe spawn them from R_DrawEntitiesOnList, perhaps.
Posted: Sun May 22, 2011 7:47 am
by r00k
Actually, I chunked the EF_ dynamic lights in there too!