Creating Complex Shapes in Box2D UPDATED

Creating complex shapes,aka compound shapes, in Box2d is accomplished by adding multiple shapes to a body. In exploring complex shapes I learned a bunch about defining objects in general. Again I couldn’t find any tutorials on this but I have been using the box2d manual. It helps but isn’t the best.

Before we start
When I started to experiment with box2d I discounted the importance/utility of the debug draw mode. It doesn’t really do anything other then show you what is in the box2d world, which when your understanding of positioning is still bad helps a ton. So turn it on, it’s magic. If you change the m_drawScale to 1 you can use pixels as your units. Include the following after you create the b2World.

m_sprite = new Sprite();
addChild(m_sprite);
var dbgDraw:b2DebugDraw = new b2DebugDraw();
var dbgSprite:Sprite = new Sprite();
m_sprite.addChild(dbgSprite);
dbgDraw.m_sprite = m_sprite;
dbgDraw.m_drawScale = 1;
dbgDraw.m_fillAlpha = 0.3;
dbgDraw.m_lineThickness = 1.0;
dbgDraw.m_drawFlags = b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit;
m_world.SetDebugDraw(dbgDraw);

Complex shapes need a body
Ok onto complex shapes. When creating any object in box2d you start by creating a b2BodyDef. The b2BodyDef defines the intial properties of a b2Body object. When you create a b2Body it will either be a static (not moving) or a dynamic (moving) object. It also provides the container for shape definitions and custom visuals . b2Body creation example:

//Create a new body definition without any shapes
var bodyDef:b2BodyDef = new b2BodyDef();
//Set its position in the world. Units in meters.
bodyDef.position.Set(150, 150);
//Create a dynamic body in the world from the bodyDef
var body:b2Body = m_world.CreateDynamicBody(bodyDef);

Creating shapes and adding them to the body
After creating the body, we need to define and add some shapes to it. There are 2 basic types of shapes, circles (b2CircleDef) and polygons (b2PolygonDef). Both of these inherit from the b2ShapeDef class which is where you will find most of the editable shape properties (friction, restitution, density, etc). When creating shapes everything is relative to a centered registration point. b2PolygonDef shapes can be defined either using SetAsBox, for squares, or via a vertex array. In the documentation it says to limit to 8 vertex points for any polygon and it has to be convex. Box2DFlash differs a little from the C++ version when creating square polygons. To position a square poly in the body you use SetAsOrientedBox instead of SetAsBox. SetAsOrientedBox takes two extra parameters for position (b2Vec2) and angle (in radians). If you create a shape using vertex points you don’t need to use SetAsBox or SetAsOrientedBox because you have defined all the points manually. Shape creation example:

//Create a single rectangular poylgon
var poly1:b2PolygonDef = new b2PolygonDef();
//Make a 120px by 50px poly that will be centered on the body centered and without any angle
poly1.SetAsOrientedBox(60, 30, new b2Vec2(0, 0), 0);
//Give it some friction. 0 being none 1 being a lot
poly1.friction = 0.3;
//Give it some density which will be used to calculate mass later
poly1.density = 1;
//Restitution is how elastic something is 0 being in elastic and 1 being totally elastic
poly1.restitution = .1;

Putting it all together
Combining the two objects is really easy. Call the CreateShape() method of the b2Body with the shape as an argument. If you are going to add more shapes to the body you can just call CreateShape() again. Example:

//Add the poly1 shape to the body
body.CreateShape(poly1);
//If you were going to add more its as simple as
//body.CreateShape(poly2)
//SetMassFromShapes is a great feature of Box2D. It will analyze your body and determine the mass and center of gravity from the shapes.
body.SetMassFromShapes();

Complete Example Source

