How to properly detect collision using callbacks?

mreuvers
Posts: 69
Joined: Sat May 10, 2008 8:39 am

How to properly detect collision using callbacks?

Post by mreuvers »

Hi there,

In our last game we experienced a *lot* of problems with the detection of collision, mainly because we didn't understand how to set it up properly.

Apart from the CollisionInterfaceDemo, with hardly any comments, there isn't any documentation at all on how to handle collision detection properly. During our last project we've spent days if not weeks, figuring out how to 'work around' several strange bumps in the road. And we want to prevent that from happening again ;-)

In our new project I want to setup a collision detection routine in the internalTick callback again, but also want to make sure I set it up correctly. Hopefully this code and thread will serve as a guide/manual to everyone who experiences problems with setting up a proper collision detection routine using the internal tick callback.

First things first: my physics world settings:

Code: Select all

	<physics substeps="4" fixed_timestep="120"
	         gravity="-10.0" friction="0.5"
	         linear_sleeping="1.0" angular_sleeping="0.1"
	         deactivation_time="1.5"
	/>
... and my cube settings

Code: Select all

	<physics_cube>
		<position x="2.0" y="-7.5" z="2.0" />
		<scale x="1.0" y="4.0" z="1" />
		<rotation x="0" y="0.0" z="0.0" w="1" />
	</physics_cube>

	<physics
		friction="0.2"
		restitution="0.0"
		density="2.4"
		linear_damping="0.5"
		angular_damping="0.5"
	/>
Groundplane is somewhere between -10 and -11.

This is the code I have in the internal tick callback:

First get the dispatcher:

Code: Select all

	btDispatcher* dispatcher = 
		const_cast<btDynamicsWorld*>(p_world)->getDispatcher();
	
	TT_NULL_ASSERT(dispatcher);
	
Then we retrieve the number of contact manifolds. From what I understand a contact manifold is a space in which there is contact between two rigid bodies. Correct me if I'm wrong though ;-)

Next we 'iterate' over all the manifolds, effectively iterating over all spaces in which there is contact.

Code: Select all

	int numManifolds = dispatcher->getNumManifolds();

	// Print some debug stuff
	if (numManifolds > 0)
	{
		TT_Printf("Internal Tick: manifolds: %d\n", numManifolds);
	}

	for (int i = 0; i < numManifolds; ++i)
	{
		btPersistentManifold* contactManifold = 
			dispatcher->getManifoldByIndexInternal(i);
		
		TT_NULL_ASSERT(contactManifold);

		// Print some debug stuff
		for (int j = 0; j < contactManifold->getNumContacts(); ++j)
		{
			btManifoldPoint& cp = contactManifold->getContactPoint(j);
			TT_Printf(" %d > dist %f impulse %f\n", i, cp.getDistance(), cp.m_appliedImpulse);
		}

		btCollisionObject* body0 = 
			static_cast<btCollisionObject*>(contactManifold->getBody0());
		btCollisionObject* body1 = 
			static_cast<btCollisionObject*>(contactManifold->getBody1());
		
		TT_NULL_ASSERT(body0);
		TT_NULL_ASSERT(body1);

		// do stuff

		contactManifold->clearManifold();                // what's this? 
}
And now the problems arise. Suppose I drop my non-rotated cube on the flat ground plane I receive these results:

Code: Select all

Internal Tick: manifolds: 1
 0 > dist -0.034546 impulse 18.978798
 0 > dist -0.034546 impulse 0.000000
 0 > dist -0.034546 impulse 20.950485
 0 > dist -0.034546 impulse 0.138508
Collision! # points 4
So there is 1 manifold with 4 contact points. Dist is the penetration depth (retrieved by manifold.getDistance()). Two of them hardly do anything and two of them generate considerable impulses.

So far so good. Although I find it a bit strange that only two contact points generate impulses and not all four, after all it's a cube with a flat surface... but okay ;-)

Let's take a look at the next internal callback result:

Code: Select all

Internal Tick: manifolds 1
 0 > dist -0.025155 impulse 0.680674
 0 > dist -0.025002 impulse 0.155525
 0 > dist -0.024833 impulse 0.000000
 0 > dist -0.024985 impulse 0.000000
Collision! # points 4
Apparently the object is still stuck in the floor, generating bogus collision detection results. In fact my object stays in the ground for at least 20+ frames! Generating 20+ collisions. Is this correct behavior?

Of course I can check the impulses to see whether it's a real collision or not, but it feels a bit like hackery to me. After all, what would be a good threshold for an impulse? If an object is really stuck in the surface, its 'unstuck' impulse can be quite high, still generating a bogus collision detection. If I set the impulse threshold too high, I will definitively get 'false negatives'. So my guess is that that isn't the way to go.

And what's that

Code: Select all

contactManifold->clearManifold();
method doing exactly? When and why should I call it? Did I call it correctly here? Should I call it whenever I move on to the next manifold? Please let me know how I should use that method.

I also tried the following collision detection 'hack' reported somewhere on this forum:

Code: Select all

	
		// check if collision is present
		bool hasCollision = false;
		for (int j = 0; j < contactManifold->getNumContacts(); ++j)
		{
			btManifoldPoint& cp = contactManifold->getContactPoint(j);
			if (cp.getDistance() < 0.0f)
			{
				hasCollision = true;
				break;
			}
		}
		
		if (hasCollision == false)
		{
			contactManifold->clearManifold();
			continue;
		}
		else
		{
			TT_Printf("Collision! # points %d\n", contactManifold->getNumContacts());
		}
