How to prevent tunneling?

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

How to prevent tunneling?

Post by mreuvers »

Hi there,

In our current game, Bullet unfortunately suffers from a lot of tunneling. Tunneling often occurs between an intersection of two objects. For instance when firing off a large object to a stack of cubes, the fired object tunnels its way exactly through the intersection of the stacked objects, leaving the stack practically untouched. Apart from looking silly, clearly this is just wrong and undesired behavior.

We tried a lot to fix this:

* shoot at lower speed
Currently our object (1x1x1 meter) travels at approx 100 m/s (linear velocity value is 100; I assume this is in m/s?). The object doesn't really have to travel at high speeds in order to tunnel. Lowering its speed would render the whole firing principle invalid and is therefore not an option.

* More substeps
Currently the game runs at 1 substep (and 1 / 120 fixed timeframe). If we increase the number of substeps the framerate on our target platform (Wii) drops significantly. Therefore we need to stick to the 1 substep maximum.

* Lower fixed timeframe
If I lower the fixed timestep from 1/120 to 1/360, tunneling hardly occurs. But then the simulation looks like as if it's been played on the Moon. Slow motion effects are cool, but not constantly ;-) Not an option either.

* Increase object size
The current object is well over 1x1x1 meter, and yet it still tunnels through 'cracks' that are only a couple of centimeters wide. Increasing the object size would look and play bad and is therefore also ruled out.

Honestly I find it very strange that a relatively slow and large object, like the one we use, can tunnel that easily. Is there perhaps some missing anti-tunneling setting that I'm missing here? :D Or is it perhaps possible to increase the checks for one particular object (that travels at higher speeds)? I.e., our fired bullet would have several substeps, yet all the other objects will only have one substep. Is such a thing possible with Bullet?

Anyhow, if anyone has experienced similar problems with Bullet, I'd like to know how they dealt with it.

Looking forward to your response.


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

Re: How to prevent tunneling?

Post by Erwin Coumans »

It could be reduced by the new CCD motion clamping, that embedded in 'integrateTransforms' in btDiscreteDynamicsWorld. It is checked into the latest Subversion now. It is not as versatile as the upcoming btContinuousDynamicsWorld, but it might help you.

If this doesn't help, it might be because most collision interactions only generate one contact point at a time. You can try to see if box versus box suffers the same problem (box-box calculates all contact points from scratch). There has been discussion on this forum by Gino van den Bergen how to improve this, so we could try to implement that.
  • CCD motion clamping activates for convex objects that exceed a (squared to avoid taking square roots) velocity threshold. By default this threshold is zero, which means this feature is disabled for rigid bodies.
    For a cube of size 1 try:

    Code: Select all

    //enable CCD if the object moves more than 1 meter in one simulation frame
    body->setCcdMotionThreshold(1);
    
  • CCD works on an embedded sphere of radius, make sure this radius is embedded inside the convex objects, preferably smaller: for an object of dimentions 1 meter, try

    Code: Select all

    body->setCcdSweptSphereRadius(0.2f);
    
  • There is no CCD motion clamping between pairs of objects that are in penetration or that share contact points.
  • Try shooting a fast box in the latest demos (using . key or right mouse button) and increase the initial speed using '+' key, and watch the gNumClampedCcdMotions variable at the screen.
Can you try 2.72 alpha and report your experiences with it?
http://bullet.googlecode.com/files/bull ... alpha0.zip

Thanks,
Erwin

Code: Select all

#include "BulletCollision/BroadphaseCollision/btCollisionAlgorithm.h"

class btClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
{
	btCollisionObject* m_me;
	btScalar m_allowedPenetration;
	btOverlappingPairCache* m_pairCache;


public:
	btClosestNotMeConvexResultCallback (btCollisionObject* me,const btVector3& fromA,const btVector3& toA,btOverlappingPairCache* pairCache) : 
	  btCollisionWorld::ClosestConvexResultCallback(fromA,toA),
		m_allowedPenetration(0.0f),
		m_me(me),
		m_pairCache(pairCache)
	{
	}

	virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
	{
		if (convexResult.m_hitCollisionObject == m_me)
			return 1.0;

		btVector3 linVelA,linVelB;
		linVelA = m_convexToWorld-m_convexFromWorld;
		linVelB = btVector3(0,0,0);//toB.getOrigin()-fromB.getOrigin();

		btVector3 relativeVelocity = (linVelA-linVelB);
		//don't report time of impact for motion away from the contact normal (or causes minor penetration)
		if (convexResult.m_hitNormalLocal.dot(relativeVelocity)>=-m_allowedPenetration)
			return 1.f;

		return ClosestConvexResultCallback::addSingleResult (convexResult, normalInWorldSpace);
	}

	virtual bool needsCollision(btBroadphaseProxy* proxy0) const
	{
		//don't collide with itself
		if (proxy0->m_clientObject == m_me)
			return false;

		///don't do CCD when the collision filters are not matching
		if (!btCollisionWorld::ClosestConvexResultCallback::needsCollision(proxy0))
			return false;

		///don't do CCD when there are already contact points (touching contact/penetration)
		btAlignedObjectArray<btPersistentManifold*> manifoldArray;
		btBroadphasePair* collisionPair = m_pairCache->findPair(m_me->getBroadphaseHandle(),proxy0);
		if (collisionPair)
		{
			if (collisionPair->m_algorithm)
			{
				manifoldArray.resize(0);
				collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
				for (int j=0;j<manifoldArray.size();j++)
				{
					btPersistentManifold* manifold = manifoldArray[j];
					if (manifold->getNumContacts()>0)
						return false;
				}
			}
		}
		return true;
	}


};

