B2D2: Box2D Time Dilation Experiment


For the last few weeks, amongst many other things, I’ve been toying with the idea of adding time dilation mechanics to the 2D physics engine Box2D (B2D). I honestly can’t recall what first moved me to think about this, and while I have ideas for game mechanics featuring similar time manipulation, I have none which would require physics simulation. Nevertheless, it’s been an interesting problem, and given me a reason to dig into and understand the internals of a physics engine. You can see a video demonstrating my progress so far below, and scroll down further for more information.

Although the implementation isn’t very far along, a lot of time has gone into exploring the B2D code, thinking about potential problems, and finding the ideal place to apply temporal forces. I am not a physicist (theoretical or otherwise), so my model of time dilation is based upon intuition and practicality of implementation:

The idea is that each object has it’s own temporal velocity (TV) which functions as a multiplier for its passage of time. Therefore, a body with a TV of 2.0 will accelerate twice as quickly, move twice as fast; a motored constraint with a TV of 0.5 will output half as much force. A time dilation field (TDF) is a physical space defined by a B2D body, for which a temporal velocity may be specified separately to the ambient temporal velocity. Bodies are affected by temporal drag (TD) based upon standard fluid dynamics drag calculations, considering their velocity relative to contacted TDFs. Thus a body will gradually adjust TV to match it’s environment.

The drag model is intended as a graceful way to handle overlapping TDFs. I had previously considered a multiplicative model, where the area overlapped by two TDFs (a & b) has a TV of TVa*TVb, but this didn’t seem to make much sense to me, physically, and requires a more complex implementation. Instead, I use the analogy of sticks drifting in a river: the river (TDF) flows at a certain speed (TV), which pushes the stick (body) according to a drag function and a collection of variables. In the case where TDFs overlap, I treat it as the ‘river’ having other currents running through it: the body is affected by drag from both currents, so it’s TV should approach their average.

There is, however, an awkward case in my implementation currently, the logic behind which has become apparent to me as I type this: Two overlapping TDFs, both with a higher TV than a body within will cause the body to experience a positive force from each, accelerating faster than it should. This is true for two fields slower than the body too. This would not be an issue if drag was applied directly to the TV, as the current (relative) TV is part of the drag equation, but as it stands both forces are combined in a temporalForce variable to be applied later in the step.

Another significant obstacle in improving the ‘accuracy’ of this (pure fantasy physics mechanic) is the ability to quickly calculate a reference area for the drag calculation. Ideally, this would be the area of a body which intersects with each field, and then the reference area for ambient TV drag would be the area not intersecting any field. I am not aware, however, of an efficient method to calculate intersection areas, and the ambient reference area calculation is complicated when the body intersects TDF overlaps. Currently, bodies simply experience full drag force from any TDF they contact, and only from the ambient TV when they contact no TDFs. Another alternative would be to consider the intersection of fixture vertices with TDFs, though this would be problematic for large or highly irregular fixtures.

There are, of course, other issues and features yet missing. I have a pretty solid idea what problems I’ll face down the road, and some ideas of how to address a few of them:

  • Constraints are not yet affected by temporal velocity; I believe only their frequency and motor parameters may be affected, and their TV can likely be treated as an average of the associated bodies in most cases.
  • My implementation features an observer TV variable, and all movements are based on the relationship between it and the body TVs. This means that if the observer TV is 2.0, bodies with a TV of 1.0 will appear to move at (1.0 / 2.0 =) 0.5x speed. The catch here is that for accuracy, we need to integrate for the change in body TV between steps, AND for observer TV. Not only do I need to revise some calculus before I can confidently implement the latter, but it also requires that I either cache previous observer TV values, or maintain an observerTemporalForce variable.
  • I need to go over the TOI (Time of Impact) solver code and work out how exactly TV impacts this, as currently this area is untouched. Also, relevant: I need to revisit my old idea of modifying the position and velocity iterations based upon the the current maximum TV in a simulation. My intention there is to maintain stability in accelerated time streams. The problem is that in order to utilize the current maximum TV we would need to either apply temporal forces to TVs in a separate loop prior to island* solving. Alternatively, we can look into the viability of allowing iteration counts to vary between islands, or utilizing the maximum TV from the previous step.
  • By using the standard drag equation, TDFs could potentially be further configurable than they currently are, for example by adding a ‘fluid density’ variable. Currently my implementation uses a (rather high) hard-coded fluid density for temporal drag calculations, to ensure rapid speed adaptation.
  • I’d like to experiment with applying a shader which stretches geometry based upon TV and (spatial) velocity. There’s already a suddenly slowdown upon exiting an accelerated TDF which is reminiscent of warp-in effects from Sci Fi media, and I think this could make it look extra cool.

In all likelihood I’ll soon have to put this on hold indefinitely and focus on some more practical projects, but it’s been fun and informative, regardless.

* Islands are B2D structures into which bodies are clustered prior to solving, based upon their contacts and constraints.