www.xbdev.net xbdev - software development
Friday December 14, 2018
home | about | contact | Donations

     
 

Game Physics Programming..

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

 

Collision Detection Using Minkowski Difference

by Ben Kenwright

 

The Minkowski Sum/Difference is a fast and relatively simple way of detecting whether two shapes interact.  In fact you might have already used it without realising it.  In essence it works by subtracting one object from another and then checking whether that new object contains the origin.  Of course its easier said than done subtracting one object from another. 

But we'll use basic shapes, such as spheres and cubes and use the technique of support mapping.

In not so many words, support mapping, is where we take a point inside an objects, for example the centre of mass.  Then we project a ray outwards, now that ray has got to hit a point on the surface of the shape, that point using the ray from the centre is support mapping.  Its easiest example is a sphere, if you take the centre of the sphere and shoot a ray out in any direction, you can easily work out point on the surface of the ray.

Using most shapes we can calculate the support mapping ponit from a ray from its origin using relatively simple maths.  For example the simplest and easiest is the sphere.  Support mapping point is:

 

Support Point P = Radius * NormalRay.

Simple fire a ray in any direction from our allocated centre and it returns the support point on the surface.

So for each shape we can get away with 2 simple functions.  GetCentre() and GetSupportPoint( Vector3 normalFromCentre ).  

To really push the theory home though we also have to do a simple demo to show that you can actually use the support mapping to generate the shapes surface.

Simple Demo [60k SourceCode & Executable ]

 

