How to Simulate Realistic Air Friction in Box2D: Starling Version

There might be a case where you desperately need a quite realistic model of air friction in your game or simulation. Air friction, like another friction, reduces the speed of objects moving in it. Box2D provides a way to cheaply do this using set damping methods (linear and angular). These methods might be quite useful if you just want to show the slowing effect, but It’s not really near accurate to to the correct model of air friction.

Air friction is quite complex to simulate. The mathematical model itself depends on many variables, including object’s geometry (which affects their Reynolds Number). When an object move with relatively low velocity so no turbulence occurs, we can model the drag force as

F=-kv

For relatively higher speed (Reynolds Number > 1000), we can model the force as:

F=-k\rho Av^2

We’ll use this later model for a ball moving in air. So, enough math.

Preparation

First, after you setup your starling correctly (there are lot of tutorial about this, just Google it), create a Box2D world:

package  
{
    // Import all needed stuff
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Collision.Shapes.b2PolygonShape;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2FixtureDef;
    import Box2D.Dynamics.b2World;
    import starling.display.DisplayObject;
    import starling.display.Image;
    import starling.display.Quad;
    import starling.display.Sprite;
    import starling.events.EnterFrameEvent;
    import starling.textures.Texture;    
    /**
     * ...
     * @author Imran
     */
    public class Game extends Sprite 
    {
        // I embed a  ball image to represent our ball body
        [Embed(source = "../bin/sprites/ball.png")]
        public static const Ball:Class;
        
        // Declare needed vars
        private var world:b2World;
        private var timestep:Number = 1/30;
        private var ratio:Number = 30;
        private var theBall:b2Body;
        private var density:Number = 0.1; // kg/m2
        private var ballWidth:Number = 50;    
        
        public function Game() 
        {
            super();
            
            // Create a b2World with gravity 9.8 towards y axis. 
            world = new b2World(new b2Vec2(0, 9.8), true);
            
            // Listen enterframe event and update world for each enter frame
            addEventListener(EnterFrameEvent.ENTER_FRAME, updateWorld);
        }
        
        private function updateWorld(e:EnterFrameEvent):void 
        {
            world.Step(timestep, 10, 10);
            world.ClearForces();
        }
        
    }
}

I also added enter frame listener to update the world in each enter frame in the event handler. Now that we have fully working Box2D world, we’re ready to make objects. I’ll create the floor first. To make it cleaner, let’s create it in a new function floor();

private function floor(pX:Number, pY:Number, width:Number, height:Number):void 
{
    var shape:b2PolygonShape = new b2PolygonShape();
    shape.SetAsBox(width / 2 / ratio, height / 2 / ratio);
    var fixDef:b2FixtureDef = new b2FixtureDef();
    fixDef.shape = shape;
    fixDef.friction = 0.2;
    fixDef.restitution = 0.7;
    var bodyDef:b2BodyDef = new b2BodyDef();
    bodyDef.position.Set(pX / ratio, pY / ratio);
    bodyDef.type = 0;
    var quad:Quad = new Quad(width, height, 0xFFFFFF);
    quad.pivotX = quad.width / 2.0;
    quad.pivotY = quad.height / 2.0;
    bodyDef.userData = new Object();
    bodyDef.userData.view = quad;
    bodyDef.userData.type = "wall";
    addChild(bodyDef.userData.view);
    var body:b2Body = world.CreateBody(bodyDef);
    body.CreateFixture(fixDef);
} 

While we’re at it, let’s create a ball function to create a ball that we’ll manipulate.

public function ball(pX:Number, pY:Number, radius:Number, texture:Texture):b2Body {
    var shape:b2CircleShape = new b2CircleShape(radius / ratio);
    var fixtureDef:b2FixtureDef = new b2FixtureDef();
    fixtureDef.shape = shape;
    fixtureDef.friction = 0.3;
    fixtureDef.restitution = 0.7;
    fixtureDef.density = density;
    var bodyDef:b2BodyDef = new b2BodyDef();
    bodyDef.position.Set(pX/ratio, pY/ratio);
    bodyDef.type = 2;
    bodyDef.bullet = true;
    var image:Image = new Image(texture);
    image.pivotX = image.width / 2;
    image.pivotY = image.height / 2;
    bodyDef.userData = new Object();
    bodyDef.userData.view = image;
    bodyDef.userData.view.width = radius*2;
    bodyDef.userData.view.height = radius*2;
    
    addChild(bodyDef.userData.view);
    
    var body:b2Body = world.CreateBody(bodyDef);
    body.CreateFixture(fixtureDef);
    
    return body;
} 

Time to make our physics bodies. Change the constructor like this:

