Game Development Community

Vehicle/Rigid Collision Fixes

by Ross Pawley · in Torque Game Engine · 06/11/2007 (10:23 pm) · 115 replies

Hey everyone, I just wanted to post these early fixes to the frequent and frustrating vehicle collision problems people have had with TGE. We spent a few hours today at the office going through things and this new code works relatively well with some small issues, mainly interpenetration. However, it's a far sight better than what was there, which was just calling into the collision routine for immobile ridgid objects (and thus unsuitable for vehicles entirely).

In rigid.cpp replace these functions:
bool Rigid::resolveCollision(const Point3F& p, Point3F normal, Rigid* rigid)
{
   atRest = false;
   Point3F v1,v2,r1,r2;
   getOriginVector(p,&r1);
   getVelocity(r1,&v1);
   rigid->getOriginVector(p,&r2);
   rigid->getVelocity(r2,&v2);
 
   // Make sure they are converging
   F32 nv = mDot(v1,normal);
   nv -= mDot(v2,normal);
   if (nv > -0.001f)
      return false;
 
   // Compute impulse
   F32 d, n = -nv * (1 + restitution * rigid->restitution);
   Point3F a1,b1,c1;
   mCross(r1,normal,&a1);
   invWorldInertia.mulV(a1,&b1);
   mCross(b1,r1,&c1);
 
   Point3F a2,b2,c2;
   mCross(r2,normal,&a2);
   rigid->invWorldInertia.mulV(a2,&b2);
   mCross(b2,r2,&c2);
 
   Point3F c3 = c1 + c2;
   d = oneOverMass + rigid->oneOverMass + mDot(c3,normal);
   Point3F impulse = normal * (n / d);
 
   applyImpulse(r1,impulse);
   impulse.neg();
   rigid->applyImpulse(r2,impulse);
   return true;
}
 
bool Rigid::resolveCollision(const Point3F& p, Point3F normal)
{
   atRest = false;
   Point3F v,r;
   getOriginVector(p,&r);
   getVelocity(r,&v);   
   F32 n = -mDot(v,normal);
   if ( n < 0 )
      return false;
   
   // Collision impulse, straight forward force stuff.
   F32 d = getZeroImpulse(r,normal);
   F32 j = n * (1 + restitution) * d;
   Point3F impulse = normal * j;
 
   // Friction impulse, calculated as a function of the
   // amount of force it would take to stop the motion
   // perpendicular to the normal.
   Point3F uv = v + (normal * n);
   F32 ul = uv.len();
   if (ul) {
      uv /= -ul;
      F32 u = ul * getZeroImpulse(r,uv);
      j *= friction;
      if (u > j)
         u = j;
      impulse += uv * u;
   }
 
   //
   applyImpulse(r,impulse);
 
   return true;
}

(CONTINUED)
#21
06/13/2007 (11:03 am)
Hey Tom,

What about WheeledVehicle vs. Player character? I know you said you are having issues with WheeledVehicle vs. WheeledVehicle, but since the player character is handled differently in terms of the collision process, is there any chance youw ill be looking at this as well?

Oh and thanks a ton guys.
#22
06/13/2007 (12:50 pm)
@Ron - It's not planned but the function that currently handles that is Vehicle::resolveDisplacement() which is currently commented out of the Vehicle code.
#23
06/13/2007 (11:53 pm)
If anyone is interested in using this to collide vehicles with rigidShapes, my buddy Kurt Schaeffer came by tonight and got it working okay, and he described what he did thusly:

"Basically, I applied the "fixed" Vehicle::ResolveCollisions and Vehicle::ResolveContacts to RigidShape.cc. Then, created a new SimObjectType enum for RigidShape (previously, the mTypeMask of RigidShape was VehicleObjectType). In the ResolveCollisions methods I created a new test condition for RigidShapeObjectType, like so:

if ( c.object->getTypeMask() & VehicleObjectType )
	{
		Vehicle* other = static_cast<Vehicle*>( c.object );
		colliding |= ns.resolveCollision(c.point , c.normal, 			&other->mRigid );
	}
	else if (c.object->getTypeMask() & RigidShapeObjectType )
	{
	RigidShape* other = static_cast<RigidShape*>( c.object );
	colliding |= ns.resolveCollision(c.point , c.normal, 		&other->mRigid );
	}
	else
		colliding |= ns.resolveCollision(c.point, c.normal);

Then, I searched through the codebase for occurences of VehicleObjectType, and added RigidShapeObjectType to nearly any OR statement that contained VehicleObjectType.

This is a quick hack, but it seems to make things work much better."
#24
06/14/2007 (5:13 pm)
Quick query, if I may. which codebase are you currently working off of at this point in time? mIsFinite() doesn't seem to show up in either the 1.42 or the tgea codebases I've got on backup... that one of the fixes introduced with the 1.5x series? I ask because rather obviously, that might indicate further changes that might need to be looked into. (was a pre-edit version of the ConvexFeature::testVertex advice set, but still. wanting to make sure you get as much *accurate* feedback as possible)

edit:. ok, so 2 queries
// Only interested in velocities greater than sContactTol,
         // velocities less than that will be dealt with as contacts
         // "constraints".
         if ( mFabs(vn) < mDataBlock->contactTol )
            continue;
         // Apply impulses to the rigid body to keep it from
         // penetrating the surface.
         if ( c.object->getTypeMask() & VehicleObjectType )
         {
            Vehicle* other = static_cast<Vehicle*>( c.object );
            colliding |= ns.resolveCollision(c.point , c.normal, &other->mRigid );
         }
         else
            colliding |= ns.resolveCollision(c.point, c.normal);
certainly seems straightforward enough, however... there's no accompanying oveloaded contacts function for rigid-on-rigid collision, per-se. Might that be the reason low-relative-velocity impacts atre misbehaving still, or is that contact+collision cumulative in nature? Doesn't seem to be, but then, I've never claimed to be the brightest in the bunch.

as to the self-colliding commenting in the code:
void Convex::updateWorkingList(const Box3F& box, const U32 colMask)
...
	  //so i says to mself, 'self, did you hit me'?
      if (itr->mConvex->getObject() != this->getObject())
	  {
		if ((!box.isOverlapped(itr->mConvex->getBoundingBox())) || (!itr->mConvex->getObject()->isCollisionEnabled())) {
			CollisionWorkingList* cl = itr;
			itr = itr->wLink.mPrev;
			cl->free();
		}
	  }

doesn't *seem* to have produced any particular errors at this point in time, but I'm not working with things with turrets and whatnot to check that at this point.
#25
06/14/2007 (8:10 pm)
You must have seen my post right before I edited it. mIsFinite is part of the Windows library or such. We had some problems with divide by zero errors so we were testing those cases.

As to the second query, contacts are handled *after* the collision is resolved already, and applies such small values that it doesn't really affect the collision velocities. In fact, you'll find that if you use only collisions that it will resolve small collisions normally handled by contacts just fine ( look in Rigid::resolveCollision and you'll notice it also calculates the same sort of friction force as resolveContacts does, but only applies it if it's less than the collision force (otherwise, it sets the friction force var to the value of the collision force var)). So, really, it's just unnecessary to have another resolveContacts function for contacts against rigid objects.

Regarding colliding with one's own object, that was a mistake in our thinking. In fact, what it's actually doing when the c.object == this, is resolving collisions of other objects (terrain, etc.) against the colliding object's faces (check out testVertex). This is important when you have objects that can find edges or vertices of the colliding object within tolerance, but the colliding object can't find either against the other object (imagine a large body colliding a small box for instance).

Edit: However, note that there are some differences in the calculations in resolveContacts, and I'm not entirely sure what those do at this point. I'm going to give Baraff's paper a full read through and I might have a better time understanding the code then(that is, I'm not saying that resolveContacts is entirely unnecessary, just that there's no real need to have one specifically for contacts against rigid shapes).
#26
06/18/2007 (7:53 am)
Apologies for aparently not making myself clearer, re:
// Only interested in velocities greater than sContactTol,
         // velocities less than that will be dealt with as contacts
         // "constraints".
         if ( mFabs(vn) < mDataBlock->contactTol )
            continue; [i]//read here, if the distance? velocity? is less than contact tolerance goto the next loop itteration and skip the below[/i]
  
         // Apply impulses to the rigid body to keep it from
         // penetrating the surface.
         if ( c.object->getTypeMask() & VehicleObjectType )
         {
            Vehicle* other = static_cast<Vehicle*>( c.object );
            colliding |= ns.resolveCollision(c.point , c.normal, &other->mRigid );
         }
         else
            colliding |= ns.resolveCollision(c.point, c.normal);

unless I'm totally misinterpreting that, the *only* function call that'd be called at the end of that for relative velocities at the sub-contact tolerance level would be contact...
#27
06/18/2007 (8:47 am)
if ( mFabs(vn) < mDataBlock->contactTol )

That confused us for a second as well... contactTol is a velocity and not a distance which it looks like at first glance. This test is ensuring that the vehicle velocity parallel to the collision normal is greater than contactTol. High speed impacts are handled in Vehicle::ResolveCollisions() and low ones in Vehicle::ResolveContacts().
#28
06/18/2007 (8:50 am)
Right. and at the moment it's beahving properly at high speed collisions, and passing through at low speed ones so...
#29
06/18/2007 (5:35 pm)
You're getting inter-penetration at low ( < contactTol ) velocities? I haven't seen that behavior in my tests, even against other vehicles.
#30
06/18/2007 (6:41 pm)
At low *relativistic* velocities, yes. (meaning the things are traveling at a high clip, but not vs one another). Been noticing that since that first post on Jun 12, 2007 16:41, actually.
#31
06/18/2007 (7:52 pm)
[say hello to the physics pedant inside of me]
I think you mean just relative velocities, Kirk. "Relativistic" refers to speeds approaching that of light. ie at least a billion jillion miles an hour, to be precise.
#32
06/18/2007 (9:25 pm)
Quote:"Relativistic" refers to speeds approaching that of light. ie at least a billion jillion miles an hour, to be precise.

