Shape collisions with btPairCachingGhostObject

Post Reply
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

I've had some working ghost objects for a while now, and recently I rotated one of them and noticed they started having an effect on objects outside of their bounds. It took me a while but I realized the Ghost shape was detecting collisions by AABB only. I did have the line:

Code: Select all

PhysWorld->getDispatcher()->dispatchAllCollisionPairs(Ghost->getOverlappingPairCache(), PhysWorld->getDispatchInfo(), PhysWorld->getDispatcher());
...in my code initially, but it degraded performance and I tried commenting it out, and it still worked, so I left it commented out. I didn't really think at the time that Real shape collision results on a box without rotation looks almost identical to AABB collision results. ><

So I removed the comments for that line of code and tested it...with absolutely no change. I'm getting a large drop in performance for absolutely nothing. I snooped through the source code that was called, making one assumption, and one annoying conclusion.

On line 387 of btOverlappingPairCache.cpp it uses the provided callback to "processOverlap". On lines 236 and 238 of btCollisionDispatcher.cpp I see it is creating a btCollisionPairCallback to be used for the "processing". At lines 224 and 226 of btCollisionDispatcher.cpp, inside the "processOverlap" function it calls the dispatchers near callback and then is hardcoded to always return false. Which returns into this if statement:

Code: Select all

if (callback->processOverlap(*pair))
{
    removeOverlappingPair(pair->m_pProxy0,pair->m_pProxy1,dispatcher);

    gOverlappingPairs--;
} else
{
    i++;
}
This seems to make it difficult for a btPairCachingGhostObject to clean unwanted overlaps/collisions from itself. Which of course means it keeps all of it's old AABB collisions in it's overlapping cache.

Is there any way to get it to clean it's cache of collisions that are AABB only(and not the actual shape) that doesn't involve modifying bullet source code?
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Re: Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

/bump

Looking through the API there doesn't seem to be any obvious answer to this issue. All the collision algorithms return void. I do see a manifold result being passed in by reference, but I'm not sure how to interpret that data, or what to do with it, or if it's even relevant to what I need to do. It's also occurred to me that maybe I can iterate over the contact manifolds, but I want to be able to have many triggers in a simulation at once...and iterating over the manifolds several times each frame seems less then optimal. Especially since dispatching all pairs should clean the PairCache in the first place.
User avatar
dphil
Posts: 237
Joined: Tue Jun 29, 2010 10:27 pm
Contact:

Re: Shape collisions with btPairCachingGhostObject

Post by dphil »

Hi Mako,

To get only the shape-overlapping objects with a ghost object, you can iterate over the manifolds for each pair in the ghost's cache (though you usually have to get the pair from the world's cache, which holds desired narrowphase details). For each manifold, get the manifold points. If any manifold point has contact distance (point.getDistance()) less than 0, you have shape overlap. Greater than 0 means aabb-only overlap. At least, this is what I understood from looking into this very same problem last year. As for the specifics of what, exactly, manifolds and manifold points are, I am not certain. But anyway, below is code I use which works as above, and gives the desired result (at least based on the limited scenarios in which I've used it). It is adapted from example code I found on this issue last year.

Code: Select all

btPairCachingGhostObject * ghost = ...;

btManifoldArray   manifoldArray;
btBroadphasePairArray& pairArray = ghost->getOverlappingPairCache()->getOverlappingPairArray();
int numPairs = pairArray.size();

for (int i=0;i<numPairs;i++)
{
    manifoldArray.clear();
    
    const btBroadphasePair& pair = pairArray[i];
    
    //unless we manually perform collision detection on this pair, the contacts are in the dynamics world paircache:
    btBroadphasePair* collisionPair = dynamicsWorld->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);

            if (pt.getDistance()<0.f)
            {
                // Object is inside ghost's shape at this manifold point
                // process shape overlap here
            }
            else
            {
                // Object is in ghost's aabb but not inside its shape at this manifold point
                // process aabb overlap here
            }
        }
    }
}
I've found that most(/all?) objects give one manifold, and basic objects like spheres and cubes typically have one manifold point, while meshes can give more. So you probably want to add loop breaks as soon as you find one manifold point with a distance less than 0, if you just care about the presence or absence of shape overlap. Sometimes you might want to do specific things for each point of contact, in which case btManifoldPoint provides a bunch of of low-level contact data (locations on the objects, contact normal, etc).