This hack makes sure that there is at least object penetration (and thus collision) before you process the rest of the loop. However this doesn't get rid of the bogus 'unstuck' impulse collision detections: they all have impulses and are also still stuck in the other object (so getDistance() < 0).

I also tried fiddling with another apparently undocumented method: refreshContactPoints(), but that didn't do much. Should we users even use that function? If so, when?

So my question is: how should I properly setup an internaltick callback to detect collisions. What are the exact steps and why? And how can I reduce the amount of reported bogus results to an absolute minimum?

Enough questions for now. Thanks a lot for your help!

Cheers,


Martijn
gjaegy
Posts: 178
Joined: Fri Apr 18, 2008 2:20 pm

Re: How to properly detect collision using callbacks?

Post by gjaegy »

Hi Martijn,

I need to solve a similar problem. Have you managed to find a nice way of having collisions properly reported ?

Thanks,
Gregory
rcharlton
Posts: 6
Joined: Wed Jun 10, 2009 1:36 pm

Re: How to properly detect collision using callbacks?

Post by rcharlton »

The original post perfectly describes my current problem. It'd be really useful if someone in the know could provide some advice. Bullet seems like a great system hampered by inadequate documentation.
orstdani
Posts: 2
Joined: Fri May 22, 2009 8:14 pm

Re: How to properly detect collision using callbacks?

Post by orstdani »

I'm not sure if it solves your problem, but I use ghost objects in my tick callback.

Update the transform of the ghost to the transform of the object being tracked and loop overlapping btCollisionObject's.

for (int i=0; i<m_ghost->getNumOverlappingObjects(); i++)
{
btCollisionObject *co = m_ghost->getOverlappingObject(i);

// check "user data pointer" on btCollisionObject and take action accordingly
...
}


// daniel
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA

Re: How to properly detect collision using callbacks?

Post by Erwin Coumans »

rcharlton wrote:The original post perfectly describes my current problem. It'd be really useful if someone in the know could provide some advice. Bullet seems like a great system hampered by inadequate documentation.
Can you describe the problem more in detail?
It is not clear what the original poster means by 'bogus collision detection results'. The code that iterates over all contact manifolds in Demos/CollisionInterfaceDemo. There is also an implementation that only iterates over the contact manifolds of objects that have bounding box overlap with a btGhostObject, see btKinematicCharacterController::recoverFromPenetration
This hack makes sure that there is at least object penetration (and thus collision)
This assumption is incorrect: the Bullet constraint solver corrects the penetration as well as the velocity between two colliding objects. Velocity correction for contacts happens when the distance is below the ContactProcessingThreshold, which can be positive.

The contactPoint.m_appliedImpulse is a combination of 'velocity correction' impulse and 'penetration correction' impulse. It is very common that the applied impulse varies for multiple contact points, this is not a bug. If you only want the velocity impulse, you can separate the penetration correction using the split impulse feature, re-enabled in Bullet 2.75 RC3 or later:

Code: Select all

dynamicsWorld->getSolverInfo().m_splitImpulse = true;
Users should never call btPersistentManifold::clearManifold or btPersistentManifold::refreshContactPoints, those methods should be marked as internal only.
Hope this helps,
Erwin
rcharlton
Posts: 6
Joined: Wed Jun 10, 2009 1:36 pm

Re: How to properly detect collision using callbacks?

Post by rcharlton »

Thanks for the comments - much appreciated.

Thanks for the reply.

I've got my collision code behaving sensibly now just using a simple impulse filter:

Code: Select all

void Scene::bullet_tick_callback(float time_step)
{
	const float min_impulse = 1.f;

	const int manifold_count = m_dynamics_world->getDispatcher()->getNumManifolds();
	for (int i = 0; i < manifold_count; ++i)
	{
		btPersistentManifold* contact_manifold = m_dynamics_world->getDispatcher()->getManifoldByIndexInternal(i);
		const int contact_count = contact_manifold->getNumContacts();
		for (int j = 0; j < contact_count; ++j)
		{
			btManifoldPoint const& pt = contact_manifold->getContactPoint(j);

			if (pt.getAppliedImpulse() > min_impulse)
			{
				float gain = (pt.getAppliedImpulse() / 50.f);
				if (gain > 1.f)
					gain = 1.f;
				play_collision_sound(gain);

				break;
			}
		}
	}
}
A couple of things contributed to my confusion:

Another post suggests filtering collisions using m_lifeTime. In my simulation this causes some collisions to be missed altogether.

For a given callback the same manifold can (but does not always) appear more than once in the dispatcher's list. It'd be good to know why this is and whether it's significant.

Thanks!
User avatar
Erwin Coumans
Site Admin
Posts: 4221
Joined: Sun Jun 26, 2005 6:43 pm
Location: California, USA

Re: How to properly detect collision using callbacks?

Post by Erwin Coumans »

For a given callback the same manifold can (but does not always) appear more than once in the dispatcher's list.
Are you sure? If so, that is a bug: a manifold should appear only once in the dispatcher's array. Can you create a simple reproducing case in one of the Bullet demos?
Thanks,
Erwin