15
\$\begingroup\$

I'm starting to learn how to DIY physics, and I have a question about implementing integration at the most basic level (i.e. this is not a Euler vs. RK4 question).

Nearly every example I come across has some integrate() function which gets the timestep since the last update, and updates the acceleration (and/or velocity and/or position) since the last update.

In the simplest form: position += velocity * deltaTime

However, I don't understand why it's accumulated like this when it could just as easily be gotten by changing a function. For example: getPosition = makeNewFunction() which could return something that has the signature of Time -> Position, and the inner workings of that function are generated via the appropriate mathematical formula.

That way, there is no accumulation... whenever the position needs to be gotten, it calls that function with the current time.

My newbie understanding is that this would also avoid the errors that come from accumulation... so why does this not work, what am I missing?

(fwiw I did put together a basic proof of concept of this idea- though it's also testing a few other things at the same time so it's not the cleanest example: https://github.com/dakom/ball-bounce-frp)

EDIT 1: as mentioned in the comments, it's probably important to point out that I have not learned about changing acceleration yet, or dealing with jerk and other things that require higher-order integration than constant acceleration.

EDIT 2: here is some basic sample code of the idea, and pseudo javascript syntax - note that getKinematicPosition is partially applied so it is returning a new function of just Time -> Position:

I'm sticking to position here but it could be something else, like getVelocity, I guess...

getKinematicPosition = initialVelocity => acceleration => time => 
  ((.5 *acceleration) * (time * time)) + (initialVelocity * time);

getPosition = getKinematicPosition ([0,0,0]) (GRAVITY);

onTick = totalTime => {
   position = getPosition (totalTime);
   onCollision = () => {
     getPosition = changeTheFunction(totalTime);
     //changeTheFunction uses totalTime to base updates from 0
     //it could use getKinematicPosition or something else entirely
   }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ What would your function do, if you have non constant speed/acceleration? \$\endgroup\$ – Linaith Jul 25 '18 at 13:58
  • \$\begingroup\$ I have no idea! :D If that's the reason - e.g. I haven't gotten to changing acceleration yet, I'd really appreciate a fuller explanation of where this would break down as the answer (otherwise I may go down this functional road and hit a dead-end!) \$\endgroup\$ – davidkomer Jul 25 '18 at 14:00
  • 6
    \$\begingroup\$ Well if your object just goes around in a circle, then sure... what about when it's a box that the player is pushing? When you call getPosition(now+100) does it predict the future to know when the player will stop pushing it? When you call getPosition(now-1000) does it have to remember the past? \$\endgroup\$ – immibis Jul 26 '18 at 1:54
34
\$\begingroup\$

...the inner workings of that function are generated via the appropriate mathematical formula...

This will work for certain classes of problems, and the key phrase to search for is a closed-form solution. For example, in Kerbal Space Program, the motion of a spacecraft in orbit is calculated this way. Unfortunately, most non-trivial problems (e.g. atmospheric reentry of said spacecraft) have no known closed-form solution. Thus the need for mathematically simpler numerical approximations (i.e. integrate() over time).

\$\endgroup\$
  • \$\begingroup\$ Ahh... awesome! If you have a minute - what do you think of aiming for this functional approach, and then fall back on accumulating if I can't figure out how to make it work (e.g. if I'm dealing with a non-closed form problem or I can't figure out how to turn it into one)? I like the idea of generating functions since it fits the math 1:1 - but if I'll always inevitably hit a dead-end it might not be worth it... \$\endgroup\$ – davidkomer Jul 25 '18 at 15:16
  • 8
    \$\begingroup\$ @davidkomer Why do you even want to keep generating functions? If you can pull this off, then you can just pre-calculate and record the whole trajectory! Of course, people already do this: it's called animation, and it has its share of subtleties. \$\endgroup\$ – Joker_vD Jul 25 '18 at 15:58
  • \$\begingroup\$ The functions change based on runtime dynamics... see the FRP ball-bounce for example \$\endgroup\$ – davidkomer Jul 25 '18 at 18:25
  • \$\begingroup\$ Actually I'll need to update that example to be something more like pong, with moving objects controlled randomly / by the user... \$\endgroup\$ – davidkomer Jul 26 '18 at 6:02
10
\$\begingroup\$

The problem with your approach is, that you don't have a history of your object. You can calculate the position if you move in a direction, but what happens if you hit something and bounce back?
If you accumulate from your last known position, you can handle the impact and go on from there. If you try to calculate it from the start, you have to recalculate the impact every time, or set it as new start position.

Your example reminded me of a racing game. (I don't know if the position would be controlled by the physics engine, but i think it works great to explain)
If you drive with your car you can accelerate and slow down. You can't calculate your position without knowing how the speed profile of your car looked like from start to now. Accumulation the distance is way easier than storing the speed you had in every frame from start to now.

Disclaimer: I haven't written game physics by now, that's just how i see the problem.

Edit:
In this diagram you can see how the values change over time.
red = acceleration (from start accelerating to sloving down)
green = speed (from starting to stopping)
blue = the way you went.

The total speed is the integral of the acceleration from your start point to your actual logation. (The area between line and axis)
The way is the integral of your speed.
If you know the values for your acceleration, you can calculate the other values. But if i'm not mistaken are integrals also calculated by accumulation on PCs. And it's way more overhead to have all acceleration values stored.
Plus it's probably too much to be calculated every frame.

acceleration/speed/way-time diagram

I know, my paint skills are great. ;)

Edit 2:
This example is for linear movement. Canging direction makes this even more difficult.

\$\endgroup\$
  • \$\begingroup\$ "or set it as new start position." - yes, but I don't see the problem with that :) Re: car example... I'm getting the strong feeling that I really need to start playing with something more complex like that to intuitively understand where this fails... \$\endgroup\$ – davidkomer Jul 25 '18 at 14:43
  • \$\begingroup\$ setting a new position is probably not a problem. i edited the car part \$\endgroup\$ – Linaith Jul 25 '18 at 15:30
  • 1
    \$\begingroup\$ In a car game, I imagine acceleration would be even more complex. There would probably be jumps and spikes, and it may depend on speed. (E.g., the acceleration decreases to 0 as the car approaches top speed.) \$\endgroup\$ – jpmc26 Jul 25 '18 at 21:57
  • 3
    \$\begingroup\$ @davidkomer don't even bother with a car (unless you want to), a basic platformer will do. How does mario.getPosition(Time) work in Super Mario Bros? \$\endgroup\$ – immibis Jul 26 '18 at 1:55
8
\$\begingroup\$

However, I don't understand why it's accumulated like this when it could just as easily be gotten by changing a function. For example: getPosition = makeNewFunction() which could return something that has the signature of Time -> Position, and the inner workings of that function are generated via the appropriate mathematical formula.

You can!

It is called using an analytical or closed form solution. It has the benefit that it is more accurate, since rounding errors that accumulate over time are non-existent.

However, this works if and only if you know such a closed form beforehand. For games, this is quite often simply not the case.

Player movement is erratic and simply can not be put into some pre-computed function. The player can and will change his velocity and orientation quite often.

NPCs could potentially utilize closed form solutions, and in fact they sometimes do. This has some other drawbacks, however. Think about a simple racing game. Every time your vehicle collides with another vehicle, you have to change your function. Maybe the car moves faster depending on the underground. Then finding such a closed-form solution will be quite difficult. In fact, there are likely more cases where finding such a closed form is either impossible or so complicated that it is simply not feasible.

A great example of using a closed-form solution is Kerbal Space Program. As soon as your rocket is in orbit and not under thrust, KSP may put it "on rails". Orbits are pre-determined in the two-body system, and are periodic. As long as the rocket doesn't apply any more thrust, you already know where the rocket will be, and you can simply call getPositionAtTime(t) (its not named exactly that, but you get the idea).

In practice, however, just using step-by-step integration is often much more practical. But when you see a situation where a closed-form solution exists and is easy to calculate, go for it! There is no reason not to use it.

For example, if your character is aiming a cannon, you could easily show the predicted impact point of the cannon ball using a closed-form solution. And, if your game does not allow for the course of the cannon ball to be altered (no wind for example), you can even use it to move the cannon ball. Note that you need to take special care of obstacles moving into the path of your cannon ball, then.

There are lots of similar situations. If you are building a round-based game, then there are likely to be far more closed-form solutions available then when building an RTS game, since you know all parameters beforehand and can say with certainty that they do not change (nothing moves suddenly into that path, for example).

Note that there are techniques to combat then numerical inaccuracies of stepped integration. For example, you can keep track of the accumulated error and apply a corrective term to keep the error in check, e.g. Kahan Summation

\$\endgroup\$
8
\$\begingroup\$

In the case of just a simple bouncing ball, coming up with closed form solutions is easy. However, the more complex systems tend to require solving an Ordinary Differential Equation (ODE). Numerical solvers are required to handle all but the simplest cases.

There are indeed two classes of numeric ODE solvers: explicit and implicit. Explicit solvers provide a closed form approximation for your next state, while implicit solvers require solving an equation to do so. What you describe for your bouncing ball is actually an implicit ODE solver, whether you knew it or not!

Implicit solvers have the advantage of being able to use much larger time steps. For your bouncing ball algorithm, your timestep can be at least as large as the duration until the next collision (which would change your function). This can make your program run much faster. However, in general, we can't always find good implicit solutions to the ODEs we are interested in. When we can't, we fall back on explicit integration.

The big advantage I see with explicit integration is that the gotchas are well known. You can open up any textbook from the '60s and read all you need to know about the little quirks that arise with particular integration techniques. Thus, a developer learns these skills once, and they never have to learn them again. If you're doing implicit integration, each use case is slightly different, with slightly different gotchas. It's a bit harder to apply what you learned from one task to the next one.

\$\endgroup\$
1
\$\begingroup\$

pos(t) = v(t) * t

only works if pos(0) = 0 and v(t) = k

you can't relate position to time without knowledge of the initial condition and the entire velocity function, so the equation is as an approximation of the integral

pos(t) = integral of v(t)dt from 0 to t

EDIT _________

Heres a little proof per the comments (assuming pos(0)=0)

let v(t) = 4

eqn 1: pos(t) = 4 * t (correct)

eqn 2: pos(t) = c + 4 * t from 0 to t = 4 * t (correct)

let v(t) = 2 * t

eqn 1: pos(t) = 2 * t^2 (wrong)

eqn 2: pos(t) = c + t^2 from 0 to t = t^2 (correct)

I should add that your equation already factors in constant acceleration (i.e. your equation is eqn 2 where v(t) = v0 + a * t and the limits of integration are t0 and t), so your equation should work as long as you update the initial position, initial velocity, and the acceleration remains constant.

EDIT2 ________

I should also add that you could also calculate position with initial pos, initial velocity, initial acceleration, and constant jerk. In other words, you can create a function based off of eqn 2 that represents position vs time by separating it into it's derivatives i.e. velocity, jerk, whatever comes next, etc, etc, etc, but you will only be accurate in your equation if v(t) can be modeled that way. If v(t) can't be modeled with just velocity, acceleration, constant jerk, etc, then you need to go back to an approxomation of eqn 2, which tends to happen when you have things bouncing, air resistance, wind, etc.

\$\endgroup\$
  • \$\begingroup\$ a constant. it just means v(t) must not vary over time \$\endgroup\$ – Kyy13 Jul 25 '18 at 14:29
  • \$\begingroup\$ I'll have to sit with this and work out why it's true... upvoting for now :) I posted a code sample in the question in case that changes things \$\endgroup\$ – davidkomer Jul 25 '18 at 14:34
  • \$\begingroup\$ no problem, updated again with better words :) \$\endgroup\$ – Kyy13 Jul 25 '18 at 15:25

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.