Hope that helps.
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Re: Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

Thanks for the reply, I thought I may have to do something like that(I'll at least try to integrate this and see how it goes), but I'm also worried about the performance. Iterating over contact manifolds in general is pretty swift, depending on the circumstances I've seen a simple simulation handle iterating over all of them in 12-30 microseconds. But that is just one iteration. If I do this per trigger...I want to be able to support 20+ triggers, and that can add up. Have you done any profiling with this?

Like I said, I'll try this regardless, just worried about whether or not it's a long term solution.
User avatar
dphil
Posts: 237
Joined: Tue Jun 29, 2010 10:27 pm
Contact:

Re: Shape collisions with btPairCachingGhostObject

Post by dphil »

Yeah I'm not sure about performance. In my cases just using ghost aabb's has often sufficed (eliminating the need for all those inner for loops and manifold inspection), and I've usually only used one or a few ghost objects at a time anyway. At least make sure to set the ghost collision mask appropriately so it only detects collisions with the minimum necessary object types.
User avatar
dphil
Posts: 237
Joined: Tue Jun 29, 2010 10:27 pm
Contact:

Re: Shape collisions with btPairCachingGhostObject

Post by dphil »

Alternatively, if you just care about enter/leave events, you could also try monitoring manifold creation/deletion events to track which objects are in contact without requiring constant manifold array iteration. There have a been a couple threads on this; for example: http://bulletphysics.org/Bullet/phpBB3/ ... f=9&t=6897

Although if you need to do something with every object in a ghost at every time step, the above approach probably won't provide much (if any) benefits.
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Re: Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

So I implemented your idea...sorta. I looked at how I was already iterating over the contact manifolds(which was copy pasta from somewhere else, I can't recall where), and realized there were two very different methods going on. I looked inside some functions and assumed iterating over what exists in the dispatcher was faster. I haven't profiled yet, so I don't know exactly, but there was no visible performance hit in my simulation, so I'm happy. Here is how I did it:

Code: Select all

int numManifolds = PhysWorld->getDispatcher()->getNumManifolds();
for (int i=0;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = PhysWorld->getDispatcher()->getManifoldByIndexInternal(i);
if( contactManifold->getBody0() != Ghost && contactManifold->getBody1() != Ghost)
    continue;
int numContacts = contactManifold->getNumContacts();
for (int j=0;j<numContacts;j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if(pt.m_distance1 > 0)
    continue;

/** Do Logic Here **/

}
But there is a problem. Not all my triggers seem to be working properly, in particular the first few simulation steps. I haven't looked at it too closely yet, I'll be doing that and updating what I find here...but what seems to be going on is that not all the proper contact manifolds seem to be made for the triggers after the first simulation step call. Any experiences with that or ideas of how to tweak this?

Edit:
Did some more testing, put in some code to look at/log the first time it iterates over the contact manifolds and it showed it to be working fine in all cases. Even though the behavior clearly showed it as not working. Then I thought to check more then the first frame. Sure enough...I don't know exactly where/which simulation step it is...but it's not detecting any overlap in the contact manifolds in 1 or 2 of the first 30 or so simulation steps.

So I've confirmed there is a reliability issue going on. Have you had any reliability issues with your implementation? It's looking like either I try how you did yours or I could try to tweak the simulation for more reliability.
Last edited by Mako_energy02 on Sat Jul 23, 2011 6:44 pm, edited 3 times in total.
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Re: Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

I added your method and testing it and it gave the exact same results as the other method I implemented. Among some of the first few frames at start-up it seems to just miss there being a contact manifold there.

More specifically what I am trying to do:
I have a ghost object to keep track of objects in a starting area. When it detects objects being added to it(which it does automatically on the first game loop), it disables gravity for that object as well as collision response. The objects can then be manipulated by the mouse until they are removed from the start area. When they leave the start area they set the gravity back to the world gravity and re-enable collision response.

So missing a manifold for even one object in one frame causes the object in the start area to drift downward until it leaves the start area, where it then accelerates via gravity normally. In the cases where the manifold is missing for two frames it drifts downward twice as fast. I have yet to see it miss 3 frames though. 2 seems to be the cap. But it's vital for my setup that the manifolds be there every frame where it's appropriate.

As a side note on the modifications you made to the dispatcher for adding and removing objects, I won't bore you with the details, but implementing that isn't compatible with our current setup. To try and even test that would require a re-write in a few places that we unfortunately don't have the time to do. :(
Last edited by Mako_energy02 on Sat Jul 23, 2011 6:44 pm, edited 1 time in total.
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Re: Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

Still having an issue with Ghost Objects. Although I overlooked a small detail in my previous posts. It seems it works properly the first time I load any level. When I go to unload a level I keep the bullet world, broadphase, dispatcher, etc...intact. I do try to clean it out, such as verifying all broadphase pairs have been cleaned and all the manifolds in the dispatcher. I also found a function on the broadphase called "resetPool()" which seems relevant to resetting the simulation.

Despite all that when I go to load it a second time(without restarting my app) it'll seem to miss there being a manifold for one or two frames. And it doesn't ever seem to be the first frame of any level load. My current best theory is just that I am not doing a good job of cleaning up. If it can be avoided, I'd like to clean up the simulation without destroying all the bullet related classes and rebuilding them. Anyone know if this is possible? Or am I stuck deleting everything?

Edit:
I've implemented functionality to destroy and recreate all bullet structures(world, broadphase, dispatcher, solver, collision configuration, and ghost callback) whenever I change a level. However...the behavior persists. But there are a couple of interesting things I've noticed from sticking a bunch of logging things in the code path. Here are my notes so far:

1) On the first level load of any application start, it works 100% of the time.
2) On the first frame of every level, it detects the object and adds it to my trigger just fine.
3) It doesn't always remove the object from the trigger, it is completely erratic on all but the first level load(see above).
4) If it does remove the object from the trigger improperly, it does so on the second simulation step of the level.
5) The object rejoins the trigger usually 1 or 2 simulation steps later. I think sometimes it'll even go as long as 3 or 4 simulation steps, but this didn't occur while I was recording it to my log.
6) Each time the object was removed from the trigger(remember, I am iterating over contact manifolds to check for real shape collisions), my weak profiler clocked the simulation step as taking only 25 microseconds, where the timing on the surrounding simulation steps was ~340 microseconds.

