Bullet binary serialization
From Physics Simulation Wiki
Bullet 2.76 onwards has built-in capability to save the dynamics world to a binary dump, without additional dependencies. This dump can be saved as a .bullet file.
The Maya_Dynamica_Plugin supports export to the .bullet binary format. Also there is a modified Blender 2.49b (updated 2010 June 1st to export object names) version that supports export to .bullet through the game engine, using PhysicsConstraints.exportBullet("fileName").
The Bullet serialization system is very basic. Those who use it will likely need to take additional steps to save/load all the aspects they require. In some cases, it is recommended to not use the Bullet serialization system at all, instead opting to recreate Bullet objects from scratch upon loading. For example, if you are developing a game in which Bullet is only one aspect, it will be far easier to save simple things such as transformation and velocities than trying to integrate the Bullet serialization system into whatever serialization system you use for the rest of your design. Generally, if you have non-Bullet things to serialize (that relate to bullet objects), I'd say it is best to avoid the Bullet serialization system entirely. On the other hand, if your usage of Bullet is such that you create Bullet objects and then "forget" about them (allow them to exist in the simulation world without any related structures or further Bullet-external interactions), then Bullet serialization will help you save and load that state rather conveniently. In considering whether you should use the Bullet serialization system, it is recommended that you take a look at the source code (btBulletWorldImporter.cpp for starters).
Saving a dynamics world to a .bullet file
Serialization, saving the objects and shapes into a buffer, is built-in. No additional libraries are necessary. Here is an example how to save the dynamics world to a binary .bullet file:
// serialize the dynamics world btDefaultSerializer* serializer = new btDefaultSerializer(); dynamicsWorld->serialize(serializer); // create a file and write the world to file FILE* file = fopen("testFile.bullet","wb"); fwrite(serializer->getBufferPointer(),serializer->getCurrentBufferSize(),1, file); fclose(file);
Several Bullet demos show how to load and save .bullet files, including Bullet/Demos/SerializationDemo and Bullet/Demos/ConvexDecompositionDemo.
Saving a single object, shape or acceleration structure
Although it is easiest to serialize the entire world, it is also possible to just serialize small parts into the .bullet file format. The .bullet file format is essentially derived from the .iff format, so it uses chunks to store information. Here is an example how to serialize a collision shape:
// create a serializer btDefaultSerializer* serializer = new btDefaultSerializer(); // start the serialization and serialize the trimeshShape serializer->startSerialization(); trimeshShape->serializeSingleShape(serializer); serializer->finishSerialization(); // create a file and write the serialized content to file FILE* file = fopen("testFile.bullet","wb"); fwrite(serializer->getBufferPointer(),serializer->getCurrentBufferSize(),1, file); fclose(file);
See Bullet/Demos/ConcaveDemo for further information how to load such collision shape or how to serialize just the btOptimizedBvh.
Loading a .bullet file, creating objects and adding them to the world
When loading a .bullet file, the BulletFileLoader library is needed to deal with endian-ness, 32/64 bit conversion. BulletWorldImporter, an example importer, takes the memory objects from BulletFileLoader and instantiates Bullet objects (btRigidBody, btCollisionShape etc) and adds them to the world. Do not mistake btBulletWorldImporter as a full featured loader, it is intended to be only a starting point. Anything past a very basic serialization of objects and positions will require that you derive from the class and make some improvements.
- Make sure to add "Bullet/Extras/Serialization/BulletWorldImporter" to your include path.
- Add BulletWorldImporter and BulletFileLoader to your project (in addition to BulletDynamics, BulletCollision, LinearMath libraries)
// at the top of the file, add #include "btBulletWorldImporter.h" // in your source code, add btBulletWorldImporter* fileLoader = new btBulletWorldImporter(m_dynamicsWorld); // optionally enable the verbose mode to provide debugging information during file loading (a lot of data is generated, so this option is very slow) // fileLoader->setVerboseMode(true); // load the contents from the file fileLoader->loadFile("testFile.bullet");
Instead of a file, you can also pass in a memory buffer and length, and use
char* memoryBuffer = ...; // some memory buffer int len = ...; // the length of the memory buffer // load the contents from the memory buffer fileLoader->loadFileFromMemory(buffer,len);
Supported Bullet structures in serialization
Initially we support the following Bullet data structures. Check out Bullet/Extras/Serializer/makesdna/makesdna.cpp file for the most up-to-data support.
- btCollisionObject, btRigidBody and btSoftBody
- btBoxShape, btSphereShape, btCylinderShape, btCapsuleShape, btConvexHullShape, btBvhTriangleMeshShape, btCompoundShape, btGImpactMeshShape, btStaticPlaneShape
- btPoint2PointConstraint, btHingeConstraint, btSliderConstraint, btGeneric6DofConstraint, btConeTwistConstraint
- For btSoftBody the nodes, links and faces are exported, as well as clusters and anchors between soft body and rigid body
Although above structures are stored in the .bullet file, the btWorldImporter only uses a small subset when re-creating the Bullet objects. Look into the btWorldImporter implementation for more details.
Setting and Getting the name for bodies, shapes and constraints
The Dynamica Maya plugin and Blender create unique names for all objects. This is useful to recognize objects, shapes and constraints, for example to bind them with graphics objects.
- When saving the file, the btDefaultSerializer is created, and all names are registered using the registerNameForPointer method. Note that registerNameForPointer without warning assumes ownership of the char* name you give it. It takes care of deallocation later on. Do not delete the name yourself.
- When loading the .bullet file, the m_name field is part of the structures: btCollisionObjectData, btRigidBodyData, btCollisionShapeData and btTypedConstraintData.
- Also the 'btBulletWorldImporter::createRigidBody' methods passes this nams as one of the arguments, when available.
Extending the file format
It is possible to add your own data structures to the .bullet format. All data structures are stored in the .bullet fileformat as 'DNA', so you have to re-generated this DNA using the utilities.
- Use cmake-gui or cmake and enable the 'INTERNAL_UPDATE_SERIALIZATION_STRUCTURES' option. This will add the makesdna, BulletDna and HeaderGenerator projects.
- The 'makesdna' utility will parse the C data structures that are used for serialization. Each headerfile needs to be added in the makesdna.cpp file: include the headerfile (right under the commend "// include files for automatic dependencies") and add it to the "char *includefiles" array.
- The 'BulletDNA' project runs the makesdna executable and when succesful it creates new binary DNA in the Bullet/src/LinearMath/btSerializer.cpp file.
There are several requirements and restrictions for the data structures for the .bullet file format. In particular, the data structures have certain alignment requirements, for easier cross-platform support (compiler alignment differences, 32/64 bit, little/big endian in particular).
- The makesdna C headerfile parser is very basic and only supports a very small subset of C99. In particular you need to take care of alignment of members and pointers and add extra 'padding' bytes.
For further information, see the serialization DNA restrictions, size and alignment rules.
- The optional HeaderGenerator can create header files by extracting the DNA structures embedded in each .bullet file. This means you don't need the Bullet SDK in order to load the .bullet file, you can just use the BulletFileLoader and those autogenerated header files. For an example, see Bullet/Extras/Serializer/HeaderFiles/autogenerated
Don't hesitate to ask help in the Bullet physics forums.
Known Limitations or Bugs
- All data that is saved is also loaded into memory in btBulletFile, but not all data is used to re-create the actual physics data in btBulletWorldImporter.
- Current velocity, collision flags, collision filter groups, and collision filter masks are not restored from the data, you can manually set the data from btBulletFile data, by modifying or deriving a class from btBulletWorldImporter.
- Child shapes of compound shapes are created twice while loading via btBulletWorldImporter. One version of the shape is what stays associated to any registered names given while saving. The other version of the shapes are unfortunately what are finally given to the compound shape, making the registered names useless. As a workaround, you can currently exploit how the order of the child shapes are at least maintained.
- Motion states are not saved, you must recreate them yourself after loading (if you use them).
- User pointers are not saved, and so cannot be used as a way to reestablish links between Bullet objects and your own structures when loading. You can use registered names for the objects as an alternative (though note the bug concerning compound shapes, mentioned above).
- See BulletSharp binary serialization for a C# version of some of the code on this page.