//#include "stdio.h"
void	btDiscreteDynamicsWorld::integrateTransforms(btScalar timeStep)
{
	BT_PROFILE("integrateTransforms");
	btTransform predictedTrans;
	for ( int i=0;i<m_collisionObjects.size();i++)
	{
		btCollisionObject* colObj = m_collisionObjects[i];
		btRigidBody* body = btRigidBody::upcast(colObj);
		if (body)
		{
			if (body->isActive() && (!body->isStaticOrKinematicObject()))
			{
				btScalar fraction = 1.f;
				btScalar squareVel =  body->getLinearVelocity().length2();

				if (body->getCcdSquareMotionThreshold() && body->getCcdSquareMotionThreshold() < squareVel)
				{
					BT_PROFILE("CCD motion clamping");
					if (body->getCollisionShape()->isConvex())
					{
						body->predictIntegratedTransform(timeStep, predictedTrans);
						btClosestNotMeConvexResultCallback sweepResults(body,body->getWorldTransform().getOrigin(),predictedTrans.getOrigin(),getBroadphase()->getOverlappingPairCache());
						btConvexShape* convexShape = static_cast<btConvexShape*>(body->getCollisionShape());
						btSphereShape tmpSphere(body->getCcdSweptSphereRadius());//btConvexShape* convexShape = static_cast<btConvexShape*>(body->getCollisionShape());
						convexSweepTest(&tmpSphere,body->getWorldTransform(),predictedTrans,sweepResults);
						if (sweepResults.hasHit() && (sweepResults.m_closestHitFraction < 1.f))
						{
							fraction = sweepResults.m_closestHitFraction;
//							printf("clamped integration to hit fraction = %f\n",fraction);
						}
					}
				}
				body->predictIntegratedTransform(timeStep*fraction, predictedTrans);
				body->proceedToTransform( predictedTrans);
			}
		}
	}
}
mreuvers
Posts: 69
Joined: Sat May 10, 2008 8:39 am

Re: How to prevent tunneling?

Post by mreuvers »

Hi Erwin,

Thanks for the tips! Unfortunately our production code is using bullet 2.70 and I'm missing 200+ commits. I've tried to implement the changes directly into the 2.70 codebase, but ran into too many missing code issues.

So I tried our code with the updated Bullet code in a seperate codebase. However when compiling the latest code (#1304) I ran into a number of compile errors. You guys still don't seem to use the highest warning level, right? Apart from having several unused variables, I also ran into this beauty:

warning C4512: 'btCollisionPairCallback' : assignment operator could not be generated.

I 'fixed' that by adding a private assignment operator declaration (no implementation), but it is a dangerous warning.

Anyhow when I got rid of all errors I ran the code. Unfortunately our code immediately crashed (access violation) in btDbvt.cpp. So I reverted btDbvt.cpp and all classes that seem to use the latest btDbvt.h. After a few reverts I was able to run the code.

Now the results... With the proposed ccd settings, the resulting collision detection seems indeed to be much more reliable. However it doesn't completely get rid of the tunneling. If the speed is high enough, I'm still able to let it tunnel 80-100% of the time. It's definitely better, but it's still not foolproof :(

Also when using the p2p constraint, I'm still able to pull objects through static and dynamic collision data with no real effort. It is as if the collision data doesn't even exists. The only way to resolve this, is by using my old impulseclamp hack, which has the negative drawback that you cannot directly control the p2p constraint: the constraint slowly follows the cursor and wobbles. Also if I stack 7 objects with a height of 30 centimeters on each other, things will start to shake.

If you have any other tricks that could resolve this, I'm all ears ;-)

Thanks again!

Cheers,


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

Re: How to prevent tunneling?

Post by Erwin Coumans »

We don't have a full working btContinuousDynamicsWorld, so I can only help with some suggestions for workaround.
Also when using the p2p constraint, I'm still able to pull objects through static and dynamic collision data with no real effort. It is as if the collision data doesn't even exists.
Indeed, the current CCD motion clamping is very primitive and it is only active between object pairs that are not when objects are not touching ( no contact points between the objects). This condition has been added to prevent objects from getting stuck.

You could try to mix the two solutions, and only apply this impulseclamp on the p2p constraint if the the object is in touching contact, and rely on CCD when objects are not in contact. Only enable CCD for a fast moving object for objects that need it. Have you tried it with spheres, and see if the problem is the same? You could experiment with different values for the ccdSweptSphereRadius, just keep it fully enclosed: a bit smaller than the actual radius of a btSphereShape.

Good luck and thanks for the feedback on the other issues,
Erwin
mreuvers
Posts: 69
Joined: Sat May 10, 2008 8:39 am

Re: How to prevent tunneling?

Post by mreuvers »

Hi Erwin,

I'll try to only apply the impulse clamp when there are no contact points and see how that turns out. Is there any quick way for me to retrieve this information directly from the rigidbody? Or do I need to use the internaltickcallback for that to determine what bodies are actually colliding?

And suppose that I enable the impulse clamping after I determined that there is a collision, won't I be too late? To do this correctly, in my opinion I would have to enable the clamping BEFORE the object collides, otherwise the object will still be pulled way into the static collision.

Also I don't think we can use spheres, because our objects are long rectangular shapes. Things go wrong when using a shape that is approx 25 cm high and 4 meters wide. As mentioned in my previous post, simply stacking 7 or more of these objects on each other will cause them to shake and tunnel through each other if using a low substep value. For these oddball shapes, is there any way to improve collision response without increasing the substeps or lowering the fixed timestep? More hacks and workarounds are greatly appreciated :-D

Thanks en de groeten uit Nederland ;-)