Skip to content

Instantly share code, notes, and snippets.

@lamberta
Created April 16, 2010 05:06
Show Gist options
  • Save lamberta/368032 to your computer and use it in GitHub Desktop.
Save lamberta/368032 to your computer and use it in GitHub Desktop.
PaperBox2D.as demo
/* scenes/Box2dScene.as
*/
package scenes {
import Box2D.Collision.Shapes.b2PolygonDef;
import Box2D.Collision.b2AABB;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.Joints.b2MouseJoint;
import Box2D.Dynamics.Joints.b2MouseJointDef;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2DebugDraw;
import Box2D.Dynamics.b2World;
import flash.display.Sprite;
import org.papervision3d.objects.DisplayObject3D;
public class Box2dScene extends Sprite {
private var worldWidth:Number;
private var worldHeight:Number;
private var iterations:uint;
private var timestep:Number;
private var worldScale:uint;
private var world:b2World;
private var mouseXWorldPhys:Number;
private var mouseYWorldPhys:Number;
private var mouseXWorld:Number;
private var mouseYWorld:Number;
private var mouseJoint:b2MouseJoint;
private var _isMouseDown:Boolean = false;
private var draggedBody:b2Body;
private var box2dBodies:Array;
private var _arrayPos:int = -1;
public function Box2dScene(w:Number, h:Number, iterate:uint) {
initVars(w, h, iterate);
initB2World();
createB2Walls();
}
private function initVars(w:Number, h:Number, iterate:uint):void {
worldWidth = w;
worldHeight = h;
iterations = iterate;
timestep = 1/30;
worldScale = 50;
}
/**
* Initialize the box2d world.
*/
private function initB2World():void {
var worldBounds:b2AABB = new b2AABB();
worldBounds.lowerBound.Set(0, 0);
worldBounds.upperBound.Set(worldWidth/worldScale, worldHeight/worldScale);
var gravity:b2Vec2 = new b2Vec2(0, 10);
var sleepMode:Boolean = true;
world = new b2World(worldBounds, gravity, sleepMode);
}
/**
* Create boundries around the stage with a border of boxes..
*/
private function createB2Walls():void {
var wallShapeDef:b2PolygonDef = new b2PolygonDef();
var wallBodyDef:b2BodyDef = new b2BodyDef();
var wall:b2Body;
//Left and Right Shape Definition
wallShapeDef.SetAsBox(100/worldScale, (worldHeight + 40)/worldScale/2);
// Left
wallBodyDef.position.Set(-95/worldScale, worldHeight/worldScale/2);
wall = world.CreateBody(wallBodyDef);
wall.CreateShape(wallShapeDef);
// Right
wallBodyDef.position.Set((worldWidth+95)/worldScale, worldHeight/worldScale/2);
wall = world.CreateBody(wallBodyDef);
wall.CreateShape(wallShapeDef);
//Top and bottom shape definition
wallShapeDef.SetAsBox((worldWidth+40)/worldScale/2, 100/worldScale);
// Top
wallBodyDef.position.Set(worldWidth/worldScale/2, -95/worldScale);
wall = world.CreateBody(wallBodyDef);
wall.CreateShape(wallShapeDef);
// Bottom
wallBodyDef.position.Set(worldWidth/worldScale/2, (worldHeight+95)/worldScale);
wall = world.CreateBody(wallBodyDef);
wall.CreateShape(wallShapeDef);
wall.SetMassFromShapes();
}
/**
* Create box2d bodies from passed pv3d objects.
*/
public function createBodiesFrom3dObjects(pv3dObjects:Array):void {
box2dBodies = new Array();
for(var i:uint; i < pv3dObjects.length; i++) {
var do3d:DisplayObject3D = pv3dObjects[i] as DisplayObject3D;
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.allowSleep = true;
//align box2d shapes to pv3d object (0,0) in middle of 3d
bodyDef.position = new b2Vec2((do3d.x + worldWidth/2) / worldScale,
(do3d.y + worldHeight/2) / worldScale);
var width:Number = do3d.extra.width;
var height:Number = do3d.extra.height;
var boxShape:b2PolygonDef = new b2PolygonDef();
boxShape.SetAsBox(width/worldScale, height/worldScale);
boxShape.density = .7;
boxShape.friction = 6;
boxShape.restitution = .1;
var body:b2Body = world.CreateBody(bodyDef);
body.CreateShape(boxShape);
body.SetUserData(do3d);
body.SetMassFromShapes();
box2dBodies.push(body);
}
}
/**
* Step through Box2D simulation.
* Called from render-loop.
*/
public function step():void {
updateMouseWorld();
mouseDrag();
world.Step(timestep, iterations);
//move the assigned pv3d object from the new box2d position
for(var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext()) {
if(bb.GetUserData() is DisplayObject3D) {
bb.GetUserData().x = bb.GetPosition().x * worldScale - worldWidth / 2;
bb.GetUserData().y = -bb.GetPosition().y * worldScale + worldHeight / 2;
bb.GetUserData().rotationZ = -bb.GetAngle() * (180 / Math.PI);
}
}
}
private function updateMouseWorld():void {
mouseXWorld = mouseX;
mouseYWorld = mouseY;
mouseXWorldPhys = mouseX/worldScale;
mouseYWorldPhys = mouseY/worldScale;
}
private function mouseDrag():void{
// mouse press
if(_isMouseDown && !mouseJoint){
draggedBody = null;
if(_arrayPos > -1){
//negative number means nothing selected
draggedBody = box2dBodies[_arrayPos];
}
if(draggedBody) {
var md:b2MouseJointDef = new b2MouseJointDef();
md.body1 = world.GetGroundBody();
md.body2 = draggedBody;
md.target.Set(mouseXWorldPhys, mouseYWorldPhys);
md.maxForce = 300.0 * draggedBody.GetMass();
md.timeStep = timestep;
mouseJoint = world.CreateJoint(md) as b2MouseJoint;
draggedBody.WakeUp();
}
}
//mouse release
if(!_isMouseDown){
if(mouseJoint){
world.DestroyJoint(mouseJoint);
mouseJoint = null;
}
}
//mouse move
if(mouseJoint){
var p2:b2Vec2 = new b2Vec2(mouseXWorldPhys, mouseYWorldPhys);
mouseJoint.SetTarget(p2);
}
}
/**
* getter's and setter's
**/
public function get isMouseDown():Boolean {
return _isMouseDown;
}
public function set isMouseDown(mousedown:Boolean):void {
_isMouseDown = mousedown;
}
public function set arrayPos(i:int):void {
_arrayPos = i;
}
public function get debugDrawSprite():Sprite {
var debugDraw:b2DebugDraw = new b2DebugDraw();
debugDraw.SetSprite(new Sprite());
/** debug draw flags:
* DebugDraw.e_aabbBit, DebugDraw.e_centerOfMassBit, DebugDraw.e_coreShapeBit,
* DebugDraw.e_obbBit, DebugDraw.e_pairBit, b2DebugDraw.e_shapeBit,
* b2DebugDraw.e_jointBit
**/
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
debugDraw.SetDrawScale(worldScale);
debugDraw.SetFillAlpha(0.25);
debugDraw.SetLineThickness(1);
world.SetDebugDraw(debugDraw);
return debugDraw.GetSprite();
}
}
}
/**
* Papervision3D + Box2D Demo.
* http://lamberta.posterous.com/papervision3d-box2d
*
* Compiled with:
* Papervision3D Public Beta 2.0, build 804.
* Box2DAS3 2.0.1, build 23.
*
* This software is released under the MIT License.
* Copyright (c) Billy Lamberta 2008, <www.lamberta.org>
*/
package {
import com.adobe.viewsource.ViewSource;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import scenes.Box2dScene;
import scenes.Pv3dScene;
import scenes.events.Pv3dArrayEvent;
[SWF(backgroundColor="0x666666", framerate="30")]
public class PaperBox2D extends Sprite {
private var stageWidth:Number;
private var stageHeight:Number;
private var pv3dScene:Pv3dScene;
private var box2dScene:Box2dScene;
private static const BOX2D_ITERATIONS:uint = 10; //simulation quality
private var box2dDebug:Sprite;
public function PaperBox2D() {
stageSetup();
init3d();
initBox2d();
//add render-loop
this.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
setupText();
}
/**
* Setup stage.
*/
private function stageSetup():void {
stageWidth = stage.stageWidth;
stageHeight = stage.stageHeight;
stage.quality = StageQuality.MEDIUM;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.addEventListener(KeyboardEvent.KEY_DOWN, debugDraw, false, 0, true);
ViewSource.addMenuItem(this, "srcview/index.html");
}
/**
* Papervision3D setup.
*/
private function init3d():void {
//setup pv3d
pv3dScene = new Pv3dScene(stageWidth, stageHeight);
pv3dScene.addEventListener(Event.ADDED_TO_STAGE, setupPv3dEvents, false, 0, true);
this.addChild(pv3dScene);
}
private function setupPv3dEvents(event:Event):void {
//dispatched internally
pv3dScene.addEventListener(Pv3dArrayEvent.OBJECT_PRESS, on3dObjPress);
}
/**
* Box2D setup.
*/
private function initBox2d():void {
//setup box2d
box2dScene = new Box2dScene(stageWidth, stageHeight, BOX2D_ITERATIONS);
box2dScene.addEventListener(Event.ADDED_TO_STAGE, setupBox2dEvents, false, 0, true);
this.addChild(box2dScene);
//creates box2d bodies based on dimensions of the pv3d objects
box2dScene.createBodiesFrom3dObjects(pv3dScene.createShapes());
//do once to get pv3d objects in place
box2dScene.step();
//debug draw
box2dDebug = box2dScene.debugDrawSprite;
box2dDebug.visible = false;
addChild(box2dDebug);
}
private function setupBox2dEvents(event:Event):void {
//attach mouse listeners to stage
this.stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown, false, 0, true);
this.stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp, false, 0, true);
}
/**
* Mouse handlers for object selection.
*/
private function on3dObjPress(event:Pv3dArrayEvent):void {
//set the box2d array position so it knows what shape to move
box2dScene.arrayPos = event.arrayPos;
}
private function onMouseDown(event:MouseEvent):void {
box2dScene.isMouseDown = true;
}
private function onMouseUp(event:MouseEvent):void {
box2dScene.isMouseDown = false;
box2dScene.arrayPos = -1; //reset body selected
}
/**
* Toggle box2d debug draw.
*/
private function debugDraw(event:KeyboardEvent):void {
//listen for 'd'
if(event.keyCode == 68) {
if(box2dDebug.visible) {
box2dDebug.visible = false;
}else {
box2dDebug.visible = true;
}
}
}
/**
* render-loop.
*/
private function onEnterFrame(event:Event):void {
pv3dScene.updateCamera();
pv3dScene.render();
box2dScene.step();
}
private function setupText():void {
var text:TextField = new TextField();
text.autoSize = TextFieldAutoSize.LEFT;
text.multiline = true;
text.textColor = 0xFFFFFF;
text.x = 20;
text.y = 10;
text.htmlText = "Papervision3D + Box2D." +
"<br><a href='http://www.lamberta.org/blog/PaperBox2D'>www.lamberta.org/blog/paperbox2d</a>" +
"<br><br>Press 'd' to toggle the Box2D debug regions." +
"<br>Right-click to view source.";
this.addChild(text);
}
}
}
/* scenes/events/Pv3dArrayEvent.as
*/
/**
* Broadcasts an object's position in an array.
**/
package scenes.events {
import flash.events.Event;
public class Pv3dArrayEvent extends Event {
public static const OBJECT_PRESS:String = "onObjectPress";
public static const OBJECT_RELEASE:String = "onObjectRelease";
//object position in the array, negative for nothing selected
private var _arrayPos:int;
public function Pv3dArrayEvent(type:String, arrayPos:int) {
this._arrayPos = arrayPos;
super(type);
}
public function get arrayPos():int {
return _arrayPos;
}
}
}
/* scenes/Pv3dScene.as
*/
package scenes {
import flash.display.Sprite;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.lights.PointLight3D;
import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.Viewport3D;
import scenes.events.Pv3dArrayEvent;
public class Pv3dScene extends Sprite {
private var stageWidth:Number;
private var stageHeight:Number;
//pv3d vars
private var viewport:Viewport3D;
private var scene:Scene3D;
private var renderer:BasicRenderEngine;
private var camera:Camera3D;
public function Pv3dScene(w:Number, h:Number) {
initStage(w, h);
init3d();
setupScene();
}
private function initStage(w:Number, h:Number):void {
stageWidth = w;
stageHeight = h;
}
private function init3d():void {
viewport = new Viewport3D(stageWidth, stageHeight, true, true);
this.addChild(viewport);
renderer = new BasicRenderEngine();
scene = new Scene3D();
camera = new Camera3D();
}
private function setupScene():void {
//setup camera start settings
camera.zoom = 1000 / camera.focus + 1;
//camera target
var cameraTarget:DisplayObject3D = new DisplayObject3D();
camera.target = cameraTarget;
}
/**
* called from above.
* creates shape dimensions from pv3d objects to pass on to box2d.
*/
public function createShapes():Array {
var shapesDimensions:Array = new Array()
addCubeShapes(shapesDimensions);
return shapesDimensions;
}
private function addCubeShapes(shapesDimensions:Array, boxes:uint = 4):void {
var width:Number = 50;
var height:Number = 50;
var depth:Number = 50;
//flatshade light
var light:PointLight3D = new PointLight3D(true);
light.z = -100;
//create materials for boxes
for(var i:uint = 0; i < boxes; i++) {
//material
var randomColor:uint = Math.random() * 0xFFFFFF;
var flatShadeMaterial:FlatShadeMaterial = new FlatShadeMaterial(light, randomColor);;
flatShadeMaterial.interactive = true;
var matList:MaterialsList = new MaterialsList();;
matList.addMaterial(flatShadeMaterial, "all");
var cube:Cube = new Cube(matList, width * 2, depth * 2, height * 2, 2, 2, 2);
//store extras we will access in the box2dscene
cube.extra = {width:width, height:height, arrayPos:i};
//will position obects in pv3d, box2d will follow
cube.x = (i * 150) - 200;
cube.y = 150; //move down a bit, y is reversed because of box2d
//add listener to pv3d object
cube.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, on3dObjPress, false, 0, true);
//add to pv3d
scene.addChild(cube);
//push into array that will go to box2d
shapesDimensions.push(cube);
}
}
/**
* Added to 3d object, broadcasts it's array position on click.
*/
private function on3dObjPress(event:InteractiveScene3DEvent):void {
this.dispatchEvent(new Pv3dArrayEvent(Pv3dArrayEvent.OBJECT_PRESS, event.target.extra.arrayPos));
}
/**
* called from render-loop.
*/
public function render():void {
renderer.renderScene(scene, camera, viewport);
}
/**
* Camera hovering.
*/
public function updateCamera():void {
camera.x -= (camera.x - viewport.containerSprite.mouseX * 5) / 200;
camera.y -= (camera.y - viewport.containerSprite.mouseY * 5) / 200;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment