7
\$\begingroup\$

I'm looking for information on how to save the runtime state of rigid bodies with Bullet Physics. Most of my world consists of static objects, but I have a few dynamic and kinematic objects as well. I need to take a snapshot of the game state and later restore the exact same state so that it produces the same simulation as if there wasn't any pause. I can easily (and prefer to) recreate the whole world including the rigid bodies, their configuration, collision shapes and constraints.

In addition I need to reset the objects to their initial state, but I think solving the save/restore problem also solves resetting.

I'm using JBullet, which is based on Bullet version 2.72. This means that the built-in serialization is not available for me, as it was added in version 2.76. However answers utilizing the serialization are also welcome, as Java has built-in serialization and others might find it useful. However the description on the wiki page sounds like it's meant for storing whole worlds instead of just the runtime state.

Checking the source code of RigidBody and CollisionObject reveals lots of internal non-final fields of which many are pointers instead primitives. I don't know which of the fields I should save and how to handle the non-primitive fields.

I am surprised to not having found any information on the Internet about this, as I think this is necessary for any game using Bullet physics and requiring exact save and restore functionality. I would prefer a solution that requires only the public API, but I'm open to other solutions as well including modifying the source code as a last resort. I'm also aware of the differences on floating point operations between platforms, but that is not a concern for me.

Edit:

I implemented the solution suggested by Byte56. I'm now saving and restoring worldTransform, linearVelocity and angularVelocity. I also tried motionState, but that did not have any effect. After restoring the state my object seems to continue the correct trajectory until first collision with a (static) wall. After the collision the object doesn't follow the same path anymore. Interestingly there seems to be only 5-7 slightly different paths that the object randomly chooses.

Without the save and restore my simulation is completely deterministic and always proceeds exactly the same results when running multiple times with the same starting values.

I was thinking that the randomness could have been because of uneven number of simulation steps. I'm using frame skipping and passing 1.0 / 60 as the delta time for stepSimulation: dynamicsWorld.stepSimulation(deltaTime, 100, 1.0f / 200);. I tried changing 200 to 180, but that didn't help. I wonder if there is something in the DiscreteDynamicsWorld that I should also save. I also tried to save DiscreteDynamicsWorld.localTime through reflection, but no luck.

Edit2:

After closer investigation even the initial trajectory before any collisions is slightly off. However there seems to be only a constant offset in the coordinates and after the first collision the path changes direction compared to the original.

I implemented a custom serializer using Java Reflection API, which recursively stores all primitive fields from RigidBody and DiscreteDynamicsWorld. That didn't help. I also tried changing physics updating to just dynamicsWorld.stepSimulation(1.0f / 60.0f);, but no luck there either. Now I'm out of ideas how to proceed.

\$\endgroup\$
3
  • \$\begingroup\$ Hey, I just noticed that the Bullet Physics solver has a setRandSeed function that takes a long as an argument. Perhaps saving and resetting a seed will help? I haven't tried it out yet, just thought I'd let you know for something to look into. I'm not sure what the solver uses random values for... \$\endgroup\$
    – House
    May 11, 2013 at 5:29
  • \$\begingroup\$ The comment says "btSeed2 is used for re-arranging the constraint rows. improves convergence/quality of friction". Not a problem yet, but looks important later when I need to serialize constraints. \$\endgroup\$
    – msell
    May 11, 2013 at 7:04
  • \$\begingroup\$ Actually, colliisions are solved using constraints, so the randomness might be because of this. \$\endgroup\$
    – cmourglia
    Nov 4, 2016 at 17:32

5 Answers 5

8
\$\begingroup\$

I just write the body and shape values to a file. Then when reading them, I create a new body and shape and set the values to those read from the file. From what I've seen this works great. I haven't seen any strange behaviors or anything like that. Bodies that were flying through the air, continue to do so when the game is reloaded.

The values I read/write are: Mass, Friction, LocalInertia, LinearVelocity, AngularVelocity.

Acceleration values are stored from other components as I just apply impulses anyway. Saving takes place after a frame step, so there are no existing forces other than gravity.

I just take those values and create a new physics object just like I was adding a new one to the DyanmicsWorld.

Works well enough for me. Your results may vary if you're using the engine in a more advanced way than I am.


I've tested this out. I saved my game right when a physics object was falling towards a ramp on the ground. I then loaded the game and watched where the physics object ended up after hitting the ramp and rolling to a rest. Each time I loaded and watched the object, it ended up in the exact same spot, pixel for pixel (I took screen shots and compared). I performed the test five times with the same result.

So then I thought I would save it again, a little farther along its fall before it hits the ramp, then load again. The next time I loaded, it ended in a different place! However, now every time I load it, it ends up in that second spot.

Now, every time I load the game it always ends in the same spot, but that spot can change depending on the starting conditions.


Looking into it more, I've found that it's a common issue. See this bug report filed against bullet. And this post about serialization and determinism. The user claims to have good determinism if they save/restore when the scene is mostly settled (which doesn't seem very useful to me). This post claims contains information claiming that 3.x Bullet will be much better at this.

So, it appears to be a failure with Bullet Physics. There is hope that this will be solved soon with 3.x. Unfortunately, we don't really know how long it'll take once 3.x is out for that to get ported to JBullet.

If you require determinism now, then you'll have to implement a system independent of Bullet Physics. Otherwise you can wait to see what 3.x brings to the table.

\$\endgroup\$
8
  • \$\begingroup\$ I tried to save worldTransform, linearVelocity and angularVelocity, but restoring them doesn't give identical results :(. My mass and local inertia are constants. \$\endgroup\$
    – msell
    Apr 16, 2013 at 15:04
  • \$\begingroup\$ What's different about the results? \$\endgroup\$
    – House
    Apr 16, 2013 at 15:04
  • \$\begingroup\$ It looks initially correct, but the restored simulation starts to deviate from to the simulation before saving. I'm implementing a replay, so I need the physics simulation to be bit-by-bit match. \$\endgroup\$
    – msell
    Apr 16, 2013 at 15:19
  • \$\begingroup\$ Interesting. We'll I do some further tests this evening and see if my simulations are accurate and if I missed anything here. I'll let you know what I come up with. \$\endgroup\$
    – House
    Apr 16, 2013 at 15:36
  • \$\begingroup\$ I have updated my question with the results of what I have now tried. \$\endgroup\$
    – msell
    Apr 19, 2013 at 5:25
5
+200
\$\begingroup\$

From your latest comments, it looks like you're trying to save/restore all the internal simulation state inside Bullet (overlapping pairs, contact points, etc.). This sounds... daunting!

Another idea: Every frame, remove and re-add all your dynamic objects. Obviously, this is hugely bad for performance, but you indicated that you don't have many dynamic objects, so maybe it'll be okay. The goal here is to get Bullet to essentially clear all its internal state that's causing determinism problems.

Just to illustrate my thought process, let's take this idea to the extreme: You could destroy and re-create your Bullet world and all your Bullet objects every frame. Of course, you would re-create dynamic objects with the saved positions and velocities from the previous frame, so the simulation would still appear normal and continuous. This would almost certainly be deterministic, because you're preventing Bullet from maintaining any of its own internal state from one frame to the next.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks, that's an interesting idea. I was next going to try to modify the JBullet source code to clear the caches every frame, but your suggestion could be done with the public API. \$\endgroup\$
    – msell
    Apr 27, 2013 at 19:27
  • \$\begingroup\$ I tried to remove and re-add all my dynamic objects and even static ones, but again, didn't help. If I recreate the whole physics world every frame, I finally get correct behaviour. Also this doesn't affect the performance too much, so now I have a working solution. Thank you very much. I can now also better investigate what parts of the world can be kept between frames. \$\endgroup\$
    – msell
    Apr 28, 2013 at 6:19
3
\$\begingroup\$

So, if I understood your problem, you have a way to set an initial state, and a consistent function for advancing the state by one frame. You also have a way to retrieve a current state, but it is not reliable, since restoring to it gives different results than if you had not restored it in the first place.

In that case, I have three options for you to consider:

  1. If you manage to reproduce the exact simulation by setting the same initial parameters, then you can save a state by saving those initial parameters, and the frame number where you want to stop. When loading the state, you reset your initial parameters and run the simulation as fast as you can, without pausing.

    This is in my opinion a pragmatic solution, which will probably require the least amount of changes in your code. If the purpose of your system is to implement replays, this will work pretty well, as it supports playback and forward seeking natively. However, reverse playback, backward and random seeking are very expensive. Which leads us to:

  2. If your purpose is to have a randomly seekable, but mostly for playback simulation, and your only problem with saving and restoring in the way Byte56 suggested, is that it gives different -but not incorrect- results, then you can implement option number 1, with keyframes. This would mean that during the initial simulation (the one you're recording), every n frames, you save the state, restore it, and then continue.

    When seeking, you first restore the closest saved state before the frame you wish to seek to, and then use option 1 above to seek to your specific frame. Once again, this works when seeking randomly, but is only marginally better than option 1 when playing backwards. By the way, this is how many video codecs work.

    Needless to say, the closer your keyframes are, the more time it will take to save them. However, seeking will be faster. I'm pretty sure you can reach a good compromise in your case.

  3. Use continuations. A continuation is an object which represents the state of a program (or part of it). Many languages, including C#, Lua and Python have mature support for continuations; also, languages where you have direct access to your memory like C, can also make it easier to implement continuations.

    In the Java case, unfortunately, the closest implementation seems to be Javaflow, which apparently is not yet completed. But may be worth a look. This may require some substantial changes in your program, and you will probably be saving much more than just the relevant parameters.

    To use continuations, you simply create a continuation object whenever you want to save the program state, and then resume from it whenever you want to seek to it.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for the excellent reply. Option 1 is a good idea. I have to check how fast I could replay just the physics, but I'm afraid that it will take a while for half an hour of gameplay. Also I don't know how to set the initial parameters without recreating the physics world, so it's a bit complicated but still doable. Option 2 would probably be the best way, although I think I have to save triggered actions separately on exact frames, as the physics environment is very sensitive. \$\endgroup\$
    – msell
    Apr 24, 2013 at 6:51
  • 1
    \$\begingroup\$ About Option 3 I don't see how it would help, if it saves just the stack and local variables. Java can save the full state of object hierarchy with serialization, but I already tried to use that without success. I don't want to save everything (geometry, etc.), but just the runtime state. I still hope I could find a solution on how to actually save the state of JBullet even if it requires source code changes. This answer is a good backup solution if that fails. \$\endgroup\$
    – msell
    Apr 24, 2013 at 6:52
  • \$\begingroup\$ @msell: if replaying the physics is too expensive, you can go with option 2. And regarding option 3, continuations are meant to save the entire (or partial, depending on implementation) state of the VM at a much lower level than what you get with simple serialization. It's not just stack, but also stuff like heap, cpu state, flags and even file handles. This is handled at the VM level, so there is a much better probability of getting identical results than by just serializing your objects. Many games implement game state in a different VM (for example in lua) to simplify saving the state. \$\endgroup\$ Apr 24, 2013 at 7:04
2
\$\begingroup\$

I made Bullet physics deterministic by saving and restoring all bullet memory: Simply call btAlignedAllocSetCustom and btAlignedAllocSetCustomAligned for a custom allocator. This always works.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ That's an interesting approach. Unfortunately it doesn't help me, as I'm using the Java port where memory is managed by the JVM. For others this sounds helpful. How do you connect the restored Bullet memory to the rest of the game world when loading? \$\endgroup\$
    – msell
    May 22, 2014 at 17:58
0
\$\begingroup\$

The problem has nothing to do with physics state loading or saving. The suggestion that Byte56 has given is perfectly valid; the real problem at hand here and the reason you're seeing deviating behavior however is your physics is using a variable time step. Read this question and answer and change your time step for physics to be fixed if you don't want behavior that is different on separate runs.

\$\endgroup\$
3
  • \$\begingroup\$ My timestep is fixed and the behavior is the same with separate runs. \$\endgroup\$
    – msell
    Apr 19, 2013 at 5:47
  • \$\begingroup\$ Why do you pass in delta time then? \$\endgroup\$ Apr 19, 2013 at 6:04
  • \$\begingroup\$ It's a constant. Should have probably named it as timeStep. \$\endgroup\$
    – msell
    Apr 19, 2013 at 7:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .