Game Development Community

dev|Pro Game Development Curriculum

Rigid Shape Class

by Thomas \"Man of Ice\" Lund · 04/07/2004 (9:57 am) · 256 comments

Download Code File

History
2005-09-08 Updated with performance fix from XoCluTch

What is this?
For my game I needed a boulder that rolls down a hill, and that one needs to evade. Initial attempts to use the existing classes (items, shapes and especially projectiles) fail because of too simple physics and/or lack of collision box support. I was faced with either implementing collision boxes in the projectile class, look into ODE or try to code my own rigid body class.

Thanks to Ben Garney I didnt have to do any of these (thanks ;-) ), as there already exists rigid physics in the engine. The rigid.cc/h files have everything needed, but they are only available as con/net objects using the vehicle classes.

So I simply took the base vehicle class and the hover vehicle class, merged them together and removed most of the unneeded code. The result is actually fantastic, and I cant believe this isnt standard part of Torque.

As the code basically is a hover vehicle there might be parts of the code that should be removed further. If you spot any of those, then post here and I'll update the codebase.

What does it look like?
No resource without a movie :-)
Here is the final result from a tech demo level in my upcomming action adventure game.

www.codejar.com/rigidshape4.wmv

I also did a few early tests and I've included the movies here. The lack of realism is totally due to way too much impulse applied with a large vertical factor + low mass for the chests.

www.codejar.com/rigidshape.wmv
www.codejar.com/rigidshape2.wmv
www.codejar.com/rigidshape3.wmv

All movies are approx 3 MB

How to add
First thing is to add the 2 attached files in the zip to your engine\game dir and to your project. Then open the shapebase.h and find this

class ShapeBaseConvex : public Convex
{
   typedef Convex Parent;
   friend class ShapeBase;
   friend class Vehicle;

and add

friend class RigidShape;

Also in the same file down the bottom find this

#define StaticShape_GenericShadowLevel 2.0f
#define StaticShape_NoShadowLevel 2.0f

and add

#define RigidShape_GenericShadowLevel 0.7f
#define RigidShape_NoShadowLevel 0.2f

Recompile your engine and thats it.

If you want to do this "for real", then one also needs to define a new objectType. I have reused the ShapeBaseObjectType - change that if you want or go through tons of code and add a RigidShapeObjectType.

How to use
From script you now got access to a RigidShapeData datablock and a RigidShape object type. Your DTS object is required to have a mass node, but nothing else. The code still includes dusttrail and splash emitters from the vehicle code, as well as impact + water sound. This example does not use any of those.

An example datablock for a rigid shape is included below

datablock RigidShapeData( BouncingBoulder )
{	

   category = "RigidShape";
	
   shapeFile = "~/data/shapes/boulder/boulder.dts";
   emap = true;

   // Rigid Body
   mass = 500;
   massCenter = "0 0 0";    // Center of mass for rigid body
   massBox = "0 0 0";         // Size of box used for moment of inertia,
                              // if zero it defaults to object bounding box
   drag = 0.2;                // Drag coefficient
   bodyFriction = 0.2;
   bodyRestitution = 0.1;
   minImpactSpeed = 5;        // Impacts over this invoke the script callback
   softImpactSpeed = 5;       // Play SoftImpact Sound
   hardImpactSpeed = 15;      // Play HardImpact Sound
   integration = 4;           // Physics integration: TickSec/Rate
   collisionTol = 0.1;        // Collision distance tolerance
   contactTol = 0.1;          // Contact velocity tolerance
   
   minRollSpeed = 10;
   
   maxDrag = 0.5;
   minDrag = 0.01;

   triggerDustHeight = 1;
   dustHeight = 10;

   dragForce = 0.05;
   vertFactor = 0.05;

   normalForce = 0.05;
   restorativeForce = 0.05;
   rollForce = 0.05;
   pitchForce = 0.05;
};

By including the category="" it now shows up in the world editor under the Shapes, and its fully working. Spawn the object on a hilltop and see it roll down :-)

For the mission editor to work you will need to add a create() function that hooks into the mission editor. You can either put this into a rigidShape.cs file (dont forget to load it from game.cs) or put it into any .cs file in the server\script

// Hook into the mission editor.

function RigidShapeData::create(%data)
{
   // The mission editor invokes this method when it wants to create
   // an object of the given datablock type.
   %obj = new RigidShape() {
      dataBlock = %data;
   };
   return %obj;
}

If you want to have a little fun pushing things around then add this to your player onCollision:

function Armor::onCollision(%this,%obj,%col)
{

...

if (%col.getDataBlock().getName() $= "BouncingBoulder") {
	 // Apply an impulse to the object we collided with
	 %eye = %obj.getEyeVector();
         %vec = vectorScale(%eye, 10);
	 
        // Add a vertical component to give the item a better arc
	%dot = vectorDot("0 0 1",%eye);
	if (%dot < 0)
		%dot = -%dot;
	%vec = vectorAdd(%vec,vectorScale("0 0 2",1 - %dot));

	// Set the object's position and initial velocity
	%trans = %col.getTransform();
   
   // Heres the position and rotation.
   %pos = getWords(%trans, 0, 2);
	%col.applyImpulse(%pos,%vec);	 
}

...

}

