performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post Reply
hyyou
Posts: 96
Joined: Wed Mar 16, 2016 10:11 am

performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by hyyou »

I have a loop like :-

Code: Select all

btDiscreteDynamicsWorld_instance->stepSimulation(......);              //has 1000 bodies (20% CPU)
add / change position of 10-20 btRigidbody;
btDiscreteDynamicsWorld_instance->performDiscreteCollisionDetection(); //<-- slow  (10% CPU)
iterating btPersistentManifold for collision callback; 
The line performDiscreteCollisionDetection() is quite slow.
I think the reason is that it rechecks manifold of every body in the world.
Is there a function to do it but only update manifold of a few certain bodies?

I wish for something like :-

Code: Select all

btDiscreteDynamicsWorld_instance->updateManifoldSingleBody(aBtRigidBodyPointer);
Edit: add the "ghost" word into the topic.
Last edited by hyyou on Sat Feb 10, 2018 6:52 am, edited 1 time in total.
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: How to performDiscreteCollisionDetection (update manifold) for only a few bodies?

Post by drleviathan »

First of all, you should know: by default btCollisionWorld::updateAabbs() will update all Aabbs, even those of inactive objects. Sometimes this is an unexpected performance problem when running a simulation with many inactive objects. There are two ways to solve this problem:

(1) Use fewer objects. If you can coalesce your static content into larger objects such that there are fewer of them... then this helps.

(2) Disable updating Aabbs of inactive objects by using: world->setForceUpdateAllAabbs(false). This works fine as long as you don't arbitrarily move non-active objects. If you DO need to move an inactive object you either need to setForceUpdateAllAabbs(true) on the subsequent step (and then set it false again later), or else manually update that object's Aabb in the broadphase before the performDiscreteCollisionDetection() part of the step.


Finally, if you still really want to update just the contact manifolds for a particular object I could see a way to do it. It helps to examine the implementation of btCollisionWorld::performDiscreteCollisionDetection():

Code: Select all

void    btCollisionWorld::performDiscreteCollisionDetection()
{
    BT_PROFILE("performDiscreteCollisionDetection");

    btDispatcherInfo& dispatchInfo = getDispatchInfo();

    updateAabbs();

    computeOverlappingPairs();

    btDispatcher* dispatcher = getDispatcher();
    {
        BT_PROFILE("dispatchAllCollisionPairs");
        if (dispatcher)
            dispatcher->dispatchAllCollisionPairs(m_broadphasePairCache->getOverlappingPairCache(),dispatchInfo,m_dispatcher1);
    }
}
The majority of the code above is setting up the overlapping pairs. The manifolds are computed inside the final non-trivial line. Which means... if only you had an up-to-date overlappingPairCache for the object in question you could then perform the dispatchAllCollisionPairs() stuff on it.

It turns out the btPairCachingGhostObject can be used to maintain such a limited overlappingPairCache.
hyyou
Posts: 96
Joined: Wed Mar 16, 2016 10:11 am

Re: How to performDiscreteCollisionDetection (update manifold) for only a few bodies?

Post by hyyou »

Thank drleviathan for the great detailed answer.
btPairCachingGhostObject is probably all I want (the 10-20 objects are all radar/sensor). I have never used ghost thingy before.

off-topic : It is lucky that this forum has you. :D
hyyou
Posts: 96
Joined: Wed Mar 16, 2016 10:11 am

Re: How to performDiscreteCollisionDetection (update manifold) for only a few bodies?

Post by hyyou »

I have just tried the ghost body.

My test case has only 2 bodies in the world.
Body1 is normal body, and Body2 is ghost.
When t=0, both objects are at (0,0,0).
When t=1, Body2 move to (3,0,0).
When t=2, Body2 move back to (0,0,0).
bullet13.jpg
bullet13.jpg (18.33 KiB) Viewed 8120 times
I want to detect manifold at t=0 and t=2. However, every time step, I can't detect anything.
Where am I wrong?

Here is a full test function(91 lines).
I notice that collisionPair->m_algorithm always == nullptr

Code: Select all

