www.xbdev.net xbdev - software development
Monday December 10, 2018
home | about | contact | Donations

     
 

Game Physics Programming..

Bouncing Objects, Springs, Balls, Rigid Bodies...

 

Simple Rigid Body Physics

by Ben Kenwright

 

Lots of bouncy of Cubes and Balls....well thats how it should begin....and you'll find that 90% of all your game physics can be approximated with these objects....for example, if we where going to do a racing car...we'd approximate it as a box!....for a character, we'd approximate all his box parts using lots of spheres...its simple and effective!...always keep it simple :)

 

Some things we've got to look at:

Work out the collision, and for each collision point we work out its position, collision normal and its penetration distance.... We've got to add in static and dynamic friction.... Then theres the added complexity of stacking ...I mean anyone can bounce a single cube around...but put 2 or 3 of them on top of each other and just watch them go funny ...either sinking of jiggling out of control with a life of there own!

 

Keep it simple!   The best code is the simplest...we'll use the basics that work and add in some extra tweeks to account for problems that we notice.

 

Impulse based game physics are simple, math is quiet easy to understand and implement and its usually very robust.

 

The two areas that usually cause problems is making objects not jiggle and bounce making them stack and slide (friction).

 

Sleeping is one way to fix some things...To-Do

 

The algorithm at its simples is:

 

  •     Collision Detection (Store all the contact points)
  •     AddForces (i.e. Gravity)
  •     Update Velocitys (Angular & Linear)
  •     Apply Impulses
  •     Update Position & Rotation
  •     Loop

 

For the Collision Detection examples I've used Object Bounding Boxes (OBB's) ...since its much more realistic and useful than dealing with spheres....as we can have multiple contact points for a single shape.  The collision detection code can be a bit messy, but for each collision point you need to determine its position, normal and penetration depth....this was done using Seperating Axis Test and a mixture of plane clipping.

 

 

The two main parts of a collision impulse are the Normal impulse, which basically pushes the objects appart...without this, we've got problems...they'd just penetrate each other.  Next we have the Friction impulse, which would stop our object from sliding, we in effect set a friction threshold, so an object sitting on a steep slope wouldn't just jiggle down the slop, alternatively it would just sit still...unless its throw with some force to cause it to slide along the tangent slope.

 

One thing to notice is that the Impulse along the normal should be pushing the objects away from each other...hence it must be >0.....else it would be pulling them together!!!

 

 

Relative velocity - it allows us to determine the state of each collision point...very important when we have multiple collision points, as you'll see, we iterate over our collision data a few times for each update to remove jitter and stablise our values, and we can determine when and which collision points need further updating :)

 

 

Once we know the relative velocity for a collision point tells us the objects are approaching each other, we can apply a suitable collision pulse which will push them apart.

 

 

 

 

Working out the normal impulse, so that the collision points velocity is reduced to zero is just as easy as:

 

 

 

One great bit of extra code you'll need, is a bit of biasing code that prevents our objects sinking!  Its such a simple bit of extra code, but it makes things so much more stable.

 

It uses the following facts:
  • Proportional to the penetration depth
  • Allow some penetration (also sometimes called slop)
  • Impulse is along the normal
  • Small!

 

One thing you'll be able to notice with the bias factor, is that for single objects on the floor it takes a long time before sinking can happen...and maybe for a couple of objects....but once you start stacking cubes and having a lot more interaction, you'll notice the objects sinking into each other more....try toggling it on and off and see :)

 

 

Added bias not makes our objects more stable...and leads to a more robust system.

 

Onions!....one thing to look into is different penetration depths causing different responses...as some of the comercial engines take do different htings in different cases...for example, if the penetration was over a certain threshold, you'd actually push the position of the object back along that direction...would cause a jump of the object...but if it reached that threshold it would be going to far into the other object..maybe even passing through it...but you get the idea.

 

 

 

 

Friction!...you don't want your cubes to slide!.  Using the basic impulse above, this works great and is all thats needed for simple flat worlds...but if you stack a couple of cubes, or put a cube/ball or some other object on a slope...then it will just slide off the edge!.  As we dont have any friction!  We can do this by taking the tangental direction part of the collision and working out how much along that direction is happening...then we'll clamp it, so it's not allowed to move along that direction unless its over a certain threshold :)

 

The tangent impulse Pt, is tricky to see at first!...basically its an impulse which will STOP any movement along the tangent direction.  So between a minimum and maximum range, there will be no slipping!....hence Pt will counteract the tangent force to keep the object from slipping.

 

The threshold is determined to be proportional to the normal impulse, so once it passes as certain magnitude, we let it slide.

 

The basic equations for impulses all rely on the principle of a single collision point....but we're interested in Multiple collision points...for example, a chair sitting on the floor would have an impulse for each leg...and a cube on a plane, we'd have an impulse for the four corners.  The problem is applying an impulse to one corner rotates and shifts the object so that when we apply it to the other corners the object is no longer correct.

 

Secret to making it work, it so continue to apply impulses multiple times to our object collision points until it settles down.

 

  • Apply an impulse at each contact point
  • Continue applying impulses for fixed number of iterations

 

 

 

So the heart of the program code looks like this...and produces some reasonably stable rigid body results:

 

Code Snippet:
...

void Step( float dt )

