Skip to content

Commit 918b3cc

Browse files
committed
Make gravity and friction integration below 72fps match 72Hz physics
1 parent 7209bc5 commit 918b3cc

2 files changed

Lines changed: 81 additions & 19 deletions

File tree

Quake/sv_phys.c

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -395,24 +395,34 @@ static int SV_FlyMove (edict_t *ent, float time, trace_t *steptrace)
395395
return blocked;
396396
}
397397

398+
static float SV_EntGravity (edict_t *ent)
399+
{
400+
eval_t *val = GetEdictFieldValue (ent, ED_FindFieldOffset ("gravity"));
401+
return (val && val->_float) ? val->_float : 1.0f;
402+
}
403+
398404
/*
399405
============
400406
SV_AddGravity
401407
408+
Gravity is applied in two phases: the move runs with velocity biased to the
409+
average of this frame's 72Hz gravity steps, which lands positions exactly on
410+
the canonical 72Hz trajectory for any frame duration. SV_FinishGravity removes
411+
the bias after the move; both phases sum to gravity*frametime and the bias is
412+
zero when frametime == 1/72.
402413
============
403414
*/
404415
static void SV_AddGravity (edict_t *ent)
405416
{
406-
float ent_gravity;
407-
eval_t *val;
408-
409-
val = GetEdictFieldValue (ent, ED_FindFieldOffset ("gravity"));
410-
if (val && val->_float)
411-
ent_gravity = val->_float;
412-
else
413-
ent_gravity = 1.0;
417+
ent->v.velocity[2] -= SV_EntGravity (ent) * sv_gravity.value * (host_frametime + 1.0 / MAX_PHYSICS_FREQ) * 0.5;
418+
}
414419

415-
ent->v.velocity[2] -= ent_gravity * sv_gravity.value * host_frametime;
420+
static void SV_FinishGravity (edict_t *ent)
421+
{
422+
// entities that landed during the move keep their clipped velocity, like at 72fps
423+
if ((int)ent->v.flags & FL_ONGROUND)
424+
return;
425+
ent->v.velocity[2] -= SV_EntGravity (ent) * sv_gravity.value * (host_frametime - 1.0 / MAX_PHYSICS_FREQ) * 0.5;
416426
}
417427

418428
/*
@@ -1029,10 +1039,20 @@ static void SV_Physics_Client (edict_t *ent, int num)
10291039
if (!SV_RunThink (ent))
10301040
return;
10311041
if (!SV_CheckWater (ent) && !((int)ent->v.flags & FL_WATERJUMP))
1042+
{
10321043
SV_AddGravity (ent);
1033-
SV_CheckStuck (ent);
1034-
assert_always (!ent->free);
1035-
SV_WalkMove (ent);
1044+
SV_CheckStuck (ent);
1045+
assert_always (!ent->free);
1046+
SV_WalkMove (ent);
1047+
if (!ent->free)
1048+
SV_FinishGravity (ent);
1049+
}
1050+
else
1051+
{
1052+
SV_CheckStuck (ent);
1053+
assert_always (!ent->free);
1054+
SV_WalkMove (ent);
1055+
}
10361056
break;
10371057

10381058
case MOVETYPE_TOSS:
@@ -1184,11 +1204,15 @@ static void SV_Physics_Toss (edict_t *ent)
11841204
VectorScale (ent->v.velocity, host_frametime, move);
11851205
trace = SV_PushEntity (ent, move);
11861206

1187-
if (trace.fraction == 1)
1188-
return;
11891207
if (ent->free)
11901208
return;
11911209

1210+
if (ent->v.movetype != MOVETYPE_FLY && ent->v.movetype != MOVETYPE_FLYMISSILE)
1211+
SV_FinishGravity (ent);
1212+
1213+
if (trace.fraction == 1)
1214+
return;
1215+
11921216
if (ent->v.movetype == MOVETYPE_BOUNCE)
11931217
backoff = 1.5;
11941218
else
@@ -1256,6 +1280,8 @@ static void SV_Physics_Step (edict_t *ent)
12561280
if (ent->free)
12571281
return;
12581282

1283+
SV_FinishGravity (ent);
1284+
12591285
if ((int)ent->v.flags & FL_ONGROUND) // just hit ground
12601286
{
12611287
if (hitsound)

Quake/sv_user.c

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,40 @@ void SV_UserFriction (void)
144144
else
145145
friction = sv_friction.value;
146146

147-
// apply friction
148-
control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed;
149-
newspeed = speed - host_frametime * control * friction;
147+
// apply friction, matching the canonical 72Hz decay for any frame duration:
148+
// exponential while speed is above stopspeed, then linear below it
149+
{
150+
const double tau = 1.0 / MAX_PHYSICS_FREQ;
151+
double s = host_frametime / tau;
152+
double r = 1.0 - friction * tau;
153+
double ns = speed;
154+
155+
if (r <= 0)
156+
{
157+
// degenerate friction values stop within one tick, keep the classic formula
158+
control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed;
159+
ns = speed - host_frametime * control * friction;
160+
}
161+
else
162+
{
163+
if (ns >= sv_stopspeed.value)
164+
{
165+
double k_cross = log (sv_stopspeed.value / ns) / log (r);
166+
if (s <= k_cross)
167+
{
168+
ns *= pow (r, s);
169+
s = 0;
170+
}
171+
else
172+
{
173+
ns = sv_stopspeed.value;
174+
s -= k_cross;
175+
}
176+
}
177+
ns -= s * tau * friction * sv_stopspeed.value;
178+
}
179+
newspeed = (float)ns;
180+
}
150181

151182
if (newspeed < 0)
152183
newspeed = 0;
@@ -248,12 +279,17 @@ void SV_WaterMove (void)
248279
wishspeed *= 0.7;
249280

250281
//
251-
// water friction
282+
// water friction, matching the canonical 72Hz decay for any frame duration
252283
//
253284
speed = VectorLength (velocity);
254285
if (speed)
255286
{
256-
newspeed = speed - host_frametime * speed * sv_friction.value;
287+
const double tau = 1.0 / MAX_PHYSICS_FREQ;
288+
double r = 1.0 - sv_friction.value * tau;
289+
if (r <= 0)
290+
newspeed = speed - host_frametime * speed * sv_friction.value;
291+
else
292+
newspeed = speed * pow (r, host_frametime / tau);
257293
if (newspeed < 0)
258294
newspeed = 0;
259295
VectorScale (velocity, newspeed / speed, velocity);

0 commit comments

Comments
 (0)