a note from the translation
Wiki Style Guide
Bullet is a 3D Collision Detection and Rigid Body Dynamics Library. The Library is Open Source and free for commercial use, under the zlib license (more info).
The Bullet physics extension is a Java wrapper for the C++ engine. This page provides information about using that wrapper. For more documentation related to the Bullet engine, please visitbulletphysics.org. Any Bullet questions not related to libgdx specifically can best be asked on their forum.
Practical information and examples on how to use the bullet wrapper can also be found at theBullet tests.
To use bullet physics in your project, you’ll need to add gdx-bullet.jar to your main project. Alternatively you can add the gdx-bullet project to the projects of the build path of your main project.
For your desktop project you’ll need to add the gdx-bullet-natives.jar to the libraries.
For your android project you’ll need to copy the armeabi/libgdx-bullet.so and armeabi-v7a/libgdx-bullet.so files to the libs folder in your android project.
Bullet isn’t supported for GWT at the moment.
Before you can use Bullet, you’ll need to load the libraries. This can be done by adding the following line in your create method:
Bullet.init();
Be aware not to use bullet before it is initialized. For example, the following will result in an error because the btGhostPairCallback
is created before the library is loaded.
public class InvokeRuntimeExceptionTest {
final static btGhostPairCallback ghostPairCallback = new btGhostPairCallback();
}
The wrapper tends to follow the original bullet class names. Meaning that most classes are prefixed with “bt”. There are a few exceptions on this, which are mostly nested structs. These are custom implemented directly into the com.badlogic.gdx.physics.bullet
package. Unfortunately some nested structs and some base classes are not suitable for a one on one translation. See the custom classes section for more information on that. If you find a class that is missing you can post it on the forums or issue tracker (https://github.com/libgdx/libgdx/issues), so it can be added to the wrapper.
Callbacks require some special attention. By default the wrapper only supports a one way interaction (from Java to C++). Callback interfaces, where C++ needs to call Java code are custom implemented. If you find a callback interface that isn't implemented yet, you can post it on the forums so it can be added to the wrapper.
List of callback interfaces (might not be complete):
LocalShapeInfo
LocalRayResult
RayResultCallback
ClosestRayResultCallback
AllHitsRayResultCallback
LocalConvexResult
ConvexResultCallback
ClosestConvexResultCallback
ContactResultCallback
btMotionState
btIDebugDraw
InternalTickCallback
ContactListener
ContactCache
Properties are encapsulated by getter and setter methods. The naming of the getter and setter methods omits the m_
prefix. For example, the m_collisionObject
member of the native classbtCollisionObjectWrapper
is implemented as getCollisionObject()
andsetCollisionObject(...)
.
Every time you create a bullet class in Java it also creates the corresponding class in C++. While the Java object is maintained by the garbage collector, the C++ object isn’t. To avoid having orphaned C++ objects resulting in memory leaks, the C++ object is by default automatically destroyed when the Java object is destroyed by the garbage collector.
While this might be useful in some cases, it’s merely a fail-safe and you shouldn't rely on it. Since you can’t control the garbage collector, you can’t control if, when and in which order the objects are actually being destroyed. Therefore the wrapper logs an error when an object is automatically destroyed by the garbage collector. You can disable this error logging using the second argument of the Bullet.init()
method, but you should preferably use the method in the following paragraph.
In order to ensure correct garbage collection you should keep a reference to every object you create until it’s not needed anymore and then destroy it yourself. You can destroy the C++ object by calling the .dispose()
method on the Java object, after which you should remove all references to the Java object since it’s unusable after that.
The above is only true for the objects you are responsible of, which are all Bullet classes you create with the new
keyword as well as classes you create using helper methods. You don’t have to dispose objects that are returned by regular methods or provided to you in callback methods.
As stated above, you should keep a reference to every Bullet class and call the dispose method when it’s no longer needed. When your application becomes more complex and objects are shared amongst multiple other objects, it can become difficult to keep track of references. Therefore the bullet wrapper support reference counting.
Reference counting is disabled by default. To enable it, call Bullet.init();
with the first argument set to true:
Bullet.init(true);
When using reference counting, you must call the obtain()
method on each object you need to reference. When you no longer need to reference an object, you must call the release()
method. The release method will dispose the object if it’s doesn't have any more references to it.
Some wrapper classes help you in managing references. For example the btCompoundShape
class obtains a reference to all its child shapes and releases them when it is disposed.
You can extend the bullet classes, but it’s recommended not to do so except for callback classes (in which case you should only override the intended methods). The information you add to a class is not available in C++. Furthermore the result of any method of the bullet wrapper that returns a class you’ve overridden will not implement that class. For example:
btCollisionShape shape = collisionObjectA.getCollisionShape();
This will create a new Java btCollisionShape class which doesn’t implement any extended class.
There is one exception to this for btCollisionObject, where the wrapper tries to reuse the same Java class. Furthermore the Java implementation of the btCollisionObject class adds a userData
member which can be used to attach additional data to the object. To accomplish this the wrapper maintains an array with references to all btCollisionObject instances. You can access that array using the static field btCollisionObject.instances
. Check the btCollisionObject section for detailed information on this.
Some classes provide a static upcast
method which can be used to cast the object to an higher class. For example:
btRigidBody bodyA = btRigidBody.upcast(collisionObjectA);
You can compare wrapper classes using the equals()
method, which checks if the classes both wrap the same native class. To get the pointer to the underlying C++ class you can use thegetCPointer
method of the specific object. You can also compare these pointers to check whether the Java classes wrap the same C++ class.
Bullet uses some classes also available in the libgdx core. While these bullet classes are available for you to use, the wrapper tries to use the libgdx class where possible. Currently these are implemented for:
Bullet | Libgdx |
---|---|
btVector3 | Vector3 |
btQuaternion | Quaternion |
btMatrix3x3 | Matrix3 |
btTransform | Matrix4 |
btScalar | float |
Note that the conversion from Matrix4 to btTransform might lose some information, because btTransform only contains an origin and rotation. In addition, note that float is a primitive type unlike btScalar.
To avoid creating objects for these common classes, the wrapper reuses the same instances. Therefore, be aware of the following two cases:
// Wrong method:
Matrix4 transformA = collisionObjectA.getWorldTransform();
// transformA now holds the worldTransform of collisionObjectA
Matrix4 transformB = collisionObjectB.getWorldTransform();
// transformA and transformB are the same object and now holds the worldTransform of collsionObjectB
// Correct method:
transformA.set(collisionObjectA.getWorldTransform());
transformB.set(collisionObjectB.getWorldTransform());
// Wrong method:
@Override
public void setWorldTransform (final Matrix4 worldTrans) {
transform = worldTrans;
}
// Correct method:
@Override
public void setWorldTransform (final Matrix4 worldTrans) {
transform.set(worldTrans);
}
Where possible the wrapper uses direct ByteBuffer objects to pass arrays from Java to C++. This avoids copying the array on the call and allows you to share the same byte buffer for both OpenGL ES and Bullet. If needed you can create a new ByteByffer usingBufferUtils.newUnsafeByteBuffer
, which you should manually delete usingBufferUtils.disposeUnsafeByteBuffer
.
In cases where ByteBuffer can't be used or is unwanted, a normal array is used. By default this means that the array is copied using iteration from Java to C++ at start of the method and copied back at the end of the method. To avoid this overhead the wrapper tries to use the Java array directly from within C++ where possible using critical arrays. During such method Java garbage collecting is blocked. An example of such method isbtBroadphasePairArray.getCollisionObjects
.
Contact callbacks allow you to be notified when a contact/collision on two objects occur (more info and a performance related warning).
By default there are three callbacks: onContactAdded
, onContactProcessed
andonContactDestroyed
) . The wrapper adds two additional callbacks: onContactStarted
andonContactEnded
. The callbacks are global (independent of e.g. the collision world), there can be only one implementation per callback active at any given time.
You can extend the ContactListener class to implement one or more callbacks:
public class MyContactListener extends ContactListener {
@Override
public void onContactStarted (btCollisionObject colObj0, btCollisionObject colObj1) {
// implementation
}
@Override
public void onContactProcessed (int userValue0, int userValue1) {
// implementation
}
}
Note that there can only be one listener enabled for each callback at a time. You can use theenable();
method to set the listener active, which disables any other listeners on that particular callback. Use the disable();
method to stop being notified for that particular callback. Instantiating a listener automatically enables that callback and destroying (dispose();
method) automatically disables it.
The ContactListener class provides one or more method signatures per callback you can override. For example the onContactAdded
callback can be overridden using the following signatures:
boolean onContactAdded(btManifoldPoint cp, btCollisionObjectWrapper colObj0Wrap, int partId0, int index0, btCollisionObjectWrapper colObj1Wrap, int partId1, int index1);
boolean onContactAdded(btManifoldPoint cp, btCollisionObject colObj0, int partId0, int index0, btCollisionObject colObj1, int partId1, int index1);
boolean onContactAdded(btManifoldPoint cp, int userValue0, int partId0, int index0, int userValue1, int partId1, int index1);
boolean onContactAdded(btCollisionObjectWrapper colObj0Wrap, int partId0, int index0, btCollisionObjectWrapper colObj1Wrap, int partId1, int index1);
boolean onContactAdded(btCollisionObject colObj0, int partId0, int index0, btCollisionObject colObj1, int partId1, int index1);
boolean onContactAdded(int userValue0, int partId0, int index0, int userValue1, int partId1, int index1);
As you can see it has three methods which provide the btManifoldPoint
and three which don’t. To provide the actual collision objects, you can choose between either thebtCollisionObjectWrapper
, btCollisionObject
or the userValue
.
Make sure to override the method that only provides the arguments you are actually going to use. For example, if you are not going to use the btManifoldPoint
then it wouldn’t make sense to create an object for that argument each time the callback is called. Likewise usingbtCollisionObject
is more performant than using btCollisionObjectWrapper
, because thebtCollisionObject
is reused. The userValue
is even more performant, because the object isn’t mapped at all (see [#btCollisionObject btCollisionObject] on how to use the useValue).
The onContactAdded
callback will only be triggered if at least one of the two colliding bodies has the CF_CUSTOM_MATERIAL_CALLBACK
set:
body.setCollisionFlags(e.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK);
To identify a contact along the added, processed and destroyed callbacks, you can use thesetUserValue(int);
and getUserValue();
of the btManifoldPoint
instance that the callback provides. This is also the value supplied to the onContactDestroyed(int)
method of theContactListener
class. Note that the onContactDestroyed
callback is only triggered if the user value is non-zero.
Contact callbacks are invoked a lot. JNI bridging between C++ and Java on every call adds quite an overhead, which decreases performance. Therefor the bullet wrapper allows you to specify for which objects you would like to receive contacts. This is done by contact filtering.
Similar to collision filtering, for every btCollisionObject
, you can specify a flag using thesetContactCallbackFlag(int);
method and a filter using the setContactCallbackFilter(int);
method. The filter of object A matches object B if A.filter & B.flag == B.flag
. Only if one or both of the filters match the contact is passed to the listener.
static int PLAYER_FLAG = 2; // second bit
static int COIN_FLAG = 4; // third bit
btCollisionObject player;
btCollisionObject coin;
...
player.setContactCallbackFlag(PLAYER_FLAG);
coin.setContactCallbackFilter(PLAYER_FLAG);
// The listener will only be called if a coin collides with player
By default the contactCallbackFlag
of a btCollisionObject
is set to 1 and thecontactCallbackFilter
is set to 0. Note that setting the flag to zero will cause the callback always to be invoked for that object (because x & 0 == 0
).
Whether or not contact filtering is used, is decided by which method signature you override. For every callback that supports contact filtering the ContactListener
class provides method signatures with the boolean match0
and boolean match1
arguments. If you override such method, contact filtering is used on that method. If you override a method that doesn’t have theboolean match
arguments, then contact filtering is not used for that method.
You can use the boolean match0
and boolean match1
values to check which of both filters matches.
public class MyContactListener extends ContactListener {
@Override
public void onContactEnded (int userValue0, boolean match0, int userValue1, boolean match1) {
if (match0) {
// collision object 0 (userValue0) matches
}
if (match1) {
// collision object 1 (userValue1) matches
}
}
}
Even when using contact filtering, the callbacks can be invoked quite often on collision. To avoid this you can set the filter to zero after processing. For example, in the case of the player and the coin, it's best to let the coin collide with the player and than set it's filter to zero on first contact, instead of letting the player collide with the coin.
In some cases it's not possible to wrap a C++ bullet class/method in a Java class/method, in which case a custom class or method is used to bridge the two. The following list describes those. Note that the list might not be complete.
The btCollisionObject is modified to reuse Java objects instead of creating a new Java object every time. This is done using the static btCollisionObject.instances
map. To remove an object from the map and delete the native object use the dispose
method.
Besides reusing instances, the Bullet wrappers allows you to provide a unique number to identify the instance. For example the index/ID of the entity in your entity system. Some frequently called methods allow you to use that value instead of the instance itself. This completely eliminates the overhead of mapping C++ and Java instances. You can set this value using thesetUserValue(int);
method and retrieve the value using the getUserValue();
method.
public class MyGameObject {
public btCollisionObject body;
}
...
Array<MyGameObject> gameObjects;
...
gameObjects.add(myGameObject);
myGameObject.setUserValue(gameObjects.size-1);
You can use the userData
field to add some additional data. For example:
btCollisionObject obj = new btCollisionObject();
obj.userData = myGameObject;
...
if (obj.userData instanceof MyGameObject)
myGameObject = (MyGameObject)obj.userData;
btCollisionObject
adds the methods takeOwnership
and releaseOwnership
which can be used to remove or make the wrapper responsable for destroying the native object when the Java object is destroyed by the garbage collector.
The btCollisionObject
also adds the following methods:
getAnisotropicFriction(Vector3)
getWorldTransform(Matrix4)
getInterpolationWorldTransform(Matrix4)
getInterpolationLinearVelocity(Vector3)
getInterpolationAngularVelocity(Vector3)
getContactCallbackFlag()
and setContactCallbackFlag(int)
getContactCallbackFilter()
and setContactCallbackFilter(int)
The ClosestNotMeConvexResultCallback
class is a custom ClosestConvexResultCallback
implementation which you can use to perform a convexSweepTest
on all objects except the specified one.
The ClosestNotMeRayResultCallback
class is a custom ClosestRayResultCallback
implementation which you can use to perform a rayTest
on all objects except the specified one.
The InternalTickCallback
is implemented to bridge the callback required bybtDynamicsWorld#setInternalTickCallback
to a java class. You can extend the class and overrideonInternalTick
method. You can use the attach
and detach
methods to start and stop getting tick callbacks.
In some cases it's easier to use btDefaultMotionState
instead of extending btMotionState
. The following custom methods are available for btDefaultMotionState
.
getGraphicsWorldTrans(Matrix4)
getCenterOfMassOffset(Matrix4)
getStartWorldTrans(Matrix4)
Note that extending btMotionState
with your own implementation is the preferred method.The btCompoundShape
class allows to keep a reference to child shapes, so you don't have to do that. To use it, use the addChildShape
with the third managed
argument set to true. Note that this will delete the managed child shape if the compound shape is deleted. Therefor the managed shapes should be exclusive for the compound shape.
The btIndexedMesh
class adds the constructor:
btIndexedMesh(Mesh)
And the methods:setTriangleIndexBase(ShortBuffer)
setVertexBase(FloatBuffer)
set(Mesh)
For easy constructing or setting a btIndexedMesh
based on a Mesh
instance or a vertex and index buffer. The buffers itself are not managed by the wrapper and should out-live the object.The btTriangleIndexVertexArray
class adds the ability to maintain a reference to the JavabtIndexedMesh
classes it holds. To use it call addIndexedMesh
with the last argument managed
set to true. When the btTriangleIndexVertexArray
is destroyed it will also destroy it's managedbtIndexedMesh
children.
Also, the btTriangleIndexVertexArray
class adds the addMesh
and addModel
methods and likewise constructors, for easy constructing and setting the class.
The btBvhTriangleMeshShape
class adds the ability to maintain a reference to the JavabtStridingMeshInterface
class. To use it construct the class with the argument managed
set to true. When the btBvhTriangleMeshShape
is destroyed it will also destroy the managedbtStridingMeshInterface
.
Also, the btBvhTriangleMeshShape
class add constructors for easy constructing one or moreMesh
or Model
instances.
The btConvexHullShape
class adds a convenience constructorbtConvexHullShape(btShapeHull)
.
The btBroadphasePairArray
class adds methods to get all collision objects within it at once:
btBroadphasePairArray.getCollisionObjects(Array<btCollisionObject> out, btCollisionObject other, int[] tempArray)
btBroadphasePairArray.getCollisionObjectsValue(int[] out, btCollisionObject other)