Immediate Mode Physics

Official forum for the Chipmunk2D Physics Library.
Post Reply
User avatar
mariocaprino
Posts: 4
Joined: Mon Feb 28, 2011 4:25 am
Location: Norway
Contact:

Immediate Mode Physics

Post by mariocaprino »

I have in the recent years been intrigued by the power of Immediate Mode APIs for our game projects. The advantages of Immediate Mode APIs (IM) over Retained Mode (RM) are already heavily appreciated in graphics APIs like OpenGL and DirectX.

It was first when I stumbled across Immediate Mode GUI I came to realize that this pattern could be extended to other fields of programming - and provide the same benefits.

When I wanted to add some physic effects to our latest project I was amazed that all physics libraries I came across used a RM API. Being a stubborn programmer (what game programmer isn't) I spent some weeks conforming the Chipmunk API to be IM - and I am very pleased with my initial results.

So what are the advantages I see in my fork compared to Chipmunk's current RM API:

Pros
  • No redundant states: No need to add and remove objects from cpSpace to make sure the physics library is in the same state as your game code - if you don't want to test against a object that frame, don't add it to your tests that frame.
  • No need to create the whole physics world - only provide the physics objects required for that one frame. This enables you to stream physics data and minimize data redundancy - create the physics object once it's actually needed.
  • You implement cpSpaceStep yourself - no need for cpBody's callbacks or cpShapes flags: If you need a different update function for cpBody that is the update function you call. If you only want to test collision between certain object's that's the only collision tests you call.
  • You may optimize the use of physics beyond what the library writer forsee - you are always the best suited to evaluate what physics code you want called
  • Chipmunk components become more reusable: cpSpaceHash, cpBody, cpShape, cpCollision are even less dependent on cpSpace. Start using physics code where you ordinary wouldn't because it is lightweight - use only the components you need for the task at hand.
  • You are inn full controll of all memory allocated: No need for userdata fields - it's you memory. If you need additional fields just add cpShape to your own structure along with your other members.
Cons
  • You implement cpSpaceStep() yourself
I don't really consider the con a disadvantage as it's rather simple to create a wrapper around the IM API to provide a backward compatible RM API. It's going the other way, providing a IM API on top of a RM API that is hard.

I realize that my current test examples are not extensive and that there might be edge cases I currently do not support - and that's why I want to propose the idea of IM Physics to this board. If you are new to the idea of an IM API this post may read like ramblings. Hopefully though I have listed some pros that got you interested in the idea - and curious enough to ask questions.

Maybe an Immediate Mode API could be in the future of Chipmunk?
[url=http://bit.ly/a8O5A9]The Monkey Hustle[/url] - Now available on the App Store!
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Immediate Mode Physics

Post by slembcke »

Hmm. Sounds interesting, but I'm not immediately sure (pun sort of intended) what an immediate mode API would look like for physics. Care to share some examples?

The big reason for retained mode APIs is performance. While DirectX and OpenGL do support immediate modes of operation, they are generally a fraction of the speed of using retained mode APIs. In the physics world, a lot of the algorithms used rely strongly on temporal coherence as well, meaning that the physics system needs to be able to identify objects as being the same from frame to frame. That's pretty hard to do with an immediate mode API. A lot of games don't really require that sort of performance though, so this might be an interesting idea to investigate.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
User avatar
mariocaprino
Posts: 4
Joined: Mon Feb 28, 2011 4:25 am
Location: Norway
Contact:

Re: Immediate Mode Physics

Post by mariocaprino »

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.
Last edited by mariocaprino on Fri Mar 04, 2011 5:22 am, edited 3 times in total.
[url=http://bit.ly/a8O5A9]The Monkey Hustle[/url] - Now available on the App Store!
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Immediate Mode Physics

Post by slembcke »

I'm packing up to go to GDC this afternoon, so I'm not sure when I get a chance to take a closer look at this. Skimming over it it looks interesting though.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
User avatar
mariocaprino
Posts: 4
Joined: Mon Feb 28, 2011 4:25 am
Location: Norway
Contact:

Re: Immediate Mode Physics

Post by mariocaprino »

I had a quick look through the latest Chipmunk and saw that you have added support for sleeping bodies in later versions. As I test the reality of an Immediate Mode Physics API for Chipmunk I decided to add sleeping to my API.

Looking over the implementation I believe sleeping bodies are currently maintaind with an undirected graph of contacts between all sleeping bodies - so I started by creating a graph structure. My code is only 60 lines due to the fact that I use a general purpose Hash Table to store the structure - and it does not require any additional members in cpBody. That's a good start and I now have a general solution - I am able to activate a whole sleeping contact group if an active shape collides with any of them.

Then I thought about the problem further and came to realize we can do better - this is an Immediate Mode API! If I order the sequence I add my objects to the Physics library that frame I can avoid maintaining a graph structure of all potential sleeping contacts. Here's how:
  1. start with an empty cpSpaceHash each frame
  2. fill cpSpaceHash with all static and sleeping shapes
  3. for each dynamic object perform a broad, then narrow test for collision
  4. add the collision pair to our list of arbiters
  5. if we collide against a sleeping shape, activate it and recursively call step 3 with the sleeping object
The idea is simple and yes - it works! The power comes from the fact that we we do not retain shapes - they have to be resubmitted every frame if they wish to be processed by the Physics library. There is no problem if you don't want to submit the objects ordered - you can always use the general solution - but the optimization possibility exisits and it's the library user's choice what path he'd like to take.

I personally think an Immediate Mode API is easier to work with and gives the library user usage paths that would not be possible in Retained Mode. My own experience is that it also greatly simplifies the library implementation because there is no state to maintain any more. Hopefully I am able to convey the power and potential of an Immediate Mode Physics API.
[url=http://bit.ly/a8O5A9]The Monkey Hustle[/url] - Now available on the App Store!
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Immediate Mode Physics

Post by slembcke »

How is the performance though? Using your immediate mode method, each frame you are reindexing all of your static and sleeping data. The entire point of sleeping and static bodies in Chipmunk is that they have a nearly zero CPU cost. If you have to insert them into the hash each frame it causes each one to have a pretty high CPU cost.

I'm still a little skeptical of immediate mode as being a catch all as well. My experience with immediate mode GUIs is that it makes simple GUIs easier to make and complex GUIs way harder to make because it forces you to write code that stores all the GUI's state. I guess I see the same problem here. While it gives you a little more flexibility to make specific optimizations, it also requires you to manage all of the state for the entire physics simulation and write the code that performs the simulation. Both are things that people don't really seem to want to do.

I think there is room for both styles of API, and they could share a lot of the same code. I can't see myself using an immediate mode physics API very much though.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
User avatar
mariocaprino
Posts: 4
Joined: Mon Feb 28, 2011 4:25 am
Location: Norway
Contact:

Re: Immediate Mode Physics

Post by mariocaprino »

slembcke wrote:How is the performance though? Using your immediate mode method, each frame you are reindexing all of your static and sleeping data. The entire point of sleeping and static bodies in Chipmunk is that they have a nearly zero CPU cost. If you have to insert them into the hash each frame it causes each one to have a pretty high CPU cost.
Filling the broad phase collision structure each frame is not free - filling cpSpaceHash with 600 cpCircleShapes consumes 7% of cpSpaceStep. It does not mean that it's impossible to achieve the "zero" CPU cost for static and sleeping objects - in an IM API temporal optimizations will require the user to store these cached structures.
slembcke wrote:I'm still a little skeptical of immediate mode as being a catch all as well. My experience with immediate mode GUIs is that it makes simple GUIs easier to make and complex GUIs way harder to make because it forces you to write code that stores all the GUI's state. I guess I see the same problem here. While it gives you a little more flexibility to make specific optimizations, it also requires you to manage all of the state for the entire physics simulation and write the code that performs the simulation. Both are things that people don't really seem to want to do.
This might come down to personal preference - my main motivation for pursuing an Immediate Mode Physics API is ease of use and extensibility. The black box model of having a monolithic library controlled update function prevents ease of customization. I consider the many flags, variables and callbacks used for input and output from cpSpaceStep "dirty hacks".

Personally I feel a RM API also make a library harder to learn: cpSpaceStep() becomes a magic function! It's great until you need to have physics and game objects talk together - at that point you need to dive head long into how cpSpaceStep works to achieve your desired effect. An Immediate Mode API will let the user gradually learn to use the physics library based on his needs. The basic IM Physics update loop is small and simple - and can be extended based on need.

To me it's obvious how you would achieve a desired effect with an Immediate Mode API: in an IM API you explicitly state what you want done.
In Chipmunk's RM API you would need to learn about all the various callbacks, flags and variables and be able to view how to achieve your effect within the restriction of these tools.
slembcke wrote:I think there is room for both styles of API, and they could share a lot of the same code. I can't see myself using an immediate mode physics API very much though.
If you were to add a an Immediate Mode API I would definitely keep the Retained Mode API. The RM API can be easily layered on top of the IM API - and would achieve the same performance as today's Chipmunk. The disadvantage as a library maintainer would be that you now have two public APIs - you would have to decide if you think the advantages are worth the maintenance cost.
[url=http://bit.ly/a8O5A9]The Monkey Hustle[/url] - Now available on the App Store!
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Immediate Mode Physics

Post by slembcke »

Well, it's something I'll definitely consider for future versions I guess. It would have to come after Chipmunk 6 though as it's almost done.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
mobilebros
Posts: 90
Joined: Tue Aug 04, 2009 9:53 am
Contact:

Re: Immediate Mode Physics

Post by mobilebros »

While it gives you a little more flexibility to make specific optimizations, it also requires you to manage all of the state for the entire physics simulation and write the code that performs the simulation. Both are things that people don't really seem to want to do.
I think you said it well slembcke, an immediate mode seems very specific to the case of optimizing and certainly from my standpoint does not look very easy to learn. cpSpaceStep as a "magical" function is not necessarily a bad thing... I just want my shapes/bodies to simulate!

However, there are certainly cases I can think of were optimizing the heck out of chipmunk (for a specific) purpose is desirable... so maybe like mariocaprino stated, wrapping a RM API around an IM API is the way to go.
User avatar
slembcke
Site Admin
Posts: 4166
Joined: Tue Aug 14, 2007 7:13 pm
Contact:

Re: Immediate Mode Physics

Post by slembcke »

From what I remember, the guys that wrote Zombie Smash wrote their own step function for performance reasons. They wanted to simulate a couple dozen ragdolls on the screen and using groups to keep ragdoll parts from self colliding was causing a lot of false-positives in the broadphase collision detection. This is honestly the only other time I can think of somebody that felt they needed (or wanted to) change cpSpaceStep(). Like mobilebros said, most people just seem to want a magic function that makes the physics work correctly. Chipmunk is open source, so the people that do want to learn how it actually works and fiddle with that stuff can.

Another good example of specific optimizations would be "bullet hell" games where you have hundreds of bullets on the screen, but only a small number of objects that they collide with. Using a broadphase here can easily be more expensive than just doing a brute force check of the bullets against a relatively small number of collidable objects. In this case though I've always just told people to go with 10 lines of for loops and bounding box checks. Using a physics engine for this sort of thing is pointlessly adding complexity and will just give you worse performance.

Don't get me wrong. I'm not saying an immediate mode API is a bad, but my experience is that most people don't really want to know how a physics engine works in that close of detail, and just want it to work. Even with immediate mode APIs like OpenGLs, you don't have to worry about clipping, rasterizing, blending, etc. OpenGL holds a lot of state, and the immediate mode really only applies to submitting geometry and it's moving quickly away from that in favor of shaders. To make an immediate mode physics API accessible, I kind of think a similar approach would have to be taken.
Can't sleep... Chipmunks will eat me...
Check out our latest projects! -> http://howlingmoonsoftware.com/wordpress/
Post Reply

Who is online

Users browsing this forum: No registered users and 6 guests