Box2D Flash Alchemy Port

jesses edited this page Nov 9, 2014 · 14 revisions

The Box2D Flash Alchemy Port is a 2D physics engine for AS3, built on the C++ Box2D engine compiled with Adobe Alchemy. It is highly recommended that you familiarize yourself with Box2D concepts by reading the Box2D C++ manual first. The core concepts are the same in the Flash Alchemy port; the only difference is the syntax. This page will only document what is different.

Starting a New Project

You will need to include the Box2DAS/Box2D.swc file and the Box2DAS package in your project. In Flash, open “Publish Settings”, then “ActionScript 3.0 Settings”. Under the “Source Path” tab, include the folder containing the Box2DAS package (Alternately, you can copy the Box2DAS package into the folder containing your project). Under the “Library Path” tab add the Box2DAS/Box2D.swc file.

Also under “Publish Settings” check the “Export SWC” checkbox. You do not need the generated SWC file, but Flash will not compile the project without this checked.

Call the initialize function in your document class or anywhere before using the library:

b2Base.initialize();

Contact Events

Telling Fixtures to Dispatch Events

In order to get a fixture to dispatch a contact event, you need to set the appropriate flag on the b2Fixture object:

myFixture.reportBeginContact = true;
myFixture.reportEndContact = true;
myFixture.reportPreSolve = true;
myFixture.reportPostSolve = true;

The reason for this is that there is some overhead to calling an AS3 function from C++, so only dispatching events for fixtures you are interested in is an optimization.

The Default b2ContactListener Implementation.

The default b2ContactListener is implemented using AS3’s native event system. So unless you build your own b2ContactListener class, you can just listen for contact events off of fixtures:

myFixture.addEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact);
myFixture.addEventListener(ContactEvent.END_CONTACT, handleEndContact);
myFixture.addEventListener(ContactEvent.PRE_SOLVE, handlePreSolve);
myFixture.addEventListener(ContactEvent.POST_SOLVE, handlePostSolve);

Normally you can get away with just listening for begin and end contact events, even if you need to process the contact each time step. Because the contact event keeps a reference to the b2Contact, it can access the updated contact information long after that contact has been dispatched. You don’t need to listen for pre or post solve to get updated contact information.

The b2ContactListener gets one callback when two fixtures collide. The default AS3 contact listener translates this single contact into 2 contact events and dispatches them off of each fixture. The advantage of this is that each contact event is biased towards the fixture it was dispatched from. The contact event normal will always face away from the fixture the event originates from and you don’t need to worry about “flipping” it based on the order of the fixtures in the contact (note this is not the case when accessing the raw b2Contact).

Contact Filtering

You can utilize the default C++ b2ContactFilter by setting the maskBits, categoryBits and groupIndex properties, but b2ContactFilter cannot be overridden on the AS3 side. This is because calling an AS3 function from within the time step for every broad phase collision would be an enormous bottleneck. If you need to override the b2ContactFilter, it must be done in C++ and recompiled using Alchemy.

V2, V3, etc. vs. b2Vec2, b2Vec3, etc.

Short Answer: Use V2, don’t use b2Vec2.

Long Answer: The b2Vec2 class is used to reference a C++ b2Vec2 vector. For example, when a AS3 b2BodyDef is created, it has a property called position, of type b2Vec2, which references the C++ position property of the C++ b2BodyDef which is also created. b2Vec2 takes a pointer as it’s only constructor parameter and expects that to point to a memory address of an already existing C++ b2Vec2 object.

So if you create a b2Vec2 and then try to manipulate it, you are writing to random C++ memory. Note it is fine to edit a b2Vec2:

myBodyDef.position.x = 123; 

This is allowed because the position property points to a valid C++ b2Vec2.

But what if you want to do complex AS3 vector operations that require the creation of new vector objects? That is where the V2 class comes in:

var vec1:V2 = new V2(10, 10); // Instantiate AS3 vector.
var vec2:V2 = myBodyDef.position.v2; // Read the C++ x, y into AS3.
vec1.add(vec2).normalize(); // Manipulate.
myBodyDef.position.v2 = vec1; // Write the x, y back to C++.

This is a nonsensical example but demonstrates A. Creating an AS3 vector object (new V2), B. Converting a b2Vec2 to a V2 object (via the .v2 getter), C. Manipulating vectors in AS3, and D. Setting the x and y values of a b2Vec2 to the values of a V2 (via the .v2= setter).

There are several other classes that use this same pattern. They are listed in the table below:

C++ Class Getter/Setter AS3 Class
b2Vec2 v2 V2
b2Vec3 v3 V3
b2Mat22 m22 M22
b2Mat33 m33 M33
b2Transform xf XF
b2AABB aabb AABB

Destroying Objects

AS3 objects are garbage collected. C++ objects must be explicitly deleted. Because of this, you may end up with memory leaks if your AS3 instances are garbage collected without ever deleting their corresponding C++ object.

All of the AS3 Box2D objects have a destroy() method that is used to delete and clean up their C++ equivalent. If you are deleting a b2World, bodies, fixtures, shapes and joints are automatically deleted. You do not need to worry about them, but you do need to call destroy() on the b2World.

Definition objects, such as b2BodyDef, b2FixtureDef, etc. are the most troublesome, because it is easy to use a definition and then forget about it. If you instantiate a definition, you must also call destroy() on it. Alternatively, you may also use the static definitions provided by the b2Def class. Example:

b2Def.body.type = b2Body.b2_dynamicBody;
b2Def.body.position.x = 300;
b2Def.body.position.y = 100;
b2Def.polygon.SetAsBox(50, 50);
b2Def.fixture.shape = b2Def.polygon;
b2Def.fixture.density = 1;
var b:b2Body = myWorld.CreateBody(b2Def.body);
b.CreateFixture(b2Def.fixture);

Definition objects can be reused. Reusing the static definition objects of b2Def means you never have to worry about destroying them.

Miscellaneous

Disabling Contacts

Contacts can be disabled from within a contact event listener via:

myContact.SetEnabled(false);

In the C++ version this must be done every time step. In the Alchemy Port, disabled contacts will remain disabled until explicitly re-enabled or until the AABB region of the shapes no longer overlap (the contact is destroyed).

Fixture Conveyor Belt Speed Property

Fixtures have a m_conveyorBeltSpeed property that, when set, will move touching objects along it as if it were a conveyor belt or tank treads.

Runtime Polygon Decomposition

Runtime polygon decomposition is provided by the b2PolygonShape.Decompose function:

var s:Vector.<b2PolygonShape> = b2PolygonShape.Decompose(Vector.<Number>(-hx, -hy,
	10, -10, 
	0, 0,
	10, 10,
	-10, 10,
	-10, -10
));

How Does it Work?

Adobe Alchemy

Check out the Adobe Alchemy Overview to learn what Adobe Alchemy is and how it works.

The Box2D SWC and The Box2DAS Package

It is important to understand there are two layers to the Box2D Flash Alchemy Port:

The Compiled C++ Box2D.swc: This is the C++ Box2D library compiled using Adobe Alchemy. A number of methods are exported to AS3 to provide a basic, bare-bones interface to this library.

The Box2DAS AS3 package: This is the AS3 library that provides a richer interface to the Box2D C++ library. It is designed to mimic the Box2D C++ API as much as possible so that documentation for the C++ version can be used as a guide for the AS3 version. In addition to calling methods exported in Box2D.swc, it also reads and writes directly to C++ memory.

When you create an AS3 Box2D object (b2BodyDef, b2ShapeDef, etc.), the constructor calls the corresponding exported C++ constructor method, which returns a pointer (an address in memory) to the object. Now you have both an AS3 and C++ instance. The AS3 instance is used to manipulate the inaccessible C++ instance, by either calling further exported C++ methods or reading and writing directly to memory based on the stored C++ pointer.

According to Alchemy’s documentation, switching back and forth between C++ code and AS3 code has a considerable overhead, however reading and writing directly to memory is relatively fast. This is why for simple operations an AS3 implementation is used rather than trying to call the C++ method. For example, b2Body::ApplyForce benefits from not having to call a C++ method.

Memory Manipulating Getters and Setters

This is an example property from b2Body:

public function get m_angularVelocity():Number { return mem._mrf(_ptr + 80); }
public function set m_angularVelocity(v:Number):void { mem._mwf(_ptr + 80, v); }

This getter / setter combo uses the pointer to the C++ b2Body (returned by the C++ constructor) plus the offset of the m_angularVelocity property (80 bytes) in the b2Body to read and write the value to C++. This code effectively changes the m_angularVelocity on both the AS3 and the C++ side:

myBody.m_angularVelocity = 20;

All AS3 Box2D objects with corresponding C++ objects have a number of these getters / setters for each of their properties. These getters / setters are generated by the “probe” project, which uses the C++ “offsetof” macro to determine the correct offset into memory to read / write.

Hacking the Source: Recompiling With Alchemy

Compiling Box2DAS/Box2D.swc

If you want to edit the engine’s internals, you’ll need to compile the C/C++ sources using Adobe Alchemy. You will need the following:

  • Flex SDK (at least v3.2)
  • Adobe Alchemy
  • This project (at least the Box2D and Box2DAS folders)

Set up Alchemy by following the instructions on Adobe’s site.
http://labs.adobe.com/wiki/index.php/Alchemy:Documentation:Getting_Started

Then, to compile Box2D, cd into the Box2DAS folder and type:

alc-on; g++ Box2D.c -I.. -O3 -Wall -swc -DOSX -o Box2D.swc; alc-off

Depending on your system you may need to drop the -DOSX flag:

alc-on; g++ Box2D.c -I.. -O3 -Wall -swc -o Box2D.swc; alc-off

Generating Getters / Setters For Added Properties

If you add new fields to the C++ side of Box2D, you will need to probe for memory locations to make the interactions between Alchemy / AS3 work. To do this, cd into the probe folder and type:

alc-on; g++ Box2D.c -I.. -O3 -Wall -swc -DOSX -o Box2D.swc; alc-off

Or:

alc-on; g++ Box2D.c -I.. -O3 -Wall -swc -o Box2D.swc; alc-off

This generates a box2d.swc that can be dropped in place of the normal one. Upon initialization, if you run in debug mode and look at your traces, you’ll get all of the memory locations neatly printed out for you.