After rewriting Chipmunk to Immediate Mode I have only come across two areas where you use temporal data -
cpSpaceHash and contact arrays. I feel that the IM solution to these problems are actually simpler than the RM solution. I've been working off an older version of Chipmunk so there might be other temporal areas that have been added later.
A couple of remarks about the code example: I like to rewrite code in order to understand what it does. Thus I use my own math library and Apache Portable Runtime for features like
Memory Pools,
Dynamic Arrays and
Hash Tables. Chipmunk names have been basterdized although should still be recognizable.
I hope that the intention behind IM Physics still are visible in my example. I have nothing against how the code was originally written and you should keep your style even if you choose to incoprate an Immediate Mode API into Chipmunk - this is just how I prefer to learn new libraries. This is also my first attempt at a IM version of Chipmunk - thus code samples may get simpler once I recognize more patterns.
Without further ado - let's have a go at a simple example. First let's create some static geometry - a floor and some walls:
Code: Select all
#define CELLDIM 48
#define SEG_RADIUS 2
static cpBody body_world;
static cpSegmentShape segment_world[3];
static void InitStaticObjects (void)
{
const vec2_t arr[]= {
{0, 0},
{0, 480},
{320, 480},
{320, 0},
};
cpBody* body= &body_world;
cpBodyInitStatic (body);
for (UINT i= 0; i < COUNT (segment_world); i++)
{
cpSegmentShapeInit (&segment_world[i], body, arr[i], arr[i + 1], SEG_RADIUS);
cpShape* shape= &segment_world[i].shape;
shape->e= 1.0f;
shape->u= 1.0f;
}
}
Nothing overly exciting - although notice that the static geometry are module variables (static) and not allocated on the heap. The fact that the physics library is now working on the user's data instead of a retained copy is one of the powers of a IM API.
Now we define some dynamic content:
Code: Select all
typedef struct
{
cpCircleShape shape;
cpBody body;
} ball_t;
static apr_array_header_t* arr_balls;
static void AddDynamicObject (void)
{
ball_t* self= apr_array_push (arr_balls);
const float radius= 15.0f;
const float mass = 10.0f;
cpBodyInit (&self->body, mass, cpMomentForCircle (mass, 0, radius, vec2_zero));
input_getfloatv (INPUT_MOUSE_POS, self->body.p);
cpCircleShapeInit (&self->shape, &self->body, radius, vec2_zero);
cpShape* shape= &self->shape.shape;
shape->e= 0.0f;
shape->u= 0.9f;
}
Notice I have chosen to add my physics objects to a user defined dynamic array - this should be fun when I later on add and remove balls from my array

