I\'m trying to use Bullet Physics for collision detection only. I don\'t need it to move any objects for me or handle rendering with callbacks. I just want to update object loca
Minimal runnable example
A sphere falling and hitting the ground.
Collisions are detected and printed to stdout.
Gnuplot visualization:
The "collision" line goes to 1
whenever the sphere touches the ground.
And for smaller restitution coefficients (0.5
and 0.5
):
Here the ball stops jumping completely and touches the ground continuously.
main.cpp
#include
#include
#include
#include
#define PRINTF_FLOAT "%7.3f"
constexpr float gravity = -10.0f;
constexpr float initialY = 10.0f;
constexpr float timeStep = 1.0f / 60.0f;
// TODO some combinations of coefficients smaller than 1.0
// make the ball go up higher / not lose height. Why?
constexpr float groundRestitution = 0.9f;
constexpr float sphereRestitution = 0.9f;
constexpr int maxNPoints = 500;
std::vector collisions;
void myTickCallback(btDynamicsWorld *dynamicsWorld, btScalar timeStep) {
collisions.clear();
int numManifolds = dynamicsWorld->getDispatcher()->getNumManifolds();
for (int i = 0; i < numManifolds; i++) {
btPersistentManifold *contactManifold = dynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);
// TODO those are unused. What can be done with them?
// I think they are the same objects as those in the main loop
// dynamicsWorld->getCollisionObjectArray() and we could compare
// the pointers to see which object collided with which.
{
const btCollisionObject *objA = contactManifold->getBody0();
const btCollisionObject *objB = contactManifold->getBody1();
}
int numContacts = contactManifold->getNumContacts();
for (int j = 0; j < numContacts; j++) {
btManifoldPoint& pt = contactManifold->getContactPoint(j);
const btVector3& ptA = pt.getPositionWorldOnA();
const btVector3& ptB = pt.getPositionWorldOnB();
const btVector3& normalOnB = pt.m_normalWorldOnB;
collisions.push_back(ptA);
collisions.push_back(ptB);
collisions.push_back(normalOnB);
}
}
}
int main() {
int i, j;
btDefaultCollisionConfiguration *collisionConfiguration
= new btDefaultCollisionConfiguration();
btCollisionDispatcher *dispatcher = new btCollisionDispatcher(collisionConfiguration);
btBroadphaseInterface *overlappingPairCache = new btDbvtBroadphase();
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
btDiscreteDynamicsWorld *dynamicsWorld = new btDiscreteDynamicsWorld(
dispatcher, overlappingPairCache, solver, collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0, gravity, 0));
dynamicsWorld->setInternalTickCallback(myTickCallback);
btAlignedObjectArray collisionShapes;
// Ground.
{
btTransform groundTransform;
groundTransform.setIdentity();
groundTransform.setOrigin(btVector3(0, 0, 0));
btCollisionShape* groundShape;
#if 1
// x / z plane at y = -1.
groundShape = new btStaticPlaneShape(btVector3(0, 1, 0), -1);
#else
// A cube of width 10 at y = -6.
// Does not fall because we won't call:
// colShape->calculateLocalInertia
// TODO: remove this from this example into a collision shape example.
groundTransform.setOrigin(btVector3(0, -6, 0));
groundShape = new btBoxShape(
btVector3(btScalar(5.0), btScalar(5.0), btScalar(5.0)));
#endif
collisionShapes.push_back(groundShape);
btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(0, myMotionState, groundShape, btVector3(0, 0, 0));
btRigidBody* body = new btRigidBody(rbInfo);
body->setRestitution(groundRestitution);
dynamicsWorld->addRigidBody(body);
}
// Sphere.
{
btCollisionShape* colShape = new btSphereShape(btScalar(1.0));
collisionShapes.push_back(colShape);
btTransform startTransform;
startTransform.setIdentity();
startTransform.setOrigin(btVector3(0, initialY, 0));
btVector3 localInertia(0, 0, 0);
btScalar mass(1.0f);
colShape->calculateLocalInertia(mass, localInertia);
btDefaultMotionState *myMotionState = new btDefaultMotionState(startTransform);
btRigidBody *body = new btRigidBody(btRigidBody::btRigidBodyConstructionInfo(
mass, myMotionState, colShape, localInertia));
body->setRestitution(sphereRestitution);
dynamicsWorld->addRigidBody(body);
}
// Main loop.
std::printf("step body x y z collision a b normal\n");
for (i = 0; i < maxNPoints; ++i) {
dynamicsWorld->stepSimulation(timeStep);
for (j = dynamicsWorld->getNumCollisionObjects() - 1; j >= 0; --j) {
btCollisionObject *obj = dynamicsWorld->getCollisionObjectArray()[j];
btRigidBody *body = btRigidBody::upcast(obj);
btTransform trans;
if (body && body->getMotionState()) {
body->getMotionState()->getWorldTransform(trans);
} else {
trans = obj->getWorldTransform();
}
btVector3 origin = trans.getOrigin();
std::printf("%d %d " PRINTF_FLOAT " " PRINTF_FLOAT " " PRINTF_FLOAT " ",
i,
j,
float(origin.getX()),
float(origin.getY()),
float(origin.getZ()));
if (collisions.empty()) {
std::printf("0 ");
} else {
std::printf("1 ");
// Yes, this is getting reprinted for all bodies when collisions happen.
// It's just a quick and dirty way to visualize it, should be outside
// of this loop normally.
for (auto& v : collisions) {
std::printf(
PRINTF_FLOAT " " PRINTF_FLOAT " " PRINTF_FLOAT " ",
v.getX(), v.getY(), v.getZ());
}
}
puts("");
}
}
// Cleanup.
for (i = dynamicsWorld->getNumCollisionObjects() - 1; i >= 0; --i) {
btCollisionObject* obj = dynamicsWorld->getCollisionObjectArray()[i];
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState()) {
delete body->getMotionState();
}
dynamicsWorld->removeCollisionObject(obj);
delete obj;
}
for (i = 0; i < collisionShapes.size(); ++i) {
delete collisionShapes[i];
}
delete dynamicsWorld;
delete solver;
delete overlappingPairCache;
delete dispatcher;
delete collisionConfiguration;
collisionShapes.clear();
}
main.gnuplot
#!/usr/bin/env gnuplot
set terminal png size 1024,1024
set output "main.png"
set key autotitle columnheader
plot 'main.dat' using 1:($2 == 1 ? $4 : 1/0), \
'' using 1:($2 == 1 ? $6 : 1/0)
Compile and run:
sudo apt install libbullet-dev
g++ -ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic \
$(pkg-config --cflags bullet) -o main.out main.cpp $(pkg-config --libs bullet)
./main.out > main.dat
gnuplot main.gnuplot
Code based on: http://www.bulletphysics.org/mediawiki-1.5.8/index.php
Version of this focused on distinguishing which object touched which object: https://gamedev.stackexchange.com/a/120881/25171
GitHub upstream: https://github.com/cirosantilli/cpp-cheat/blob/503a3b6487ccb75334798839b5ed912270446d14/bullet/ground_ball.cpp
The next thing you will want is a better visualization mechanism than gnuplot for the 3D shapes. The example browser is one possibility to look into: How to apply Bullet physics to drawn Opengl 3d shapes It would also be amazing if they had glTF output: https://www.khronos.org/gltf/
Tested on Bullet 2.88, Ubuntu 20.04.