I've done a few tests with forces, masses and such - and as you saw from my movies with varying results.

The objects are fully network aware, and I bet this code can replace ODE for most purposes if you dont need total realism.

As I wrote earlier, the code can be stripped down further. There are various pieces of the physics that I do not understand fully and left in. I would be very happy if someone could run through those parts and see if any of it can be taken out.

Regarding performance, I've tried to add approx 20 shapes to a scene and then letting all collide with each other (its in one of the movies actually). I noticed no performance degradation at all with those simple shapes (they had a 6 sided box as collision mesh.

The boulders in the last movie have an approx 25 faced hedra inside it as a collision mesh. I think I had 15 of those in an scene without performance degradation.

These shapes do have 1 problem though, and that is the collision detection that sometimes fails - exactly as vehicles. If they gain too much speed they will dip into the terrain or go through it. I think a lot of people are looking into that problem at the moment here www.garagegames.com/mg/forums/result.thread.php?qt=17384

Enjoy ;-)
#21
04/08/2004 (8:59 am)
Seems my last post got eaten up. I'll try again (2 secs to dinner, so I'm in a hurry)

Its all my fault. I cleaned up a last item 10 mins before posting the resource. The fix was to change the collision mask to ShapeBase. I ran a quick test in my game, and everything collided as before. All my shapes are moving, so I never tested on a resting one. Sorry.

The fix is:

Open your rigidshape.cc, find the RigidShape constructor and change the mask from ShapeBase to Vehicle - that fixes the collision problem when @ rest.

mTypeMask |= VehicleObjectType;

I'll update the code later today. Dinner time!! :-)
#22
04/08/2004 (11:14 am)
Code has now been updated and should work with resting objects

This also means that you need to be extra careful with your raycasts - the items will now appear to be Vehicles and not shapebase objects, maybe making it a bit tricky to do manual raycasts in case you forget.
#23
04/08/2004 (10:10 pm)
Great work Thomas, life-like physics really opens up opportunities.

I noticed that the spheres reacted to each other, and to the player, but was the player affected by the collision? I
#24
04/09/2004 (12:26 am)
To make the boulders react to each other I simply added a onCollision to my boulders, tested to see if they collided with another boulder and then applied an impulse to the collision object. Very simple and looks cool.

You could use the same approach to your players. I am unsure if a player has a mass, but applying impulses on collision between players (put it in Armor::onCollision) is simple. I havent tested it, but I cannot see why it shouldnt work. It already works for explosions, so the mechanism is the same.
#25
04/09/2004 (2:03 am)
How can you make an object move in the direction hit?
i.e. player runs into boulder backwards, and it moves in the direction the player faces, same if the player strafes into it.
#26
04/09/2004 (3:04 am)
@Luke

Rewrite these 2 lines in the onCollision

%eye = %obj.getEyeVector();
    %vec = vectorScale(%eye, 10);

They determine the vector (=direction) you apply the impulse. In this simple example it gets the eye vector = the direction the player faces. A more correct way would be to take velocity vector in the object transform as that would be the direction the player pushes and use that.
#27
04/09/2004 (3:59 am)
Thomas, i figured it out that far, i just dont know exactly what to use to do it.
As in, how do i get the direction in the first place?
I've looked at some doc's for console commands and a few script examples, but I'm still a bit rusty on vectors.
(thanks for the tip though, it clarified it a bit better)
#28
04/09/2004 (4:07 am)
%vel = %obj.getVelocity();