The IM API does not care how I store the physics objects in memory - as long as I resubmit the objects I want processed by the physics library each frame.
Now we are ready to perform some physics:
Code: Select all
static BOOL IntegratePositionAndUpdateBBCache (ball_t* self, const float *dt)
{
cpBodyUpdatePosition (&self->body, *dt);
if (480 + 15 < self->body.p[1])
return FALSE;
cpShapeCacheBB (&self->shape.shape);
return TRUE;
}
...
array_filterself (arr_balls, IntegratePositionAndUpdateBBCache, &dt);
Ok - because we are coding
cpSpaceStep() ourselves we take advantage of what we know about our own data. We know each body only has one shape so we can merge the two functions
cpBodyUpdatePosition() and
cpShapeCacheBB().
Because this is a user written function we might as well use the moment to remove game objects that have died or are no longer interesting. Removing an object from the simulation is as easy as not submitting it to the physics library - no dual state in the game code and physics library need to be maintained.
Ready for some broad phase collision detection?
Code: Select all
static void FillStaticObjects (apr_hash_t* ht)
{
irect_t key;
for (UINT i= 0 ; i < COUNT (segment_world); i++)
{
cpShape* self= &segment_world[i].shape;
cpspace_hash_key (&key, &self->bb, CELLDIM);
cpspace_hash_put (ht, &key, self);
}
}
Beause this is Immediate Mode we don't store broadphase results between frames, instead we create a new
cpSpaceHash per frame, and fill it. We no longer have to rehash
cpSpaceHash for dynamic objects - it's just as fast to reinsert them into the hash table. This makes the implementation of
cpSpaceHash simpler - I'm actually just using a general Hash Table implementation myself,
apr_hash_t.
To achieve the effect of layers you can have a different
cpSpaceHash for each layer. Remember the user is in control of cpSpaceStep now - they can code the details for their specific scenario. It also makes it easier to extend Chipmunk with new functionality eg. a new broadphase test.
Code: Select all
static void CollideDynamicObject (ball_t* self, userdata_t* userdata)
{
cpShape* shape= &self->shape.shape;
apr_hash_t* ht= userdata->ht;
apr_array_header_t* arr= userdata->arr;
cpSpace* space= userdata->space;
//body need to be reassigned to shape because we store all our object in a dynamic array
shape->body= &self->body;
//clear the temporary array for new broadphase collisions
apr_array_clear (arr);
//retrieve a list of broadphase collisions for shape's bounding box
irect_t key;
cpspace_hash_key(&key, &shape->bb, CELLDIM);
cpspace_hash_get (ht, &key, arr);
//loop through all broadphase collisions
for (UINT i= 0; i < arr->nelts; i++)
{
//sort shapes so they are always processed in the same order
cpShape* a= ((cpShape**) arr->elts)[i];
cpShape* b= shape;
CPCOLLIDE_SWAP (&a, &b);
//retrieve list of contact points (narrow phase collision test)
UINT i= space->contactArray->nelts;
UINT n= cpCollideShapes (a, b, space->contactArray);
if (n == 0)
continue;
//store contact points for future processing
cpArbiter* arbiter= cpspace_newarbiter (space, a, b);
cpArbiterUpdate (arbiter, ((cpContact* ) space->contactArray->elts) + i, n);
}
//add current shape to broadphase
cpspace_hash_put (ht, &key, shape);
}
...
array_map (arr_balls, CollideDynamicObject, &userdata);
I hope the comments clearly explain the collision function for dynamic objects.
Notice the required fix of reassigning the body to a shape. This is required because I have chosen to store my objects in a dynamic array. Amazingly this is also the only fix required to store objects in a dynamic array - no other part of Chipmunk require permanent pointers between frames.
The rest of the code to update the physics objects are similar to what you already have in your
cpSpaceStep() function - the main difference being that the user is free to only call the functions he deem neccessary.
Code: Select all
static void frame_tick (void)
{
...
array_filterself (arr_balls, (array_filterfunc_t) IntegratePositionAndUpdateBBCache, &dt);
cpspace_begin (space);
apr_hash_t* ht= apr_hash_make (POOL_GETFRAME());
FillStaticObjects (ht);
userdata_t userdata;
userdata.ht= ht;
userdata.arr= apr_array_make (POOL_GETFRAME(), 8, sizeof (const void*));
userdata.space= space;
array_map (arr_balls, (array_mapfunc_t) CollideDynamicObject, &userdata);
const rect_t rect= {0, 0, 320, 480};
drawSpatialHash (ht, &rect, CELLDIM, buffer_solid);
ht= cpspace_getarbiters (space);
PrestepArbiters (ht, dt);
RunImpulseSolver (ht, DEFAULT_ELASTIC_ITERATIONS, 1);
array_map (arr_balls, (array_mapfunc_t) IntegrateVelocity, &dt);
ApplyCachedImpulse (ht);
RunImpulseSolver (ht, DEFAULT_ITERATIONS, 0);
array_map (arr_balls, draw_ball, NULL);
}
How does an IM API simplify Chipmunks structures and internals? Here's some examples:
Code: Select all
typedef struct cpBody
{
cpFloat m_inv, i_inv;
cpVect p, v, f; // Linear components of motion (position, velocity, and force)
cpFloat a, w, t; //Angular components of motion (angle, angular velocity, and torque)
cpVect rot; //Cached unit length vector representing the angle of the body.
//Internally Used Fields
//Velocity bias values used when solving penetrations and correcting joints.
cpVect v_bias;
cpFloat w_bias;
} cpBody;
struct cpShape
{
const cpShapeClass *klass; // The "class" of a shape as defined above
struct cpBody *body; //cpBody that the shape is attached to.
cpBB bb; // Cached BBox for the shape.
//Surface properties.
cpFloat e; // Coefficient of restitution. (elasticity)
cpFloat u; // Coefficient of friction.
cpVect surface_v; // Surface velocity used when solving for friction.
//Internally Used Fields
unsigned id; // Unique id used as the hash value.
};
#define CPCONTACT_PERSISTENCE 3
typedef struct cpSpace
{
struct apr_pool_t* pool;
struct apr_hash_t *contactSet[CPCONTACT_PERSISTENCE];
struct apr_array_header_t* contactArray;
} cpSpace;
Notice how simple all structures (not to mention the library code) have become because the user will write his own
cpSpaceStep() routine - and explicitly state what he wants done by the physics library.
Hopefully you can recognize how one would achieve some of the benefits I mentioned earlier with this example. I look forward to your questions.
[url=http://bit.ly/a8O5A9]The Monkey Hustle[/url] - Now available on the App Store!