Bullet Physics Simplest Collision Example

前端 未结 3 2515
醉酒成梦
醉酒成梦 2021-02-20 11:44

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

3条回答
  •  北恋
    北恋 (楼主)
    2021-02-20 11:58

    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.

提交回复
热议问题