Saturday, February 26, 2011

Adapting to Farseer Physics Engine's Meter-Kilogram-Second (MKS) system of units

Recently switched to version 3.x of Farseer Physics Engine and have been experiencing slow simulations since? Can't get anything to accelerate past a certain point because everything looks like it's floating in a damn aquarium? Are you frustrated and tearing your hair out because you think this new version "sucks" !? Don't worry, you're not alone.

Now, take a deep breath and carry on reading...(tl;dr: demo)

Starting with 3.x, FPE now uses the MKS (Meter-Kilogram-Second) system of units and this has been the source of confusion for both people who were working with version < 3 and also people who were new to FPE.

Note: Other people have already written about this topic but I feel that, since this has been a major source of confusion for many, it hasn't been given the attention it requires.

To begin with, let's be naive and try to create a Body like such:

float width = 200f, 
      height = 100f,
      density = 10f;
Vector2 position = new Vector2(300, 400);

Body body = BodyFactory.CreateRectangle(world, width, height, density, position);

In the Draw method, I (naively) draw the texture at the body's position as follows:

spriteBatch.Draw(texture, body.Position, Color.White);

What I'm doing above (or what at least I think I'm doing) is create a 200x100 pixel rectangular body positioned at (200, 300) pixels on the screen and then drawing it at wherever the body is at the current moment, starting from our initial (300, 400).

But because now Farseer Physics Engine uses the MKS system of units, what we are actually doing is creating a 200m wide and a 100m high rectangle rectangle positioned 200 meters from the right and 100 meters from the top! And Box2D (since Farseer is based on Box2D) tells us that we should keep our bodies (especially if they move) in the 0.1 - 10 meter range.

This means that we now have to split the way we position physics objects on screen and the way we draw their corresponding textures.

To accomplish this, we can use the ConvertUnits class found under Samples\Samples XNA\Samples XNA\ScreenSystem.

ConvertUnits is a helper class that lets us switch between display units (pixels) and simulation units (MKS) easily.

So let's go back to that previous example and fix our code by first converting our display units to simulation units with ConvertUnits.ToSimUnits:

float width = ConvertUnits.ToSimUnits(200f), 
      height = ConvertUnits.ToSimUnits(100f),
      density = 10f;
Vector2 position = ConvertUnits.ToSimUnits(300, 400); // ToSimUnits has an overload which returns a vector given two floats.

Body body = BodyFactory.CreateRectangle(world, width, height, density, position);

Now to draw our body on screen, we need to convert the simulation units back to display units by the appropriately named method ConvertUnits.ToDisplayUnits:

spriteBatch.Draw(texture, ConvertUnits.ToDisplayUnits(body.Position), Color.White);

Still can't get it? No worries, because I created the simplest Farseer Physics 3 example you can find which demonstrates what I said above:

The example creates a rectangle and lets gravity do its part; that's it! I've compiled it for both the Windows and the Windows Phone 7 XNA versions.

Another note: Since FPE is currently undergoing the upgrade to 3.3, the above code example can break if used with a different version of the engine. I've compiled the example with the version from Changeset #85352.