#pragma once
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <LinearMath/btDefaultMotionState.h>
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
#include <BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h>
#include <BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.h>
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
#include <BulletCollision/CollisionDispatch/btActivatingCollisionAlgorithm.h>
#include <iostream>

int main(){
		btDbvtBroadphase single_btDbvtBroadphase;
		btDefaultCollisionConfiguration single_btDefaultCollisionConfiguration;
		btCollisionDispatcher single_btCollisionDispatcher{&single_btDefaultCollisionConfiguration};
		btSequentialImpulseConstraintSolver single_btSequentialImpulseConstraintSolver;

		btDiscreteDynamicsWorld single_btDynamicsWorld{
			&single_btCollisionDispatcher,
			&single_btDbvtBroadphase,
			&single_btSequentialImpulseConstraintSolver,
			&single_btDefaultCollisionConfiguration
		};

		btGhostPairCallback btGhostPairCallback_;
		single_btDynamicsWorld.getPairCache()->setInternalGhostPairCallback(&btGhostPairCallback_);
		//v create body1 at (0,0,0)
		btBoxShape shape1=  btBoxShape(btVector3(1,1,1));
		btDefaultMotionState motion1=  btDefaultMotionState(btTransform::getIdentity());
		btRigidBody body1 =  btRigidBody(
			btRigidBody::btRigidBodyConstructionInfo(
				1, //mass
				&motion1,
				&shape1,
				btVector3(1,1,1) //inertia
			)
		);
		//v create body2 (ghost) at (0,0,0)
		btPairCachingGhostObject body2{};
		body2.setWorldTransform(btTransform::getIdentity());
		btBoxShape shape2 = btBoxShape(btVector3(1,1,1));
		body2.setCollisionShape (&shape2);
		body2.setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);
		//v add both object to world
		single_btDynamicsWorld.addCollisionObject(&body1,0xFFFF,0xFFFF);
		single_btDynamicsWorld.addCollisionObject(&body2,btBroadphaseProxy::SensorTrigger,btBroadphaseProxy::AllFilter & ~btBroadphaseProxy::SensorTrigger);
//		single_btDynamicsWorld.stepSimulation(0.0000001f,1);
		for(int t=0;t<=2;t++){
			//will move body2 : t=0->(0,0,0)   ; t=1->(3,0,0) ; t=2->(0,0,0)
			// Thus, I should detect collision at t=0 and t=2 only

			btTransform tranNew=btTransform::getIdentity();
			if(t==1)tranNew.setOrigin(btVector3(3,0,0));
			body2.setWorldTransform(tranNew);
			btManifoldArray	manifoldArray;
			btBroadphasePairArray& pairArray = body2.getOverlappingPairCache()->getOverlappingPairArray();
			int numPairs = pairArray.size();
			//no different if I call this : single_btDynamicsWorld.getDispatcher()->dispatchAllCollisionPairs(body2.getOverlappingPairCache(), single_btDynamicsWorld.getDispatchInfo(), single_btDynamicsWorld.getDispatcher());
			bool myFoundCollision=false;
			for (int i=0;i<numPairs;i++){
				manifoldArray.clear();
				const btBroadphasePair& pair = pairArray[i];
				btBroadphasePair* collisionPair =single_btDynamicsWorld.getPairCache()->findPair(pair.m_pProxy0,pair.m_pProxy1);
				if (!collisionPair)
					continue;
				if (collisionPair->m_algorithm)
					collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);

				for (int j=0;j<manifoldArray.size();j++)				{
					btPersistentManifold* manifold = manifoldArray[j];
					for (int p=0;p<manifold->getNumContacts();p++)
					{
						const btManifoldPoint&pt = manifold->getContactPoint(p);
						myFoundCollision=true;
					}
				}
				
			}
			std::cout<<myFoundCollision<<std::endl; //should print : true false true
			
		}
		int afsdds=0;
}

I have read :-
- https://raw.githubusercontent.com/kripk ... erDemo.cpp
- http://www.bulletphysics.org/mediawiki- ... hostObject
- https://pybullet.org/Bullet/phpBB3/viewtopic.php?t=3026
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by drleviathan »