{

      // Integrate Forces

      for (int i=0; i<g_numCubes; i++)

      {

            g_cubes[i].UpdateVel(dt);

      }

 

      // Update a few times to take into account stacking and multiple

      // contact points - applys impulse to each contact point on the cube

      for (int i=0; i<g_numIterations; i++)

      {

            ApplyImpulses(dt);

      }

 

      // Finally update the new position of our cube

      for (int i=0; i<g_numCubes; i++)

      {

            g_cubes[i].UpdatePos( dt );

      }

}

...

 

 

 

 

 

I put together a simple demo that demonstrates various test senarious using cubes...either on a plane or stacked...the performance in release stays around 60fps with 20 boxes stacked so its not to bad...but you can easily make it more efficient...most of the code has been kept simple so you can follow it.

 

Everything in the demo is 3D Cubes....even the ground is a large thin cube which is imovable...but it would be so easy to add in other shapes once you get to grips with the basics...Sphere-sphere, sphere-box, convex poly's etc.

 

Download Source Code (30k)

 

On the right you can see some screenshots of some test cases.

 

 

 

Sleeping!

Now we can make things more stable and happy by not updating objects when there kinetic energy level falls below a certain threshold....we meet some certain criteria so it wakes up under certain conditions.  So how do we determine the kinetic energy of an object?....well its really quiet smart.  The energy for kinetic energy is:

 

 

We have access to all the values to calculate the kinetic energy...but we can make it a lot more simple...as things like mass and the moment of inertia are constant!....also, the 0.5 is constant...so we can in effect simplify it down to:

 

 

This means that the code to caculate our rigid body motion simply becomes:

 

        float motion =  Dot(m_linVelocity, m_linVelocity) +

                        Dot(m_angVelocity, m_angVelocity);

 

 

 

But if you use this motion value as is, you'll find its still inefficient, because the linear/angular velocities can jump arround a lot and are quiet irratic at times due to the objecta all bouncing around like crazy beans.... so we'll include some linear interpolation between the current value and the next one...so depending upon some scalar value we pass in, depends how fast it converges to its new value....This is sometimes called RWA - Recently Weighted Average...but I usually just think of it as lerping.

 

            float motion =  Dot(m_linVelocity, m_linVelocity) +

                            Dot(m_angVelocity, m_angVelocity);

 

            float bias = 0.96f;

            m_rwaMotion = bias*m_rwaMotion + (1-bias)*motion;

 

The closer the bias is to 1.0f the slower it goes to the new value....so if its 1.0f, then it would never go to the new motion value and would remain at the old value...if we make it 0.0f, then it would immediately go the new value....  We call this function in our main update loop so its updated each frame.

 

Now to determine when our object sleeps or wakes up, we test the motion value against some test epsion value:

 

           if (m_rwaMotion < g_sleepEpsilon)

           {

              m_awake = false;

              m_linVelocity = D3DXVECTOR3(0,0,0);

              m_angVelocity = D3DXVECTOR3(0,0,0);

           }

           else if (m_rwaMotion > 10 * g_sleepEpsilon)

           {

              m_rwaMotion = 10 * g_sleepEpsilon;

              m_awake = true;

           }

 

If our motion is less than the epsilon value...I found 0.05 a good value...but a bit of trial and error is good.....once an object falls alseep...it takes a larger value to wake it up...so an object isn't sleeping and waking up all the time...so to fix this, a wake up value of 10 times sleepEpsilon is required.

 

Some additional rules need to be obeyed to keep the system working correctly as well:

 

  • The sleeping object must be in a collision with another sleeping object or in collision with an object with infinite mass.
  • If the sleeping object is in a collision with another object who's motion energy is over 2 times sleepEpsilon then our object must wake up.
  • The objects motion energy must be below sleepEpison to go to sleep. (Also it must remain below this threshold for a certain time)

 

As a side not, I've added in the demo whether we want to start our objects asleep or awake...for example if we start them awake, we start with an initial motion value greater than sleepEpsilon...then it would take 5-6 frames for the objects to settle down and go to sleep.  But I found if your doing walls and other stacked objects it was reasonable to assume the objects starting asleep, since there all resting on each other.....its a bit tricker if your objects start all mashed together in the sky...as you'd have to add an extra check to check if groups of sleeping objects are actually in contact with a infinite mass object...such as the ground.

 

   
Adding in sleeping, and a few more complicated test configurations, such as domino's and stack of tables and a some random cubes thrown around the place.

 

You can start with sleep enabled or disabled when the cubes start...has the added benefit of not having to wait for stacked cubes to settle.

 

So you can actually notice the objects go to sleep, I've done it so that for debug when an object goes to sleep, its alpha is set at 50%.

 

Using 20-30 boxes the frame rate remained pretty stable...even when the 20 objects where all asleep so wern't being updated, the limiting factor is the collision detection...so with a lot of collision detection optimisation you could dramatically increase the number of objects to the hundreds.

 

Download Source Code (31k)

 

Compile with Visual Studio 2005 and Directx9.1

 

 

 

 

 

 

Other things to add later on, which are for later tutorials are:

Contact point caching...so new contacts are added to a list, and we can keep track of how old/new a contact point is

Optimize the code for multiple iterations - certain things we do when we iterate over the impulse code can be taken out of the loop as there values remain the same.

Better Sleep code - hundreds or thousands of rigid bodies in a world, but only update those which aren't sleeping.

 

Further Reading

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved.
Designated tutorial and software are the property of their respective owners.