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);
}
}
}
}
}
September 11th, 2008 at 10:41 am
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…
September 18th, 2008 at 5:13 pm
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.
September 19th, 2008 at 5:17 pm
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.
April 5th, 2009 at 6:10 am
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