Maybe try this version. I think you need to call world.stepSimulation(timeStep, 1) between each test, and you need to supply enough timeStep for it to take at least 1 substep (if you don't supply a large enough timeStep it just accumulates time internally but does not actually take a substep).

BTW, for my own sanity I took the liberty of supplying more readable variable names.

Code: Select all

#pragma once
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
#include <BulletCollision/CollisionShapes/btBoxShape.h>
#include <LinearMath/btDefaultMotionState.h>
#include <BulletCollision/BroadphaseCollision/btDbvtBroadphase.h>
#include <BulletCollision/CollisionDispatch/btDefaultCollisionConfiguration.h>
#include <BulletDynamics/ConstraintSolver/btSequentialImpulseConstraintSolver.h>
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
#include <BulletCollision/CollisionDispatch/btActivatingCollisionAlgorithm.h>
#include <iostream>


int main(){
    // initialize the world
    btDbvtBroadphase broadphase;
    btDefaultCollisionConfiguration config;
    btCollisionDispatcher dispatcher{&config};
    btSequentialImpulseConstraintSolver solver;
    btDiscreteDynamicsWorld world{
        &dispatcher,
        &broadphase,
        &solver,
        &config
    };

    // the world will have ghosts, therefore we must give it a ghostCallback
    btGhostPairCallback ghostCb;
    world.getPairCache()->setInternalGhostPairCallback(&ghostCb);

    // create one shape (we'll use it for both objects)
    btVector3 boxHalfExtents(0.5, 0.5, 0.5);
    btBoxShape boxShape =  btBoxShape(boxHalfExtents);

    // create two transforms
    btTransform transform0 = btTransform::getIdentity();
    btTransform transform1 = transform0;
    transform1.setOrigin(10.0 * boxHalfExtents);

    // create box at transform0
    btDefaultMotionState boxMotionState =  btDefaultMotionState(transform0);
    btRigidBody box =  btRigidBody(
        btRigidBody::btRigidBodyConstructionInfo(
            1, //mass
            &boxMotionState ,
            &boxShape ,
            btVector3(1,1,1) //inertia
        )
    );

    // create ghost at transform1 (should NOT be in overlap)
    btGhostObject ghost{};
    ghost.setWorldTransform(transform1);
    ghost.setCollisionShape (&boxShape);
    ghost.setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);

    // add both objects to world
    world.addCollisionObject(&box); // box uses default collision group and mask
    world.addCollisionObject(&ghost,btBroadphaseProxy::SensorTrigger,btBroadphaseProxy::AllFilter & ~btBroadphaseProxy::SensorTrigger);

    // step the world once
    btScalar defaultTimestep(1.0 / 60.0);
    world.stepSimulation(defaultTimestep, 1);

    {   // verify ghost does NOT overlap
        int numOverlaps = ghost.getNumOverlappingObjects();
        std::cout << "000"
            << "  numOverlaps = " << numOverlaps
            << "  expectedNumOverlaps = 0"
            << std::endl;
    }

    {   // count the manifolds
        int numManifolds = world.getDispatcher()->getNumManifolds();
        std::cout << "000"
            << "  numManifolds = " << numManifolds
            << std::endl;
        for (int i = 0; i < numManifolds; ++i) {
            btPersistentManifold* contactManifold = world.getDispatcher()->getManifoldByIndexInternal(i);
            int numContacts = contactManifold->getNumContacts();
            std::cout << "    manifold " << i << " has " << numContacts << " contacts" << std::endl;
        }
    }

    // move the box to where the ghost is
    box.setWorldTransform(transform1);

    // step the world again
    world.stepSimulation(defaultTimestep, 1);

    {   // check to see if the ghost overlaps
        int numOverlaps = ghost.getNumOverlappingObjects();
        std::cout << "001"
            << "  numOverlaps = " << numOverlaps
            << "  expectedNumOverlaps = 1"
            << std::endl;
    }

    {   // count the manifolds
        int numManifolds = world.getDispatcher()->getNumManifolds();
        std::cout << "001"
            << "  numManifolds = " << numManifolds
            << std::endl;
        for (int i = 0; i < numManifolds; ++i) {
            btPersistentManifold* contactManifold = world.getDispatcher()->getManifoldByIndexInternal(i);
            int numContacts = contactManifold->getNumContacts();
            std::cout << "    manifold " << i << " has " << numContacts << " contacts" << std::endl;
        }
    }

    // move ghost out of overlap
    ghost.setWorldTransform(transform0);

    // step the world again
    world.stepSimulation(defaultTimestep, 1);

    {   // verify ghost does NOT overlap again
        int numOverlaps = ghost.getNumOverlappingObjects();
        int expectedNumOverlaps = 0;
        std::cout << "002"
            << "  numOverlaps = " << numOverlaps
            << "  expectedNumOverlaps = 0"
            << std::endl;
        if (numOverlaps != expectedNumOverlaps) {
            std::cout << "Is this a bug?" << std::endl;
        }
    }

    {   // count the manifolds
        int numManifolds = world.getDispatcher()->getNumManifolds();
        std::cout << "002"
            << "  numManifolds = " << numManifolds
            << std::endl;
        for (int i = 0; i < numManifolds; ++i) {
            btPersistentManifold* contactManifold = world.getDispatcher()->getManifoldByIndexInternal(i);
            int numContacts = contactManifold->getNumContacts();
            std::cout << "    manifold " << i << " has " << numContacts << " contacts" << std::endl;
        }
    }

    // step the world again
    world.stepSimulation(defaultTimestep, 1);

    {   // does the ghost overlap?
        int numOverlaps = ghost.getNumOverlappingObjects();
        int expectedNumOverlaps = 0;
        std::cout << "003"
            << "  numOverlaps = " << numOverlaps
            << "  expectedNumOverlaps = " << expectedNumOverlaps
            << std::endl;
        if (numOverlaps != expectedNumOverlaps) {
            std::cout << "interesting, the ghost still considers the box as overlapping, but what about the contact manifold?" << std::endl;
        }
    }

    {   // count the manifolds
        int numManifolds = world.getDispatcher()->getNumManifolds();
        std::cout << "003"
            << "  numManifolds = " << numManifolds
            << std::endl;
        for (int i = 0; i < numManifolds; ++i) {
            btPersistentManifold* contactManifold = world.getDispatcher()->getManifoldByIndexInternal(i);
            int numContacts = contactManifold->getNumContacts();
            std::cout << "    manifold " << i << " has " << numContacts << " contacts" << std::endl;
        }
    }
    return 0;
}
hyyou
Posts: 96
Joined: Wed Mar 16, 2016 10:11 am