/*
Original software provided by Erin Catto http://www.gphysics.com. Thanks Erin for all the hard work.
Also thanks to Matthew Bush for all the hard work in porting box2D to flash
Some of the code in this example is from the Box2D Test bed examples. Check them out they are really helpful.
*/
package {

	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.geom.*;

	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Dynamics.Joints.*;
	import Box2D.Dynamics.Contacts.*;
	import Box2D.Common.*;
	import Box2D.Common.Math.*;
	
	public class ComplexTest extends Sprite {
		private var m_world:b2World;
		private var m_iterations:int = 10;
		private var m_timeStep:Number = 1/30;
		private var m_sprite:Sprite;
		//Convert to rads from degrees
		private var dtor:Number = Math.PI/180;

		public function ComplexTest():void {
			this.addEventListener(Event.ENTER_FRAME, update, false, 0, true);
			// Creat world AABB
			var worldAABB:b2AABB = new b2AABB();
			worldAABB.lowerBound.Set(-3000, -3000);
			worldAABB.upperBound.Set(3000, 3000);

			// Define the gravity vector
			var gravity:b2Vec2 = new b2Vec2(0, 300);

			// Allow bodies to sleep
			var doSleep:Boolean = true;

			// Construct a world object
			m_world = new b2World(worldAABB, gravity, doSleep);
			m_sprite = new Sprite();
			addChild(m_sprite);
			var dbgDraw:b2DebugDraw = new b2DebugDraw();
			var dbgSprite:Sprite = new Sprite();
			m_sprite.addChild(dbgSprite);
			dbgDraw.m_sprite = m_sprite;
			dbgDraw.m_drawScale = 1;
			dbgDraw.m_fillAlpha = .3;
			dbgDraw.m_lineThickness = 1;
			dbgDraw.m_drawFlags = b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit;
			m_world.SetDebugDraw(dbgDraw);

			// Add ground body
			var ground:b2BodyDef = new b2BodyDef();
			ground.position.Set(0, stage.stageHeight);

			//Define a ground poly
			var groundPoly:b2PolygonDef = new b2PolygonDef();
			groundPoly.SetAsBox(1000, 90);
			groundPoly.friction = .3;

			var groundBody:b2Body = m_world.CreateStaticBody(ground);
			groundBody.CreateShape(groundPoly);
			groundBody.SetMassFromShapes();

			//Create a new body definition without any shapes
			var bodyDef:b2BodyDef = new b2BodyDef();
			//Set its position in the world. Units in meters.
			bodyDef.position.Set(150, 150);
			//Give a bit of an angle to show the object colliding
			bodyDef.angle = 15 * dtor;
			//Create a dynamic body in the world from the bodyDef
			var body:b2Body = m_world.CreateDynamicBody(bodyDef);


			//Create a single rectangular poylgon
			var poly1:b2PolygonDef = new b2PolygonDef();
			//Make a 4m by 2m poly that will be centered on the body centered and without any angle
			poly1.SetAsOrientedBox(60, 30, new b2Vec2(0, 0), 0);
			//Give it some friction. 0 being none 1 being a lot
			poly1.friction = .3;
			//Give it some density which will be used to calculate mass later
			poly1.density = 1;
			//Restitution is how elastic something is 0 being in elastic and 1 being totally elastic
			poly1.restitution = .1;

			//Create a second poly
			var poly2:b2PolygonDef = new b2PolygonDef();
			poly2.SetAsOrientedBox(30, 60, new b2Vec2(30, 0), 0);
			poly2.friction = .3;
			poly2.density = 1;
			poly2.restitution = .1;

			body.CreateShape(poly1);
			body.CreateShape(poly2);
			body.SetMassFromShapes();
		}
		
		private function update(e:Event):void {
			m_world.Step(m_timeStep, m_iterations);
			// Go through body list and update sprite positions/rotations
			var bb:b2Body = m_world.m_bodyList;
			for (bb; bb; bb = bb.m_next) {

				if (bb.m_userData is Sprite) {
					bb.m_userData.x = bb.GetPosition().x;
					bb.m_userData.y = bb.GetPosition().y;
					bb.m_userData.rotation = bb.GetAngle() * (180/Math.PI);
				}
			}
		}
	}
}

7 Responses to “Creating Complex Shapes in Box2D UPDATED”

  1. Eric Smith Says:

    Thanks for your tips. Box2D help is a gold mine, especially coming from APE.

    Curious, what are you using dbgSprite for? You add it to the m_sprite, then never touch it again…

  2. BigP Says:

    Hmm good point, I’ve noticed that too. I’ve modified the example and used it right against the Document Class sprite instead and worked fine!

    I’m guessing he actually wanted to assign it to the dbgDraw.m_sprite property… then again that’s like… two sub-child Sprites…. an explanation would of been helpful for sure.

    But this is definitely a great beginner Tutorial for Box2D users, and for APE users transitioning to Box2D.

  3. Brettf Says:

    I was lazy and didn’t optimize the code. It doesn’t do anything at the moment but you could feed it into the dbgDraw object as the debug draw area. This way you can separate it from things inside m_sprite. Good eyes guys.

  4. ben Says:

    Hi and Congrats for your work. I’m new to Box2D but found it very cool but not always efficient for my as scripter reflex so the first thing I’ve done yesterday after playing with this wonderfull API was to create a
    getBodyByName() method to allow easier way to get a body than iterating trough the famous list.

    As you seems an old user would you be interested to have a look and tell me …
    whatever …
    thanks

  5. wouter Says:

    hello,

    I have spent some time recently implementing a very simple bare bones editor which spits out xml to create complex shapes in box2d.

    in this version you can grab the xml from the [process xml] button

    might be usefull to someone.

    http://www.nuvorm.nl/start/index.php?option=com_content&view=category&layout=blog&id=40&Itemid=53

  6. linto Says:

    How to do the same in the latest box2d api? I couldn’t accomplish this with new api. Could anybody help me to do the same in new api.
    Thanks a lot,
    LINTO

  7. Jacob Says:

    You can also check out my box2d editor. It has a live preview. exports as3 xml and does tons of stuff. http://www.jacobschatz.com/bisonkick
    Currently implementing joints into it. It uses 2.1a so @linto you should check that out.