Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bullet Physics Simplest Collision Example

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 locations every frame and use it to tell me when I have collisions. To get the simplest example going, I'm trying to find collisions between objects with btBoxShape as their shape. Everything runs fine without crashes or apparent memory leaks, but I get no collisions so I must be making some mistakes somewhere. I'll try to keep this as brief as I can without leaving anything important out.

Here's my world setup function:

collisionConfig      = new btDefaultCollisionConfiguration();
dispatcher           = new btCollisionDispatcher(collisionConfig);
overlappingPairCache = new btDbvtBroadphase();
solver               = new btSequentialImpulseConstraintSolver;
dynamicsWorld        = new btDiscreteDynamicsWorld(dispatcher, 
overlappingPairCache, solver, collisionConfig);         

dynamicsWorld->setGravity(btVector3(0.0f, -9.8f, 0.0f));

Right now I have player and enemy objects of the type btCollisionObject*. I'm setting them up like this:

mPlayerBox = new btBoxShape(btVector3(1,3,1));
mPlayerObject = new btCollisionObject();
mPlayerObject->setCollisionShape(mPlayerBox);
btTransform playerWorld;
playerWorld.setIdentity();
//playerPos is a D3DXVECTOR3 that holds the camera position.
playerWorld.setOrigin(btVector3(playerPos.x, playerPos.y, playerPos.z));
mPlayerObject->setWorldTransform(playerWorld);
mPlayerObject->forceActivationState(DISABLE_DEACTIVATION);//maybe not needed
dynamicsWorld->addCollisionObject(mPlayerObject);

I do essentially the same thing with my enemy objects.

Then every frame I update all my objects with something like this:

btTransform updatedWorld;
updatedWorld.setIdentity();
updatedWorld.setOrigin(btVector3(position.x, position.y, position.z));
mPlayerObject->setWorldTransform(updatedWorld);

//do the same for my enemies, and then...

dynamicsWorld->performDiscreteCollisionDetection();
//Also tried doing this with stepSimulation(deltaTime, 7), but nothing changed.
//stepSimulation seems to only be for letting Bullet set world Transforms?

//check collisions with player
dynamicsWorld->contactTest(mPlayerObject, resultCallback);
int numManifolds = dynamicsWorld->getDispatcher()->getNumManifolds();
if(numManifolds > 0)
{
   //there's a collision, execute blah blah blah
}

And finally, here's the structure that defines my result callback:

struct rCallBack : public btCollisionWorld::ContactResultCallback
{
 btScalar rCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObject*
 colObj0, int partId0, int index0, const btCollisionObject* colObj1, int partId1,
 int index1)
 {
   btVector3 ptA = cp.getPositionWorldOnA();
   btVector3 ptB = cp.getPositionWorldOnB();
   return 0;
 }
}

I've looked at a lot of the demos, but they seem to mostly be leaving the movement up to Bullet, and since I'm moving characters at a set speed without any special physics when they collide, I had trouble adapting the examples into my application. The result callback actually came from this post on the forums: http://bulletphysics.org/Bullet/phpBB3/viewtopic.php?t=6816 It's about using triangle meshes, but it seemed closest to what I was trying to implement.

Anyway, if you read this far, thank you!! Any advice or links you could spare would be very much appreciated.

like image 661
Aztal Avatar asked Jun 24 '12 07:06

Aztal


3 Answers

I am writing an IOS app with flighter shooting each other on 3D scene. I use bullet physics for collision detection I set the flighter as kinematic object, my logic move the flighter and then update the btMotionState worldTransform of the kinematic object. I also don't get any collision detections until i change the following two statements (set the masking and group to the same for both player and enemy)

dynamicsWorld->addRigidBody(mPlayerObject,1,1);
dynamicsWorld->addRigidBody(mEnemyObject,1,1);
...
dynamicsWorld->setInternalTickCallback(myTickCallback);

then i can see the

void myTickCallback(btDynamicsWorld *world, btScalar timeStep) {
    int numManifolds = world->getDispatcher()->getNumManifolds();
    printf("numManifolds = %d\n",numManifolds);
}

numManifolds value become 1 when object collides.

like image 77
John Chui Avatar answered Oct 23 '22 08:10

John Chui


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 <cstdio>
#include <cstdlib>
#include <vector>

#include <btBulletDynamicsCommon.h>

#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<btVector3> 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<btCollisionShape*> 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.


You can check the contact information as explained here:

Contact Information

The best way to determine if collisions happened between existing objects in the world, is to iterate over all contact manifolds. This should be done during a simulation tick (substep) callback, because contacts might be added and removed during several substeps of a single stepSimulation call. A contact manifold is a cache that contains all contact points between pairs of collision objects. A good way is to iterate over all pairs of objects in the entire collision/dynamics world:

//Assume world->stepSimulation or world->performDiscreteCollisionDetection has been called

    int numManifolds = world->getDispatcher()->getNumManifolds();
    for (int i=0;i<numManifolds;i++)
    {
        btPersistentManifold* contactManifold =  world->getDispatcher()->getManifoldByIndexInternal(i);
        btCollisionObject* obA = static_cast<btCollisionObject*>(contactManifold->getBody0());
        btCollisionObject* obB = static_cast<btCollisionObject*>(contactManifold->getBody1());

        int numContacts = contactManifold->getNumContacts();
        for (int j=0;j<numContacts;j++)
        {
            btManifoldPoint& pt = contactManifold->getContactPoint(j);
            if (pt.getDistance()<0.f)
            {
                const btVector3& ptA = pt.getPositionWorldOnA();
                const btVector3& ptB = pt.getPositionWorldOnB();
                const btVector3& normalOnB = pt.m_normalWorldOnB;
            }
        }
    }

You may be interested in btGhostObject that keeps track of its own overlapping pairs.

like image 1
franzbischoff Avatar answered Oct 23 '22 07:10

franzbischoff