Re: performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by hyyou »

Wow! A complete code. Thank drleviathan, again XD.

For science, I took the liberty of running your code, and post the result :-
bullet14.jpg
bullet14.jpg (32.66 KiB) Viewed 8103 times
There are 2 aspects that worry me :-
- At 002, it shows "bug?".
- If stepSimulation() is removed, numOverlaps at 001 will = 0 (more wrong).

My objective of using ghost object = just to avoid the expensive operation :-

Code: Select all

stepSimulation() / performDiscreteCollisionDetection()
If I don't care performance, I can just use a simple feature :-

Code: Select all

btRigidBody->setCollisionFlags( ... btCollisionObject::CF_NO_CONTACT_RESPONSE);
Is it possible to just move the ghost and check it without updating the whole world?
Here is my dream :-

Code: Select all

stepSimulation() --> move the ghost ---(no stepSimulation here)--> check manifold of ghost --> stepSimulation() ... 
I believe I have to use btPairCachingGhostObject rather than btGhostObject, but I am not sure.
I tried to change it, but still get the same result.

I start to have a crazy idea that btGhostObject/btPairCachingGhostObject are totally useless classes.
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by drleviathan »

I too noticed that the btGhostObject was reporting an overlap when I didn't expect it. I didn't dig deep enough to figure out if it was a bug or expected behavior.

The difference between btGhostObject and btPairCachingGhostObject can be understood by looking at the API that btPairCachingGhostObject supplies above and beyond its base class: getOverlappingPairCache(), which gives you access to a btHashedOverlappingPairCache of a reduced set near the ghost. If you don't need access to such then you can get by with just a btGhostObject.

