Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Actionscript 3, Box2D and Flash.

The fourth part of this tutorial comes from Antoan Angelov and not only allows us to slice any kind of bitmap image mapped on our Box2D objects, but also improves the code making it faster and more robust.

This is the final result:

Use the mouse to slice objects.

And this is the fully commented and explained code:

package {
	import flash.display.*;
	import Box2D.Dynamics.*;
	import Box2D.Dynamics.Joints.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;

	public class main extends MovieClip {
/*
			Box2D body slicer, created by Antoan Angelov.
		*/

		var world:b2World;
		var tempBox:b2Body;
		var stageW:Number=stage.stageWidth,stageH:Number=stage.stageHeight;
		var cont:Sprite = new Sprite();
		var begX:Number,begY:Number,endX:Number,endY:Number;
		var polyShape:b2PolygonShape;
		var enterPointsVec:Vector. = new Vector.();
		var mouseReleased:Boolean=false;
		var objectsCont:Sprite = new Sprite();
		var laserCont:Sprite = new Sprite();
		var numEnterPoints:int=0,i:int;
		var boxDef:b2PolygonShape;
		var fixtureDef:b2FixtureDef,bodyDef:b2BodyDef,body:b2Body;
		var woodTexture:BitmapData,rockTexture:BitmapData;

		public function main() {
			// First, I create the textures, which are BitmapData objects.
			var tempSpr:Sprite;

			tempSpr = new texture1();
			woodTexture=new BitmapData(tempSpr.width,tempSpr.height);
			woodTexture.draw(tempSpr);

			tempSpr = new texture2();
			rockTexture=new BitmapData(tempSpr.width,tempSpr.height);
			rockTexture.draw(tempSpr);

			// World setup
			world=new b2World(new b2Vec2(0,10),true);

			// Making the ground, which is a static body, and then creating its Sprite equivalent.
			bodyDef = new b2BodyDef();
			bodyDef.type=b2Body.b2_staticBody;
			fixtureDef = new b2FixtureDef();
			fixtureDef.density=5;
			fixtureDef.friction=1;
			fixtureDef.restitution=0;
			boxDef = new b2PolygonShape();
			boxDef.SetAsBox((stageW)/30, 10/30);
			bodyDef.position.Set((0.5*stageW)/30, stageH/30);
			fixtureDef.shape=boxDef;
			tempBox=world.CreateBody(bodyDef);
			tempBox.CreateFixture(fixtureDef);

			tempSpr = new Sprite();
			tempSpr.graphics.lineStyle(2, 0x00FF00);
			tempSpr.graphics.beginFill(0x00FF00, 0.3);
			tempSpr.graphics.drawRect(-0.5*stageW, (stageH-10), 2*stageW, 20);
			tempSpr.graphics.endFill();
			addChild(tempSpr);

			// Initializing bodies that can be sliced.
			fixtureDef.density=5;
			fixtureDef.friction=0.2;
			fixtureDef.restitution=0;

			createBody((230)/30, 50/30, [new b2Vec2(-100/30, -75/30), new b2Vec2(100/30, -75/30), new b2Vec2(100/30, 75/30), new b2Vec2(-100/30, 75/30)], woodTexture);
			createBody((stageW-230)/30, 50/30, [new b2Vec2(3.0795984417042894, 1.275611441216966), new b2Vec2(1.2756114412169661, 3.0795984417042894), new b2Vec2(-1.275611441216966, 3.0795984417042894), new b2Vec2(-3.0795984417042894, 1.2756114412169663), new b2Vec2(-3.0795984417042894, -1.2756114412169657), new b2Vec2(-1.2756114412169677, -3.0795984417042885), new b2Vec2(1.2756114412169666, -3.079598441704289), new b2Vec2(3.0795984417042885, -1.275611441216968)], rockTexture);

			// You can see the reason for creating the enterPointsVec in the coments in the intersection() method.
			enterPointsVec=new Vector.(numEnterPoints);

			this.addChild(cont);
			cont.addChild(objectsCont);
			cont.addChild(laserCont);

			stage.addEventListener(MouseEvent.MOUSE_DOWN, mDown);
			addEventListener(Event.ENTER_FRAME, update);
		}

		private function createBody(xPos:Number, yPos:Number, verticesArr:Array, texture:BitmapData) {
			var vec:Vector.=Vector.(verticesArr);
			bodyDef = new b2BodyDef();
			bodyDef.type=b2Body.b2_dynamicBody;
			boxDef = new b2PolygonShape();
			boxDef.SetAsVector(vec);
			bodyDef.position.Set(xPos, yPos);
			// I use my own userData class to store the unique ID of each body, its vertices and its texture.
			bodyDef.userData=new userData(numEnterPoints,vec,texture);
			objectsCont.addChild(bodyDef.userData);
			fixtureDef.shape=boxDef;
			tempBox=world.CreateBody(bodyDef);
			tempBox.CreateFixture(fixtureDef);
			tempBox.SetBullet(true);
			numEnterPoints++;
		}

		private function mDown(e:MouseEvent) {
			begX=mouseX;
			begY=mouseY;

			stage.addEventListener(MouseEvent.MOUSE_UP, mUp);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, mMove);
		}

		private function mMove(e:MouseEvent) {
			laserCont.graphics.clear();
			laserCont.graphics.lineStyle(2);
			laserCont.graphics.moveTo(begX, begY);
			laserCont.graphics.lineTo(mouseX, mouseY);
		}

		private function mUp(e:MouseEvent) {
			mouseReleased=true;

			stage.removeEventListener(MouseEvent.MOUSE_UP, mUp);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE, mMove);
		}

		public function update(e:Event):void {
			if (mouseReleased) {
				// Here I use the world.RayCast() method (I use it twice, see why in the comments in the intersection() method below) to get the intersection points between the line you just drew and all bodies in the Box2D world.

				endX=mouseX;
				endY=mouseY;

				var p1:b2Vec2=new b2Vec2(begX/30,begY/30);
				var p2:b2Vec2=new b2Vec2(endX/30,endY/30);

				world.RayCast(intersection, p1, p2);
				world.RayCast(intersection, p2, p1);
				enterPointsVec=new Vector.(numEnterPoints);
				mouseReleased=false;
			}

			world.Step(1/24, 90, 90);
			world.ClearForces();

			var p:b2Body,spr:Sprite;

			// Here all the bodies' Sprite equivalents are synchronized to the bodies themselves.

			for (p = world.GetBodyList(); p; p = p.GetNext()) {
				spr=p.GetUserData();
				if (spr) {
					spr.x=p.GetPosition().x*30;
					spr.y=p.GetPosition().y*30;
					spr.rotation=p.GetAngle()*180/Math.PI;
				}
			}
		}

		private function intersection(fixture:b2Fixture, point:b2Vec2, normal:b2Vec2, fraction:Number):Number {
			var spr:Sprite=fixture.GetBody().GetUserData();

			// Throughout this whole code I use only one global vector, and that is enterPointsVec. Why do I need it you ask? 
			// Well, the problem is that the world.RayCast() method calls this function only when it sees that a given line gets into the body - it doesnt see when the line gets out of it.
			// I must have 2 intersection points with a body so that it can be sliced, thats why I use world.RayCast() again, but this time from B to A - that way the point, at which BA enters the body is the point at which AB leaves it!
			// For that reason, I use a vector enterPointsVec, where I store the points, at which AB enters the body. And later on, if I see that BA enters a body, which has been entered already by AB, I fire the splitObj() function!
			// I need a unique ID for each body, in order to know where its corresponding enter point is - I store that id in the userData of each body.

			if (spr is userData) {
				var userD:userData=spr as userData;

				if (enterPointsVec[userD.id]) {
					// If this body has already had an intersection point, then it now has two intersection points, thus it must be split in two - thats where the splitObj() method comes in.
					// Before calling splitObj() however, I first draw the two intersection points - the blue one is the enter point and the red one is the end point.
					laserCont.graphics.lineStyle(4, 0x0000FF);
					laserCont.graphics.drawCircle(enterPointsVec[userD.id].x*30, enterPointsVec[userD.id].y*30, 7);

					laserCont.graphics.lineStyle(4, 0xFF0000);
					laserCont.graphics.drawCircle(point.x*30, point.y*30, 7);

					splitObj(fixture.GetBody(), enterPointsVec[userD.id], point.Copy());
				} else {
					enterPointsVec[userD.id]=point;
				}

			}
			return 1;
		}

		private function splitObj(sliceBody:b2Body, A:b2Vec2, B:b2Vec2):void {
			var origFixture:b2Fixture=sliceBody.GetFixtureList();
			var poly:b2PolygonShape=origFixture.GetShape() as b2PolygonShape;
			var verticesVec:Vector.=poly.GetVertices(),numVertices:int=poly.GetVertexCount();
			var shape1Vertices:Vector. = new Vector.(), shape2Vertices:Vector. = new Vector.();
			var origUserData:userData=sliceBody.GetUserData(),origUserDataId:int=origUserData.id,d:Number;

			// First, I destroy the original body and remove its Sprite representation from the childlist.
			world.DestroyBody(sliceBody);
			objectsCont.removeChild(origUserData);

			// The world.RayCast() method returns points in world coordinates, so I use the b2Body.GetLocalPoint() to convert them to local coordinates.
			A=sliceBody.GetLocalPoint(A);
			B=sliceBody.GetLocalPoint(B);

			// I use shape1Vertices and shape2Vertices to store the vertices of the two new shapes that are about to be created. 
			// Since both point A and B are vertices of the two new shapes, I add them to both vectors.
			shape1Vertices.push(A, B);
			shape2Vertices.push(A, B);

			// I iterate over all vertices of the original body. 
			// I use the function det() ("det" stands for "determinant") to see on which side of AB each point is standing on. The parameters it needs are the coordinates of 3 points:
			// - if it returns a value >0, then the three points are in clockwise order (the point is under AB)
			// - if it returns a value =0, then the three points lie on the same line (the point is on AB)
			// - if it returns a value <0, then the three points are in counter-clockwise order (the point is above AB). 
			for (i=0; i0) {
					shape1Vertices.push(verticesVec[i]);
				} else {
					shape2Vertices.push(verticesVec[i]);
				}
			}

			// In order to be able to create the two new shapes, I need to have the vertices arranged in clockwise order.
			// I call my custom method, arrangeClockwise(), which takes as a parameter a vector, representing the coordinates of the shape's vertices and returns a new vector, with the same points arranged clockwise.
			shape1Vertices=arrangeClockwise(shape1Vertices);
			shape2Vertices=arrangeClockwise(shape2Vertices);

			// setting the properties of the two newly created shapes
			bodyDef = new b2BodyDef();
			bodyDef.type=b2Body.b2_dynamicBody;
			bodyDef.position.SetV(sliceBody.GetPosition());
			fixtureDef = new b2FixtureDef();
			fixtureDef.density=origFixture.GetDensity();
			fixtureDef.friction=origFixture.GetFriction();
			fixtureDef.restitution=origFixture.GetRestitution();

			// creating the first shape
			polyShape = new b2PolygonShape();
			polyShape.SetAsVector(shape1Vertices);
			fixtureDef.shape=polyShape;

			bodyDef.userData=new userData(origUserDataId,shape1Vertices,origUserData.texture);
			objectsCont.addChild(bodyDef.userData);
			enterPointsVec[origUserDataId]=null;

			body=world.CreateBody(bodyDef);
			body.SetAngle(sliceBody.GetAngle());
			body.CreateFixture(fixtureDef);
			body.SetBullet(true);

			// creating the second shape
			polyShape = new b2PolygonShape();
			polyShape.SetAsVector(shape2Vertices);
			fixtureDef.shape=polyShape;

			bodyDef.userData=new userData(numEnterPoints,shape2Vertices,origUserData.texture);
			objectsCont.addChild(bodyDef.userData);
			enterPointsVec.push(null);
			numEnterPoints++;

			body=world.CreateBody(bodyDef);
			body.SetAngle(sliceBody.GetAngle());
			body.CreateFixture(fixtureDef);
			body.SetBullet(true);
		}

		private function arrangeClockwise(vec:Vector.):Vector. {
			// The algorithm is simple: 
			// First, it arranges all given points in ascending order, according to their x-coordinate.
			// Secondly, it takes the leftmost and rightmost points (lets call them C and D), and creates tempVec, where the points arranged in clockwise order will be stored.
			// Then, it iterates over the vertices vector, and uses the det() method I talked about earlier. It starts putting the points above CD from the beginning of the vector, and the points below CD from the end of the vector. 
			// That was it!

			var n:int=vec.length,d:Number,i1:int=1,i2:int=n-1;
			var tempVec:Vector.=new Vector.(n),C:b2Vec2,D:b2Vec2;

			vec.sort(comp1);

			tempVec[0]=vec[0];
			C=vec[0];
			D=vec[n-1];

			for (i=1; ib.x) {
				return 1;
			} else if (a.x

userData class is as follows:

package 
{
	import Box2D.Common.Math.b2Vec2;
	import flash.display.Sprite;
	import flash.display.BitmapData;
	import flash.geom.Matrix;

	public class userData extends Sprite
	{
		var id:int, texture:BitmapData;

		public function userData(id:int, verticesVec:Vector., texture:BitmapData)
		{
			this.id = id;
			this.texture = texture;

			// I use the matrix so that I can have the center of the shape I'm drawing match the center of the BitmapData image - I "move" the BitmapData projection left by half its width and up by half its height.
			var m:Matrix = new Matrix();
			m.tx = -texture.width*0.5;
			m.ty = -texture.height*0.5;
			
			// I then draw lines from each vertex to the next, in clockwise order and use the beginBitmapFill() method to add the texture.
			this.graphics.lineStyle(2);
			this.graphics.beginBitmapFill(texture, m, true, true);
			this.graphics.moveTo(verticesVec[0].x*30, verticesVec[0].y*30);
			for (var i:int=1; i

The result is really amazing so I think we'll see some new game concepts using slicing.

Download the source code.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.