That last one especially caught my attention. I'm starting to think I need a bullet author to step in and give an opinion if I'm going to see any resolution to this. Does bullet have some quick escape inside stepSimulation() that causes it to clear the contact manifolds? Is there anything else that can explain this behavior?
Last edited by Mako_energy02 on Tue Jul 26, 2011 6:17 pm, edited 1 time in total.
Mako_energy02
Posts: 171
Joined: Sun Jan 17, 2010 4:47 am

Re: Shape collisions with btPairCachingGhostObject

Post by Mako_energy02 »

Through more copious logging I have determined that the broadphase pairs for the objects are still there and doing fine. However their mainfolds are disappearing and then coming back 1 or 2 frames later with the exact same values. Also, only the manifolds pertaining to one object overlapping with two of my triggers seems to be going missing. The only thing about the object that is unique is that it's the only free moving object. Every other object in the simulation is either a trigger, static body, or constrained to a static body. Because the object is accumulating momentum through gravity, I know it is in the simulation and being processed as normal. But the amount of time StepSimulation takes is alarming. I don't think it's likely that bullet is actually calling the narrowphase collision detection. Timing bullet when there are no objects in the world StepSimulation takes 60 microseconds to complete(more then double what it's doing when my manifolds go missing). So it has to be taking some kind of shortcut and skipping some heavy function calls.
Post Reply