0
\$\begingroup\$

There are a few similar posts, but they only deal with one of the three components I'm looking for, and none seem to handle the case of using a fixed step size (they're all about simulations which use a variable deltaTime).

I'm using a fixed step simulation, and my lifelong (bad) habit has been to eschew units and just directly use parameter values which have deltaT "baked in". Now that I'm trying to add support for different fixed rates, it's biting me in the ass! :/

Here's a simplified example of my code, covering all three cases:

run_speed = 2;      //velocity, linear
brake_decay = 0.95; //viscous damping, exponential
gravity = 1;        //acceleration, quadratic

if(button_pressed)  vel_x = run_speed;
else                vel_x *= brake_decay;

vel_y += gravity;

pos_x += vel_x;
pos_y += vel_y;

I would like to support different fixed timesteps by scaling my existing parameters:

run_speed = 2 * A_to_B_vel;       
brake_decay = 0.95 * A_to_B_damp; 
gravity = 1 * A_to_B_acc;         

My question is: if my fixed timestep is currently A ms/tick, and I want to switch to a fixed timestep of B ms/tick, how do I calculate these three new scaling factors, such that the object follows the same curve through space and time?

(I realize that the actual positions will vary between A and B since we'll be sampling the trajectory curve at a different rate, I just want the underlying curve itself to be identical regardless of how we sample it.)

A_to_B_vel: this is the case which has been covered by many other "framerate independent" questions, and it's relatively trivial because the relationship is linear: A_to_B_vel = B/A

A_to_B_damp: this seems to have been answered here Frame-rate independant movement with acceleration via the Pow() function. This makes sense since it describes an exponential curve; I'd still appreciate someone breaking down the formula though, because the answer given there involves "referenceFPS" and I'd prefer to stick solely to units of time, ie ms/frame, not frame/sec. (and it's not clear that I can simply substitute one for the other into the formula they give)

A_to_B_acc: this is where it gets confusing! According to the above answer, this should be treated similarly to A_to_B_vel (ie scale by the frame duration), however this is definitely wrong, since I've implemented it and it doesn't work. i.e A_to_B_acc = B/A doesn't work. My calculus is very rusty, but I would imagine that the scaling factor needs to involve a dT*dT term somewhere, since acceleration changes position quadratically.

Additionally, if anyone could point me towards a resource which explained the underlying calculus here, I would really appreciate it.

Thanks for your time! :) Raigan

\$\endgroup\$
  • \$\begingroup\$ You can transform a reference FPS into a reference time step (or vice versa) by taking the reciprocal. So referenceFPS = 30f <==> referenceTimestep = 1f/30f \$\endgroup\$ – DMGregory Sep 15 at 17:37
  • \$\begingroup\$ thanks! My issue is that this doesn't work with the formula they gave for damping: scaledDamping = Pow(referenceDamping, deltaTime * referenceFPS); i.e I can't just plug in referenceTimestep and have the result work properly. \$\endgroup\$ – Raigan Burns Sep 15 at 19:53
  • \$\begingroup\$ "They" is me, and yes you can. scaledDamping = Pow(referenceDamping, deltaTime/referenceTimestep); - you literally only have to change the variable name, and make the * into a /. \$\endgroup\$ – DMGregory Sep 15 at 19:56
  • \$\begingroup\$ ah okay -- thanks! Any hints for the acceleration case? \$\endgroup\$ – Raigan Burns Sep 15 at 21:35
0
\$\begingroup\$

The equation of motion under constant acceleration is:

$$\vec p (t) = \vec p_0 + \vec v_0 \cdot t + \frac {\vec a} 2 t^2$$

So if you didn't have your drag, you could modify your code like so...

if(button_pressed)  vel_x = run_speed;
else               // TODO //

pos_x += vel_x * dt;
pos_y += vel_y * dt + 0.5 * gravity * dt * dt;

vel_y += gravity * dt;

Note that we add gravity to the velocity after computing the position, so that we don't double-dip and count gravity twice in our position calculation: once on its own, and once inside the velocity.

To convert your constants from your old time step referenceTimestep to the new one dt, you'd just multiply them by the ratio:

run_speed = reference_run_speed * dt / referenceTimestep
gravity = reference_gravity  * dt / referenceTimestep

Unfortunately, adding drag makes this much more complicated.

This kind of exponential drag has the form:

$$\begin{align} \vec v (t) &= \vec v_0 \cdot b^t\\ \frac {d \vec v(t)} {d t} &= \vec v_0 \cdot \ln(b) \cdot b^t \end{align}$$

Where \$ b \$ is a braking ratio constant, describing what fraction of the velocity should remain after one second. If it's the only thing acting on velocity as above, then we can integrate that just fine.

$$\begin{align} \frac {d \vec p(t)} {d t} &= \vec v(t) = \vec v_0 \cdot b^t\\ \vec p(t) &= \frac {\vec v_0} {\ln (b)} b^t + \vec c = \vec p_0 + \frac {\vec v(t) - \vec v_0} {\ln (b)} \end{align}$$

But as soon as velocity is changing according to this braking ratio and a constant acceleration due to gravity simultaneously, things get a lot more complicated.

$$\frac {d \vec v} {d t} = \vec a + \vec v_0 \cdot \ln (b) \cdot b^t\\ \vec v(t) = \frac {\vec a \cdot e^{b^t} \cdot Ei(-b^t)} {\ln b} + \vec c\cdot e^{b^t}$$

This is according to Wolfram Alpha. That \$Ei\$ there is the Exponential Integral, one of those nasty transcendental functions we usually have to approximate by summing terms from an infinite series, but one not common enough to be included in our standard gamedev math libraries.

If we try to integrate this to get a closed-form expression for our position, Wolfram gives up and just leaves the integral unevaluated:

$$\vec p(t) = \int_1^t\left( \frac {\vec a \cdot e^{b^s} \cdot Ei(-b^s)}{\ln(b)} + \vec c_1 e^{b^s}\right)d s + \vec c_2$$

So, approximation is your only hope here.

I'd say your best bet is to compute position using the kinematic equation for constant acceleration as above, which assumes no braking affects the velocity over the time step. That's wrong, but it's only a little wrong if your braking is gradual and your timestep is short.

Then you can compute your velocity using the integral above if you feel like using a math package that lets you compute the exponential integral. Or you can approximate it stepwise:

if(button_pressed)  vel_x = run_speed;
else                vel_x *= brake_decay;

Where:

brake_decay = pow(reference_brake_decay, dt / referenceTimestep);

Again this is only a little wrong, and it's a lot cheaper. Since perfect consistency with different timesteps is off the table anyway, I'd say this is a reasonable compromise.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you so much for this thorough explanation! I'm going to try the latter approach first and see how that goes. :) I failed to mention: this is in the context of a Position Based Dynamics simulation matthias-research.github.io/pages/publications/posBasedDyn.pdf , so AFAIK my integrator has to be the way it is (some papers refer to it as "semi-implicit Euler"). This will be ticked at around 1khz so that should be tiny enough that approximate drag is close enough. \$\endgroup\$ – Raigan Burns Sep 16 at 0:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.