public function Game() 
{
    super();
    
    world = new b2World(new b2Vec2(0, 9.8), true);
    
    floor(400,590,800,20);
    theBall = ball(100,400,ballWidth/2,Texture.fromBitmap(new Ball()));
    // I also set the velocity of the ball
    theBall.SetLinearVelocity(new b2Vec2(8, -8));
    
    addEventListener(EnterFrameEvent.ENTER_FRAME, updateWorld);
}

And finally, change the updateWorld function to skin our bodies:

private function updateWorld(e:EnterFrameEvent):void 
{
    world.Step(timestep, 10, 10);
    world.ClearForces();
    
    for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext()) {                
        
        if (bb.GetUserData() is Object) {
            var sprite:DisplayObject = bb.GetUserData().view;
            sprite.x = bb.GetPosition().x * ratio;
            sprite.y = bb.GetPosition().y * ratio;
            sprite.rotation = bb.GetAngle();    
        } else 
        {
            world.DestroyBody(bb);
        }    
    }
}

Now we have a ball falling launched and followed parabolic trajectory, and it’s skinned. Preparation complete!

Applying Air Friction

We’ll apply air drag/friction using the model above. Air drag is affected by object’s density, area, and speed. To sum it up, the drag force is :

F = – constant * density * (width*width) * (v*v)

If we apply this force to the ball in each time step, it’ll simulate air friction. So, let’s do it! Change the updateWorld function like this:

private function updateWorld(e:EnterFrameEvent):void 
{
    world.Step(timestep, 10, 10);
    world.ClearForces();
    
    for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext()) {                
        
        if (bb.GetUserData() is Object) {
            var sprite:DisplayObject = bb.GetUserData().view;
            sprite.x = bb.GetPosition().x * ratio;
            sprite.y = bb.GetPosition().y * ratio;
            sprite.rotation = bb.GetAngle();    
        } else 
        {
            world.DestroyBody(bb);
        }
    }
    
    var k:Number = 1e-4 * density * ballWidth * ballWidth;
    var velX:Number = theBall.GetLinearVelocity().x;
    var velY:Number = theBall.GetLinearVelocity().y;
    // Apply drag force
    var dragForce:b2Vec2= new b2Vec2(-k*theBall.GetLinearVelocity().Length()*velX, -k*theBall.GetLinearVelocity().Length()*velY);
    theBall.ApplyForce(dragForce, theBall.GetWorldCenter());
}

I’ll show you through what we have change in updateWorld function. First, we find the value of constant part of the equation.

var k:Number = 1e-4 * density * ballWidth * ballWidth;

All constants are merged to the k variable. I multiplied them by 1e-4 to compensate ballWidth’s conversion from pixel to meter, and for nice adjustment.

Next, we find the x and y component of ball’s velocity vector using

var velX:Number = theBall.GetLinearVelocity().x;
var velY:Number = theBall.GetLinearVelocity().y;

So we can now count the force vector:

var dragForce:b2Vec2= new b2Vec2(-k*theBall.GetLinearVelocity().Length()*velX, -k*theBall.GetLinearVelocity().Length()*velY);

and then apply it to the ball:

theBall.ApplyForce(dragForce, theBall.GetWorldCenter());

Publish the movie and see what happens. If you do this correctly, you can see that the ball’s trajectory isn’t parabolic anymore (which should happen if there were no air drag). Good luck ^_^


Imran
A compassionate AS3 developer for simulation and games.
I love comments, leave one ^_^


Advertisements
This entry was posted in Simulation and Games. Bookmark the permalink.

3 Responses to How to Simulate Realistic Air Friction in Box2D: Starling Version

  1. .. Great tutorial, is there a way to show the Box2D debugger, i’ve added tha Flash Sprite layer on top of Starling’s, but still can’t see the Debugger !

    • imranedu says:

      When using starling, it’s a bit tricky to use b2DebugDraw because it require a flash display object as you already know. You could override that class and make new class that works with starling.

      • .. Thank you for your reply, i managed to get it to work using the following debug function , i find your tutorial very useful, if you have any other Starling/Box2D examples or Straling/Spine(animation) that’ll be great, once again Thank You.

        public function debugDraw(debugSprite:flash.display.Sprite):void{
        var debugDraw:b2DebugDraw = new b2DebugDraw();
        debugDraw.SetSprite(Starling.current.nativeOverlay);
        debugDraw.SetDrawScale(30);
        debugDraw.SetLineThickness( 1.0);
        debugDraw.SetAlpha(1);
        debugDraw.SetFillAlpha(0.4);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
        world.SetDebugDraw(debugDraw);
        }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s