[ Visual Studio 2008 with C#]

[ XNA & Framework 3.1]

 

To try and show some examples of support mapping, I did a simple app which generates some common

shapes (ellipsoid, sphere, cube, plane etc) using the support mapping technique.  Its very quick and dirty, but its

very effective in showing its workings.

 

Basically starts off with a shape around the origin, and then keeps firing a ray in each direction till a tollerance has been

met.  Using the points its aquired from firing the ray it will build the shape triangles.

 

 

 

 

 

 

 

This gives us a good starting point - we now have a shape and a way of working out its surface point.  This will help us create a new shape using the Minkowski Difference.

 

 

One shape to Two....

 

Now we've got two shapes, and we can represent them both by there support surfaces.  So we need a reference centre for our new shape.  As long as its inside our new shape thats what matters.  So a good centre is the to the middle of both centre of masses of each object and do the mean between them.

 

 

Vector3 v01 = q1.Rotate( p1.GetCentre() ) + t1;

Vector3 v02 = q2.Rotate( p2.GetCentre()) + t2;

 

Vector3 v0 = v02 - v01;

 

Subtracting the support points of one shape form another and checking if the origin is inside the generated shape we can determine if our two shapes have intersected.

o If A and B are intersection (i.e. Minkowski Difference of A and B) then it contains the origin!

o The Minkowski Difference(A,B) = A.Support(n) - B.Support(n) (i.e. just subtract one support point from another using the same support normal)

 

Using some basic 3D math and the plane equation we can loop around the shapes and check if there hitting.

 

 

Demo:  [ 69k Executable & Source Code ]

Description: Move objects around using ASWD Keys and to trigger shape changing colour when the objects intersect.

 [Visual Studio 2008 with C#]

 [ XNA & Framework 3.1]

 

 

The crosses are some of the test points that it uses to generate the support difference of the 2 shapes before it finally comes to the result of whether there is or isn't a collision.  Not you could force a full support manafold to build the actual shape difference, but where interested in speed, and if we can determine in advance if there is or isn't a collision we return the value and break out.

 

 

 

Intersection Information (Normals, Hit Points, Penetration Depths)

Of course collision detection isn't really that useful, unless you can gather extra information, such as the normal, the actual collision points etc. 

 

 

Demo:  [ 48k Source Code ]

Description: Move objects around using ASWD Keys.  When the objects intersect it will show the intersection point and the normal with the shortest path to push the objects appart.

 [Visual Studio 2008 with C#]

 [ XNA & Framework 3.1]

 

 

 

 

 

Collision Manafold ("Contact Manifold")

In a lot of physics systems that use collision detection, in most cases the collision detection will return a single point which is the point that is the furthest penetration and also a normal which is the direction which is the shortest distance to take the shapes out of intersecting.

 

Of course if we have objects stacked or resting on each other we can have multiple collisions.  So what we end up doing is caching the previous few collision points and updating them to determine if there still valid, enabling us to build up a collision manifold which allows the shape to settle down.

 

 

 

 

 

 

Keeping track of the manifold can be done in different ways.  One way which is popular and you'll find lots of information on the net, is to use an Arbiter, which keeps track of which objects are intersecting with with other object and keeps a list of the collision points.  Then using the collision points:

o Age

o Amount of movement from when it was created

o Number of collision points, first in, last out (old ones get pushed out if we have to many)

 

 

o List Artbiters (ArbiterContainer)

o Arbiter - References 2 RigidBodies (uses either id or pointer to identify which rigidboides)

               - List contacts between the bodies

o Each frame we look for collisions between the rigidbodies.  If we have a collision we find the Arbiter between the 2 bodies.  If one doesn't exist its added.  Once we have the arbiter we add the contact to it.

If no contacts exist, its a new contact and its just added to the list of contacts.  (ie. its position, normal, and timestamp).  Else if other contacts exist, check that its not withing some threashold distance of it.  e.g. 0.2 squared, if so, we will replace it with the new collision point.  This is done by replacing the collision point and updating the contact normal and timestamp.

 

So when we add a new contact one of two things can happen, its either a new contact or an existing contact is updated.

 

Code Snippet: Arbiter

 

/*

      We have a single ArbiterContainer_c, which is just a static class array,

      and stores a list of ArbiterItems

      As new points of contact between the pair are discovered, they are added

      to the Arbiter with a call to AddContact().

*/

public class ArbiterContainer_c

{

      public class ArbiterItem_c

      {

            public ArbiterItem_c(ArbiterKey_c key, Arbiter_c arbiter)

            {

                  this.key          = key;

                  this.arbiter      = arbiter;

            }

            public ArbiterKey_c           key;

            public Arbiter_c        arbiter;

      };

      public static List<ArbiterItem_c> arbiterArray = new List<ArbiterItem_c>();

     

      public static

      Arbiter_c FindArbiter( ref RigidBody_c rb1, ref RigidBody_c rb2 )

      {

            ArbiterKey_c key = new ArbiterKey_c(ref rb1, ref rb2);

            for (int i=0; i<arbiterArray.Count; i++)

            {

                  if (arbiterArray[i].key.b1.id==key.b1.id &&

                        arbiterArray[i].key.b2.id==key.b2.id)

                  {

                        return arbiterArray[i].arbiter;

                  }

            }

           

            arbiterArray.Add( new ArbiterItem_c(key, new Arbiter_c(ref rb1, ref rb2)) );

            return arbiterArray[ arbiterArray.Count-1 ].arbiter;

      }

     

      static public

      void DebugDraw(GraphicsDevice graphicsDevice)

      {

            for (int i=0; i<arbiterArray.Count; i++)

            {

                  for (int k=0; k<arbiterArray[i].arbiter.contacts.Count; k++)

                  {

                        Vector3 n = arbiterArray[i].arbiter.contacts[k].normal;

                        Vector3 p = arbiterArray[i].arbiter.contacts[k].point;

                       

                        DrawLine_c.DrawLine( graphicsDevice, p, p + n*10.0f, Microsoft.Xna.Framework.Graphics.Color.Yellow);

                       

                        DrawLine_c.DrawCross( graphicsDevice, p, 4.0f );

                  }

            }

      }

};

/*

      Each Arbiter maintains a list of contacts between two RigidBody objects.

      As new points of contact between the pair are discovered, they are added

      to the Arbiter with a call to AddContact().

*/

public class Arbiter_c

{

      public      Arbiter_c(ref RigidBody_c in1, ref RigidBody_c in2)

      {

            b1 = in1;

            b2 = in2;

      }

     

      ~Arbiter_c()

      {

            contacts.Clear();

      }

      public void AddContact(Vector3 p1, Vector3 p2, Vector3 normal)

      {

            Vector3 point = (p1 + p2) * 0.5f;

           

            int foundContact = -1;

            for (int i=0; i<contacts.Count; i++)

            {

                  if ((contacts[i].point - point).LengthSquared() < 1.0f )

                  {

                        foundContact = i;

                        break;

                  }

            }

           

            if (foundContact<0)

            {

                  contacts.Add( new Contact_c(b1, b2, p1, p2, normal) );

            }

            else

            {

                  contacts[foundContact].point = point;

                  contacts[foundContact].Update(  p1,  p2,  normal );

            }

      }

      public RigidBody_c            b1;

      public RigidBody_c            b2;

      public List<Contact_c>  contacts    = new List<Contact_c>();

};

/*

      To find the Arbiter for two RigidBodies, build an ArbiterKey for the two

      bodies and use it as the lookup key.

*/

 

public class ArbiterKey_c

{

      public ArbiterKey_c(ref RigidBody_c b1, ref RigidBody_c b2)

      {

            if ( b1.id < b2.id )

            {

                  this.b1 = b1;

                  this.b2 = b2;

            }

            else

            {

                  this.b1 = b2;

                  this.b2 = b1;

            }

      }

      public RigidBody_c      b1;

      public RigidBody_c      b2;

};

 

Contact Manifolds Demo:

[ 55k Source Code ]

Description: Move objects around using ASWD Keys and to trigger shape changing colour when the objects intersect.

 [Visual Studio 2008 with C#]

 [ XNA & Framework 3.1]

 

Lets you add lots of test shapes and display various debug information - using this technique of caching the previous contact points, you can build up a contact manifold so the shape settle.  Cached contacts are updated and removed as time progresses.

 

 

 

 

So how many Cubes can we bounce around?  Well with un-optimised code and using C# we can probably get 50 cubes bouncing around easily with realistic physics. Of course as you pass 100 you'll notice the framerate really dropping, then you'll start to have to use some sort of world partitioning for your collision detection (i.e BSP and Octree's).  If your running your code in C/C++ you can take advantage of optimising some of the vector/matrix math to use SSE or maybe even just fast inline functions.

 

The screenshot shows 99 Blocks including the 5 walls which are also blocks, you can run the demo and add in various other shapes, such as balls, rugby's and tubes.  You can add hundreds of shapes and you'll get a stable simulation even though its frame rate drops drastically.  As long as you use a fixed time-step for your simulation step.

 

If your running it for a game you can use a fixed time-step with a catch-up loop to always maintain the same frame rate with the main game loop.

 

 

 

 

 

 

Notes:

If your using the method for a physics system, you can cache the collision points for dynamic contacts (Collisions) and using them for numerous iterations for the collision response.  But for the resting contacts (static contacts) you'll find that you need to update the collision data each iteration to solve the problem of jiggling.  Its a bit painful for performance but can be reduced if you use shock propagation for stacking and spatial partitioning so only objects within vicinity of each other need updating so its not so bad.s

 

 

Just for fun

Using the basic impulse physics principles, with a lot more iterations so that the rigidbodies actually settled and didn't jiggle around, I did a few samples that you can compile and run, ranging from stacks of blocks (using shock propogation), dominos, bowling and a simple marble slide.  Feel free to fiddle around with it, its a good sample for the collision detection code, letting you set forward/backwards frames to check things and also debug draw the collision points etc.

 

 

Fun Rigid Body Demo:

[ 69k  Source Code]

Adding various rigidbodies and showing there various collisions with crosses.  Also using simple impulses and to make the samples more fun, I've done some simple test configurations - dominos, sea-saws, walls, stack of blocks, marbles rolling down planks.s

 [Visual Studio 2008 with C#]

 [ XNA & Framework 3.1]

 

The sample sourcecode uses Seperated Velocity/Position updates to improve stacking and reduce jiggling.  We also update the collisions and contacts seperately.  And finally we use a shock propogation technique for updating contacts so where able to stack large numbers of rigidbodys.

 

Various Sample Screenshots:

 

 

 

 

Theres a ton of different papers on collision detection - its a fantastic subject and one which is constantly under research.  Either ways of speeding up existing algorithms, pushing them onto the gpu, or modified hybrid versions.  I'd definetly recomment do anyone if they had time to do some reading on it.

 

Physics!...well again another juicy topic, lots of maths and some very hard reading papers, but well worth it in the end when you see the results you can get - from buildings flying appart when a ball hits it, or just rain drops bouncing off the roof, theres endless things you can do...and you can always simpliy the math so you have a add-hok version that gives you visually pleasing results but isn't the full mathematical model.  As most physics simulations, especially realtime are making things as fast as possible and looking as good as possible, even if you've got to cut a few corners.

 

 

References:

[Introduction to GJK]s
http://www.cas.mcmaster.ca/~carette/SE3GB3/2006/notes/gjk1_pres.pdf

 

[Contact Detection Algorithms]

http://www.academypublisher.com/jcp/vol04/no10/jcp041010531063.pdf

 

[Nonconvex Rigid Bodies with Stacking]

http://physbam.stanford.edu/~fedkiw/papers/stanford2003-01.pdfs

 

[XenoCollide]

http://xenocollide.snethen.com/

 

 

 

 

s

 
 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.