If you just want to harvest contact manifolds between two colliding objects then you don't need a btGhostObject, because in this scenario you want the contacts rather than overlaps. Just create two normal static RigidBodys, set the collision group/masks correctly so they actually collide in the broadphase, and get the resulting manifolds directly from the world's Dispatcher. You could consider the btDiscreteDynamicsWorld::stepSimulation() method as "the call which happens to calculate collision manifolds" for you. If this is unacceptable for performance reasons you could examine btDiscreteDynamicsWorld internals and implement an more efficient method that does the minimal "calculate collision manifolds" part and avoids any unrelated work. However, if you have an empty world but for two static objects then stepSimulation() is doing little else besides computing contact manifolds.

Note that the contact manifolds are "persistent". They have a lifetime somewhat disconnected from their contact points. The world may keep them around for a while even though their number of contacts is zero.
hyyou
Posts: 96
Joined: Wed Mar 16, 2016 10:11 am

Re: performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by hyyou »

Hmm.... I also tried to use it a few years ago but failed .... so I always assume that Ghost-and-friend are quite useless. :lol:

Unfortunately, I have a lot of objects in the world (100-1000).
My ultimate objective is to simulate a compound shape with radar :-
bullet15.jpg
bullet15.jpg (17.45 KiB) Viewed 8061 times
However, Bullet doesn't support btCompound with a non-collision-response shape.

Thus, I currently simulate it by using 2 RigidBodies :-
bullet16.jpg
bullet16.jpg (18.51 KiB) Viewed 8061 times
After the big compound body1 move from the stepSimulation(), I move body2 to match the correct relative position of body1.

Code: Select all

Game Loop{
  - stepSimulation()
     //body1 is moved (it may collide with other things that change its velocity)
  - move body2 to mimic compound shape
     //[#1#] both body1 and body2 now are in a correct (relative) position, but manifold is obsoleted
  - performDiscreteCollisionDetection()  //<-- want to get rid of this line
  - check manifold (body2 vs other bodies)
}
I am looking into a line that you suggested :-

Code: Select all

dispatcher->dispatchAllCollisionPairs(m_broadphasePairCache->getOverlappingPairCache(),dispatchInfo,m_dispatcher1);
I doubt .... because body2 is moved by me, m_broadphasePairCache->getOverlappingPairCache() will be obsoleted at [#1#].
I think it boiled down to : How to force AABB-update (broadphase) just for a single btRigidbody?
If I can do it, I can probably just use that magic line of code, and everything will work.
User avatar
drleviathan
Posts: 849
Joined: Tue Sep 30, 2014 6:03 pm
Location: San Francisco

Re: performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by drleviathan »

Here's an idea, use btCollisionWorld::contactTest() to perform "hypothetical asynchronous" collision detection after stepSimulation(). It would go something like this:

(1) Derive MyCustomContactSensorCallback from btCollisionWorld::ContactResultCallback. You would override addSingleResult() to handle each manifold point as it is added. I don't think you can keep pointers to the points for later, but you can copy the results into your own data structures.

(2) body1 is normal RigidBody in world with CompoundShape.

(3) body2 is normal RigidBody with RadarShape but is NOT in world.

(4) After world->stepSimulation() you give body2 the right transform and call world-contctTest(body1, myCustomCallback). The btManifoldPoints that are generated during the contactTest() don't actually affect the dynamics of the world, they are for you own consumption.

This is all theoretical. I've never used btCollisionWorld::contactTest() myself, so I can't vouch for the success of such a strategy.
Lovey1981
Posts: 1
Joined: Wed Mar 14, 2018 4:00 pm

Re: performDiscreteCollisionDetection (update manifold) for only a few bodies (ghost?)

Post by Lovey1981 »

hey there drleviathan, it does sounds like a good idea to be honest. never used it myself and i realized that this is all theortical and yet, it does sounds like a good thing. come with updates in case you would have some. thanks! https://delicerecipes.com/instant-pot-l ... eat-sauce/
Post Reply