Ah that. No, haven't seen that happen but the once so far, and I'm fairly certain thats been fixed along the line as well, since that off to neptune collision was around the third post.

Goofy smartmouthed asides aside, if nobody else is seeing that, then it must be something this end. Will go digging.
#33
06/18/2007 (10:24 pm)
I've got that. At high speed everything is hunky dory, but if I push into a parked car with another car, I can often get them to mesh together.

Edit: For what it's worth, that low-speed thing happens whether I do the "const S32 sMaxWarpTicks = 0" hack or not. It will happen with moving vehicles if it's not 0, as noted above.

another edit: I get an extremely slow/lockup situation when colliding a bunch of rigidShapes together with this code. I wonder if it's not a nice extreme test case, with no wheels and stuff to get in the way? The portopotties below are rigidShapes (and I've applied this code to my rigidShapes class as described above:
www.singularityfps.com/test.jpgIf you shove them real slowly, they'll merge together. Shove them with fast (like with a car hurtling from the side), once they start colliding with each other it will often lock up--if you tried it in my game, you'd want to remove rigidShape/rigidShape script collision code from server/scripts/rigidshape.cs. It's freely downloadable from www.singularityfps.com if anyone cares to mess with it. Please feel free to just use my DTS's for testing purposes, if you wish.

Not that I could stop you! ;-)
#34
06/18/2007 (10:39 pm)
Which version of TGE are you basing this off of?

I looked at the code and there are significant differences in the code for the function calls for 1.5.2.

Oh and in case I forgot, thanks a ton for your work on this guys.
#35
06/19/2007 (8:35 am)
@Ron - We're working with an older version of TGEA. If your talking specifically about mIsFinite() that was code we added for testing over here and nothing that has ever been a part of Torque.

@Lee - We've actually been testing with a simple wheel-less car lately. We've seen some lockups and cured them, but since we're not sure that our solutions are correct we're waiting to post them until we feel comfortable that we won't be setting people back.

The lockup issue is just a case where the collisions don't resolve and keep iterating forever. I'm thinking that we need more than just crossed fingers to ensure that we don't run into this infinite loop case. One idea was that after a certain amount of iterations... scale up the normal impulses by some increasing factor. So that eventually enough force will be generated to push any lockup apart. This might be a better solution than breaking the loop early and letting a collision fail.

On the other hand we've gone a long way towards not easily giving up on a collision. The reason you can get interpenetration at higher speeds is that the collisionTol is used not only to test for collisions, but in gathering potential collisions. So if your collision tolerance is 0.1, which seems to be everyones default, you get this situation.

farm2.static.flickr.com/1289/570276007_a0933ffb79.jpg
If all your verts are further away than collisionTol for each tick as your vehicle passes thru an object.... it will never see any collision. We had a perfect test of this when we dropped a vehicle with a simple box collision shape from 300 meters on to a really big flat DIF. It was luck if you would get a tick where one of the vehicle collision box verts was within the collisionTol distance to one of the DIF faces.

Our solution is to use a much bigger tolerance when selecting potential collisions... testing with 3 meters or so... then in Vehicle::ResolveCollisions() properly ignore collisions that haven't happened yet (further than collisionTol and on the front side of the collision face) and respond to penetrations.

As soon as we feel that we have this resolved properly we'll post the changes.
#36
06/19/2007 (9:20 am)
Actually Tom I asked because the newer version of of TGE uses a lot more vector based functions now, so the function's format is actually a bit different now. Instead of 1.0 you use 1.0f and the like.
#37
06/19/2007 (9:38 am)
@Ron - I'll have a look at it and see... it could be that GG has some improvements in 1.5.2 that we could take advantage of.
#38
06/26/2007 (8:28 pm)
@Tom - Did you get a chance to see what I meant with the differences in 1.5.2? Also, how are the slow collisions coming along?
#39
06/27/2007 (9:25 am)
Ron: Not sure what you mean here about using more vector-based functions. ?

The main changes to function prototypes [is this what you mean by format?] in 1.5.1 & 1.5.2 where const-ifying parameters and making some references to avoid unnecessary copying of objects.

As for the 1.0/1.0f thing - 1.0 is a double and 1.0f is a float. TGE 1.5.1 & 1.5.2 corrected a slew of cases where float constants should have been used instead of double constants. The gcc compiler can be un-smart and for PPC compiles inserts instructions to convert a double to a float when a double constant is used instead of a float constant.
#40
06/27/2007 (11:48 am)
Yeah disregard what I said about vectors earlier, not sure what I was smoking, but I meant to mention the floats instead and much like you said making parameters constants.