Framerate-independent physics

Post tutorials on how to do certain tasks within game or engine code here.
Post Reply
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Framerate-independent physics

Post 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 ();
}
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

That article was a fun read.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Post 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.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post 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. ;)
Last edited by mh on Mon May 16, 2011 9:12 am, edited 1 time in total.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post 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.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
revelator
Posts: 2621
Joined: Thu Jan 24, 2008 12:04 pm
Location: inside tha debugger

Post 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 ?
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post 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 .... ;)
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post 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.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post 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.
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post 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.
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post 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.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
qbism
Posts: 1236
Joined: Thu Nov 04, 2004 5:51 am
Contact:

Post 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!
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post 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?)
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
mh
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Post 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.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
r00k
Posts: 1111
Joined: Sat Nov 13, 2004 10:39 pm

Post by r00k »

Actually, I chunked the EF_ dynamic lights in there too!
Post Reply