Gets you the velocity vector of an object (afaik)
#29
04/09/2004 (4:50 am)
This is all i've managed to figure out so far.
[code]
if (%col.getDataBlock().className $= "RigidShapeData") {
// Apply an impulse to the object we collided with
//%eye = %obj.getEyeVector();
// %vec = vectorScale(%eye, 0.5);

%vec = VectorNormalize(%col);

// Set the object
#30
04/11/2004 (4:36 pm)
Wow Thomas this is a fun resource! Thanks!

I've been hacking on this and came up with some interesting stuff.

Basically I made a datablock option to allow you to pick a typeMask, choosing between shapeBase, vehicle, and Item. I am working on a racing game and have need of some nice bouncing pylons, however, the way I am handeling the collision tends to make the vehicle stop cold when hitting a little ole pylon, regardless of whether or not an impulse knocked the pylon away.

in file rigidShape.h inside class RigidShapeData : public ShapeBaseData around line 112
Point3F               dustTrailOffset;
   F32                   triggerTrailHeight;
   F32                   dustTrailFreqMod;
   
   //Founder - collision foolery
   S32 fakeMaskType;   //0 = shapeBase, 1 = Vehicle, 2 = Item

Then in rigidShape.cc:
RigidShapeData::RigidShapeData() , at the very bottom:
//Founderism
   fakeMaskType = 0;
Then in void RigidShapeData::packData(BitStream* stream), right after the splash sound velocity
stream->write(medSplashSoundVel);
   stream->write(hardSplashSoundVel);

   //Founderism
   stream->write(fakeMaskType);
then in the same place in void RigidShapeData::unpackData(BitStream* stream)
stream->read(&medSplashSoundVel);
   stream->read(&hardSplashSoundVel);
   //Founderism
   stream->read(&fakeMaskType);

then added at the bottom of: void RigidShapeData::initPersistFields()
addField("fakeMaskType",  TypeS32,        Offset(fakeMaskType,  RigidShapeData));
then finally the guts goes at the top of: bool RigidShape::onAdd()
bool RigidShape::onAdd()
{
   if (!Parent::onAdd())
      return false;
   
   
   if(mDataBlock->fakeMaskType == 0)
      mTypeMask |= ShapeBaseObjectType;
   else if(mDataBlock->fakeMaskType == 1)
      mTypeMask |= VehicleObjectType;
   else
      mTypeMask |= ItemObjectType;

Then in my scripts, I have the following function to let the rigidShape objects handle the collision:

function RigidShapeData::onCollision(%data, %obj, %col, %vec, %speed)
{
   //if it is colliding with it's self or the terrain, ignore it
   if(%obj == %col || %col.getType() & $terrainObjectType)
      return;

   error("RigidShapeData::onCollision %col:" SPC %col.getDataBlock().getName());
   error("RigidShapeData::onCollision %obj:" SPC %obj.getDataBlock().getName());

   %normal = vectorDot(%speed, vectorNormalize(%speed));
   if(%normal > 58)
      %scale = %normal / 2;

   %objMass = %obj.getDataBlock().mass;
   %colVel = vectorLen(%col.getVelocity());

   //add some random boost to the Z
   %objVec = getWord(%vec, 0) SPC getWord(%vec, 1) SPC mFloor(getRandom(2, 5));

   %objImpulse = %objMass * ((%speed / 100) + (%colVel / 15));

   //the case is, we don't want to apply an impulse greater then X times the objects mass, so
   //clamp it down, otherwise it might visit the moon
   %objImpulse = %objImpulse / 8 > %objMass ? %objMass * 8 : %objImpulse;

   error("objImpulse:" SPC %objImpulse);

   %obj.applyImpulse(%obj.getWorldBoxCenter(), VectorScale(%objVec, %objImpulse));
}

Of course remember to stick the new var in the dataBlocks:

fakeMaskType = 2; // 0 = shapebase, 1 = vehicle, 2 = item

Now my vehicles will send the pylons flying, actually slightly before they would make actual contact, but yet I can set heavier objects as vehicle or shapeBase so they will damage my vehicles before they are impulsed away.
#31
04/12/2004 (12:35 am)
Lovely - thanks for hacking and sharing :-)
#32
04/28/2004 (11:42 pm)
I was just able to download this and get it compiled this evening, and it is a great resource, thank you for sharing it. Though there was one problem I ran into, when ever the object got going of any speed at all and either ran into the terrain or an interior it would get stuck (see screenshot below) I am not to much of a programmer so I wouldn't get anything from looking through the code to figure this problem out, can anyone lend a hand?
---
www.blackflamesoft.com/images/torque/physics_problems.jpg
#33
04/28/2004 (11:58 pm)
I've never had a problem on terrain yet, but the issue I bet is the same as for vehicles. Collision detection with terrain and interiors is a little buggy.

Some of it can be helped by turning up the integration setting in the datablock at the expense of CPU power. Try a setting of 6 or 8

Also the first time you add some object in the editor objects can fall through the terrain. Grab them clear above the ground at let them fall down so collision detection occurs "normally" and not with the object stuck inside the terrain.

Hope that helps a bit
#34
06/25/2004 (10:09 pm)
I just tried this on the the HEAD of TGE and it worked great! Thanks for such a great resource! :D

Only problem I've noticed is that the rigid shape uses a lot more CPU when they're idling than when they're moving which is pretty interesting...
#35
06/26/2004 (1:46 am)
Small update - my example code for the collision had a bug.

I changed this:
// Heres the position and rotation.
   %pos = getWords(%trans, 4, 6);
   %col.applyImpulse(%pos,%vec);

into

// Heres the position and rotation.
   %pos = getWords(%trans, 0, 2);
   %col.applyImpulse(%pos,%vec);

Weird collisions with things flying in wrong directions should be gone now, and everything behave as expected
#36
10/10/2004 (3:06 pm)
Im not sure why but I have added the code above to player.cs but I still can not push the boulder.
Any idea on what im missing?
Thanks
#37
10/10/2004 (9:51 pm)
Wayne,
I had the same issue. You should decrease the mass of the boulder from 500 to something smaller like 100 and you should increase the force of the impulse by changing %vec = vectorScale(%eye, 10); to 100 or so.
GL,
Joe
#38
10/11/2004 (5:23 am)
That worked great thanks Joe
#39
10/11/2004 (5:24 am)
It would be nice to be able to setup where you can pick it up with a button click and throw it :)
Im a nob so i would know how to go about doing that kind of stuff lol.
#40
10/11/2004 (5:56 am)
Also , is thir a way to set up a trigger so that when the boulder rolls through it that it will give a point or something to the last persion or team doing the goal, like socker ?