JigLibFlash + Papervision3d : Quick run-through

April 3rd, 2009

Hi all:

So last night I got some time to set up a quick and dirty example demonstrating how easy it is to implement physics in 3D using the new “plugin” classes created by Bartek who, if I do say so myself, has done a beautiful job setting up the plugin system for both Papervision3D and Away3D [EDIT : he also set up a really cool post over in the project wiki!]. Check this out (use your arrow keys to move the sphere around):

And here is the code for it:

package
{
    import flash.display.Bitmap;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.ui.Keyboard;
   
    import jiglib.geometry.JSphere;
    import jiglib.math.JNumber3D;
    import jiglib.physics.RigidBody;
    import jiglib.plugin.papervision3d.Papervision3DPhysics;
    import jiglib.plugin.papervision3d.Pv3dMesh;
   
    import org.papervision3d.cameras.CameraType;
    import org.papervision3d.cameras.SpringCamera3D;
    import org.papervision3d.core.math.Number3D;
    import org.papervision3d.lights.PointLight3D;
    import org.papervision3d.materials.BitmapMaterial;
    import org.papervision3d.materials.WireframeMaterial;
    import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
    import org.papervision3d.materials.utils.MaterialsList;
    import org.papervision3d.objects.DisplayObject3D;
    import org.papervision3d.objects.primitives.Plane;
    import org.papervision3d.objects.primitives.Sphere;
    import org.papervision3d.view.BasicView;
    import org.papervision3d.view.layer.util.ViewportLayerSortMode;
   
    /**
     * Simple Papervision3D + JigLibFlash example
     * @author Reynaldo a.k.a. reyco1
     *
     */

    [SWF(width="900", height="700", backgroundColor="#000000", frameRate="60")]
    public class RandomObjects extends BasicView
    {
        [Embed(source="assets/earthTexture512x256.jpg")]
        public var EarthTexture:Class;
       
        private var physics:Papervision3DPhysics;
        private var sphereObject:Sphere;
        private var physicsObject:RigidBody;       
        private var keyRight:Boolean = false;
        private var keyLeft:Boolean = false;
        private var keyForward:Boolean = false;
        private var keyReverse:Boolean = false;
        private var keyUp:Boolean = false;     
        private var moveForce:Number = 10;     
        private var springCamera:SpringCamera3D;
        private var cameraTarget:DisplayObject3D;
        private var sceneLight:PointLight3D;
       
        public function RandomObjects()
        {
            super(stage.stageWidth, stage.stageHeight, true, false, CameraType.TARGET);
            viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
            physics = new Papervision3DPhysics(scene, 1);
                       
            addKeyboardListeners();
            setupLighting();
            createFloor();
            setCamera();
            createSphere();
            createBoxes();     
            startRendering();
        }
       
        private function addKeyboardListeners():void
        {
            stage.addEventListener( KeyboardEvent.KEY_DOWN, keyDownHandler );
            stage.addEventListener( KeyboardEvent.KEY_UP, keyUpHandler );
        }
       
        private function setupLighting():void
        {
            sceneLight = new PointLight3D(true, true);
            sceneLight.x = 0;
            sceneLight.y = 400;
            sceneLight.z = -300;
        }
       
        private function setCamera():void
        {
            springCamera = new SpringCamera3D();
            springCamera.mass = 10;
            springCamera.damping = 10;
            springCamera.stiffness = 1;
               
            springCamera.lookOffset = new Number3D(0, 20, 30);
            springCamera.positionOffset = new Number3D(0, 100, -1500);
           
            springCamera.focus = 100;
            springCamera.zoom = 10;
        }
       
        private function createSphere():void
        {
            var earthMaterial:BitmapMaterial = new BitmapMaterial(Bitmap(new EarthTexture()).bitmapData, true);
            earthMaterial.tiled = true;
            earthMaterial.smooth = true;
           
            sphereObject = new Sphere(earthMaterial, 100, 13, 11);
            scene.addChild(sphereObject);
           
            physicsObject = new JSphere(new Pv3dMesh(sphereObject), 100);
            physicsObject.y = 200;
            physicsObject.restitution = 3;
            physicsObject.mass = 1
            physics.addBody(physicsObject);
           
            cameraTarget = new DisplayObject3D();
            cameraTarget.copyPosition(sphereObject);
            scene.addChild(cameraTarget);
           
            springCamera.target = cameraTarget;
        }
       
        private function createBoxes():void
        {
            var randomBox:RigidBody;
            var material:MaterialsList = new MaterialsList();
            material.addMaterial(new FlatShadeMaterial(sceneLight, 0x77ee77), "all");
            //material.addMaterial(new ColorMaterial(0x77ee77), "all");
           
            for(var a:Number = 0; a<10; a++)
            {
                randomBox = physics.createCube(material, 100, 100, 100);
                randomBox.z = 1000;
                randomBox.y = a*100 + 55;
                randomBox.mass = .2;
            }
        }
       
        private function createFloor():void
        {
            physics.createGround(new WireframeMaterial(0xFFFFFF, 0), 1800, 0);
           
            var floor:Plane = new Plane(new WireframeMaterial(0xFFFFFF), 10000, 10000, 10000*0.001, 10000*0.001);
            floor.rotationX = 90;
            floor.y = -150
            scene.addChild(floor);
           
           /*  var floorViewportLayer:ViewportLayer = new ViewportLayer(viewport, floor);
            floorViewportLayer.addDisplayObject3D(floor, true);
            floorViewportLayer.layerIndex = 1;
           
            viewport.containerSprite.addLayer(floorViewportLayer); */

        }
       
        private function keyDownHandler(event:KeyboardEvent):void
        {
            switch(event.keyCode)
            {
                case Keyboard.UP:
                    keyForward = true;
                    keyReverse = false;
                    break;
   
                case Keyboard.DOWN:
                    keyReverse = true;
                    keyForward = false;
                    break;
   
                case Keyboard.LEFT:
                    keyLeft = true;
                    keyRight = false;
                    break;
   
                case Keyboard.RIGHT:
                    keyRight = true;
                    keyLeft = false;
                    break;
                case Keyboard.SPACE:
                    keyUp = true;
                    break;
            }
        }
       
        private function keyUpHandler(event:KeyboardEvent):void
        {
            switch(event.keyCode)
            {
                case Keyboard.UP:
                    keyForward = false;
                    break;
   
                case Keyboard.DOWN:
                    keyReverse = false;
                    break;
   
                case Keyboard.LEFT:
                    keyLeft = false;
                    break;
   
                case Keyboard.RIGHT:
                    keyRight = false;
                    break;
                case Keyboard.SPACE:
                    keyUp=false;
            }
        }
       
        override protected function onRenderTick(event:Event = null):void
        {
            if(keyLeft)
            {
                physicsObject.addWorldForce(new JNumber3D(-moveForce, 0, 0), physicsObject.currentState.position);
            }
            if(keyRight)
            {
                physicsObject.addWorldForce(new JNumber3D(moveForce, 0, 0), physicsObject.currentState.position);
            }
            if(keyForward)
            {
                physicsObject.addWorldForce(new JNumber3D(0, 0, moveForce), physicsObject.currentState.position);
            }
            if(keyReverse)
            {
                physicsObject.addWorldForce(new JNumber3D(0, 0, -moveForce), physicsObject.currentState.position);
            }
            if(keyUp)
            {
                physicsObject.addWorldForce(new JNumber3D(0, moveForce, 0), physicsObject.currentState.position);
            }
           
            cameraTarget.copyPosition(sphereObject);
           
            physics.step();
            renderer.renderScene(scene, springCamera, viewport);
        }
       
    }
}

Stripping out all the usual stuff from the code above, these are snippets that integrate the physics:

First thing we need to is set up and instance of the physics plugin

physics = new Papervision3DPhysics();

Now, there are two ways to apply physics to your 3d objects using the plugin system. In the example, I use both. The “manual” approach can be seen in the createSphere method:

First the Papervision3D sphere is created and added to the scene

var earthMaterial:BitmapMaterial = new BitmapMaterial(Bitmap(new EarthTexture()).bitmapData, true);
earthMaterial.tiled = true;
earthMaterial.smooth = true;
           
sphereObject = new Sphere(earthMaterial, 100, 13, 11);
scene.addChild(sphereObject);

Then we create a physics sphere and add it to the physics engine:

physicsObject = new JSphere(new Pv3dMesh(sphereObject), 100);
physicsObject.y = 200;
physicsObject.restitution = 3;
physicsObject.mass = 1;
physics.addBody(physicsObject);

That’s basically it. You can go through all those steps (which aren’t many actually) or you can use the shortcut method which I used for creating the cubes in the createBoxes method:

randomBox = physics.createCube(material, 100, 100, 100);
randomBox.z = 1000;
randomBox.y = a*100 + 55;
randomBox.mass = .2;

Basically, both the 3d object and the physics object are created by the engine. Just pass the same arguments you would pass to the 3d Object class, in this case a Cube.

You can access the DisplayObject3D class anytime by using the following method from the plugin

physics.getMesh(randomBox)

Applying forces to your 3D/physics objects is pretty simple too. I’ll go over it more in detail in a later post, but this is the line that makes it happen:

physicsObject.addWorldForce(new JNumber3D(moveForce, 0, 0), physicsObject.currentState.position);

Where the first parameter is a vector or JNumber indicating in what direction the force should be applied and the second parameter is the current position of the physics object to which the force is being applied to.

I recommend you guys jump into the JigLibFlash repo and start exploring. There’ll be more examples to come soon!

JigLibFlash, Lab, papervision3d

Box2Dwrapper – More Examples with Joints

April 1st, 2009

Hi!

Here are a few examples on how to set up joints using Box2Dwrapper. More to come! Remember, you can grab the wrapper source here : https://box2dwrapper.googlecode.com/svn/trunk I’ll be posting more complex examples soon so stay tuned ;)

Prismatic Joint Example

package
{
    import Box2D.Common.Math.b2Vec2;
   
    import com.box2dwrapper.math.Vec;
    import com.box2dwrapper.object.Box;
    import com.box2dwrapper.object.Joint;
    import com.box2dwrapper.view.Box2DView;

    public class PrismaticJointExample extends Box2DView
    {
        public function PrismaticJointExample()
        {
            super();
            initialize();
        }
       
        private function initialize():void
        {
            createBoundingWalls();
            showDebug = true;
            allowDragging = true;
                       
            createObjects();
           
            startPhysicsRender();
        }
       
        private function createObjects():void
        {
            var plank:Box = new Box();
            plank.scale(100, 20);
            plank.position(stage.stageWidth * 0.5, stage.stageHeight * 0.5);
            plank.type = "dynamic";
            addBody(plank);
           
            var joint:Joint = new Joint(Joint.PRISMATIC);
            joint.initialize(world.GetGroundBody(), plank.body, plank.body.GetWorldCenter(), new b2Vec2(1, 0));
            joint.lowerTranslation = -3;
            joint.upperTranslation = 3
            joint.enableLimit = true;          
           
            joint.maxMotorForce = 100;
            joint.motorSpeed = 4.0;
            joint.enableMotor = true;          
           
            addJoint(joint);
        }
       
    }
}

Revolute Joint Example

package
{
    import com.box2dwrapper.math.Vec;
    import com.box2dwrapper.object.Box;
    import com.box2dwrapper.object.Circle;
    import com.box2dwrapper.object.Joint;
    import com.box2dwrapper.view.Box2DView;

    public class RevoluteJointExample extends Box2DView
    {
        public function RevoluteJointExample()
        {
            super();
            initialize();
        }
       
        private function initialize():void
        {
            createBoundingWalls();
            showDebug = true;
            allowDragging = true;
                       
            createObjects();
           
            startPhysicsRender();
        }
       
        private function createObjects():void
        {
            var pivot:Circle = new Circle();
            pivot.radius = 10;
            pivot.position(stage.stageWidth * 0.5, stage.stageHeight * 0.5);
            pivot.type = "static";
            addBody(pivot);
           
            var plank:Box = new Box();
            plank.scale(20, 100);
            plank.position(pivot.x, pivot.y + 35);
            plank.type = "dynamic";
            addBody(plank);
           
            var tac:Joint = new Joint(Joint.REVOLUTE);
            tac.initialize(pivot.body, plank.body, pivot.body.GetWorldCenter());
           
            tac.enableMotor = true;
            tac.motorSpeed = 2;
            tac.maxMotorTorque = 50;
           
            addJoint(tac);
        }
       
    }
}

Gear Joint Example

package
{
    import Box2D.Dynamics.Joints.b2GearJoint;
    import Box2D.Dynamics.Joints.b2GearJointDef;
   
    import com.box2dwrapper.object.Circle;
    import com.box2dwrapper.object.Joint;
    import com.box2dwrapper.view.Box2DView;

    public class GearJointExample extends Box2DView
    {
        public function GearJointExample()
        {
            super();
            showDebug = true;
            allowDragging = true;
            initialize();
            startPhysicsRender();
        }
       
        private function initialize():void
        {
            var gear1:Circle = new Circle();
            gear1.radius = 40;
            gear1.density = 2;
            gear1.position(stage.stageWidth * 0.45, stage.stageHeight * 0.5);
            gear1.type = "dynamic";
            addBody(gear1);
           
            var gear2:Circle = new Circle();
            gear2.radius = 60;
            gear2.density = 3;
            gear2.position(stage.stageWidth * 0.65, stage.stageHeight * 0.5);
            gear2.type = "dynamic";
            addBody(gear2);
           
            var joint1:Joint = new Joint(Joint.REVOLUTE);
            joint1.initialize(world.GetGroundBody(), gear1.body, gear1.body.GetWorldCenter());
            addJoint(joint1);
           
            var joint2:Joint = new Joint(Joint.REVOLUTE);
            joint2.initialize(world.GetGroundBody(), gear2.body, gear2.body.GetWorldCenter());
            addJoint(joint2);
           
            var gearJoint:Joint = new Joint(Joint.GEAR);
            gearJoint.body1 = gear1.body;
            gearJoint.body2 = gear2.body;
            gearJoint.joint1 = joint1.joint;
            gearJoint.joint2 = joint2.joint;
            gearJoint.ratio = 1;
            addJoint(gearJoint);               
        }      
    }
}

Experiment, Lab, as3

Box2Dwrapper – Joints wrapped!

March 3rd, 2009
Comments Off

Hi all:

What are joints (anchor points)? Basically, joints are used to constrain physics bodies to either the physics world or each other. Different joints have different properties. Some provide “limits” and some provide “motors”. There are six types of joints:

  • Distance
  • Revolute
  • Prismatic
  • Pulley
  • Gear
  • Mouse



All joints in Box2D have a neat little “initialize” method which sets up their geometric data. So how would this work with Box2Dwrapper you ask? Very simple :) . All of the joints (with the exception of Mouse) can be created using the new Joint wrapper class.

The Joint wrapper class extends the flash.utils.Proxy class hence providing full access to the selected joint type. Here’s an example of the code you will see below:

First you create the wheels:

var circ:Circle = new Circle("dynamic");
circ.radius = 25;
circ.position(stage.stageWidth/2, 20);
circ.skin = new Wheel(); // Wheel is the class name of a center registered movie clip in the library
addBody(circ);

var circ2:Circle = new Circle("dynamic");
circ2.radius = 25;
circ2.position(stage.stageWidth/2 + 100, 20);
circ2.skin = new Wheel();
addBody(circ2);

Then we create the joint. In this case, a Distance Joint:

var circleLink:Joint = new Joint(Joint.DISTANCE)
circleLink.initialize(circ.body, circ2.body, circ.body.GetWorldCenter(), circ2.body.GetWorldCenter());
circleLink.collideConnected = true;
addJoint(circleLink);

So the first line defines and creates (constructs) the type of joint we require. It can either of the following types:

  • Joint.DISTANCE
  • Joint.REVOLUTE
  • Joint.PRISMATIC
  • Joint.PULLEY
  • Joint.GEAR


The second line calls the native “Initialize” method of the the joints definition. The initialize method accepts different arguments depending on the joint type:

  • Distance : body1, body2, anchor1:b2Vec2, anchor2:b2Vec2
  • Revolute : body1, body2, anchor:b2Vec2
  • Prismatic : body1:b2Body, body2:b2Body, anchor:b2Vec2, axis:b2Vec2
  • Pulley : body1:b2Body, body2:b2Body, ground_anchor:b2Vec2, ground_anchor2:b2Vec2, anchor1:b2Vec2, anchor2:b2Vec2
  • Gear : none (this joint has no initialize method. I will explain it’s use in another post)


In line three, the “collideConnected” property is not a property of the Joint class, rather a property of the joint definition created by the wrapper itself. Because the joint class extends the Proxy class, all the properties and methods which pertain to the selected joint can be directly called Via the Joint class.

That’s pretty much it! All classes have been updated over at http://box2dwrapper.googlecode.com .

Lab

Box2Dwrapper – Box2DFlashAS3 made simple!

February 28th, 2009

Hi guys:

So, about a week ago, a friend of mine asked me if I could give him a quick explanation as to how to use/setup Box2DFlashAS3. Honestly, I didn’t have the time as I was swamped with work, so I directed him over to Emanuele’s site which is chock full on Box2DFlashAS3 tutorials. After going through his posts for a couple days, my buddy said that he was able to figure it out but that the syntax was ridiculous! I don’t blame him either; the syntax is very unorthodox for a developer that has only develops in AS3.

That got me to thinking and I decided to create a wrapper class to try and make it simpler to use. Now, keep in mind that I really never dug into this particular physics engine. I quickly saw that a simple wrapper class was not going to be enough, but I started anyway and ended up with the Box2Dwrapper library.

The Box2Dwrapper library is modeled after Papervision3D’s BasicView and is extremely easy to use. Please keep in mind that the library is still under development and probably will be for some time since I will be busy with JigLibFlash refactoring and other work, never the less, I will update it often so keep tuned.

Enough gibberish, on to the code! Remember, it’s still in its infant stage and undocumented, but I’m still gonna let you guys check it out.

This is how simple it is to use:

package
{
    import com.box2dwrapper.object.Box;
    import com.box2dwrapper.object.Circle;
    import com.box2dwrapper.view.Box2DView;

    public class Box2Dwrapper_Test extends Box2DView
    {
        public function Box2Dwrapper_Test()
        {
            super(2000, 2000, 0, 9.8, true);
            showDebug = true;
            allowDragging = true;
            initialize();
        }
       
        private function initialize():void
        {
            createFloor();
            createFace();
            startPhysicsRender();
        }
       
        private function createFloor():void
        {
            var floor:Box = new Box();
            floor.position(stage.stageWidth * 0.5, stage.stageHeight);
            floor.scale(stage.stageWidth * 0.5, 10);
            floor.type = "static";
            addBody(floor);
        }
       
        private function createFace():void
        {
            var face:Circle = new Circle();
            face.radius = 20;
            face.position(stage.stageWidth * 0.5, 25);
            face.type = "dynamic";
            face.restitution = 0.5;
            addBody(face);
        }
       
    }
}

IN THE CONSTRUCTOR

- The Box2DView class accepts 4 parameters passed in via the “super” method. They are:

  • world width
  • world height
  • horizontal force
  • vertical force
  • allow sleep


- showDebug (internal) allows you to see the debug drawn physics objects.

- allowDragging (internal) makes the objects automatically draggable.

IN THE INITIALIZE METHOD

- startPhysicsRender() (internal) starts the render ticker.

IN THE createFloor and createFace METHODS

Here you can see how easy it is to create a box/circle with physics properties. Definitely a syntax we can relate to. Here are their properties and methods:

  • radius (circle only) sets the radius
  • density sets the mass
  • restitution sets the elasticity
  • friction sets the friction
  • type can be either “static” or “dynamic”
  • skin sets a display object from the library as a material, for example circle.skin = new Face();



  • scale(width, height) sets the width and height
  • position(x, y) sets the position


There are other properties and methods that are in the process of integration and that will be available in a future release. In the meantime, you guys can take the library as is, add, edit, remove stuff, dissect it as you wish. Just don’t claim it as yours ;)

You can grab the code from the Google repository here :
http://box2dwrapper.googlecode.com/svn/trunk/

Lab

Exploring a Papervision3D class – Sound3D

February 4th, 2009

So, this weekend I was working on a project; I use Flex Builder to code and as I was casting a class, I noticed “Sound3D” in the code completion and immediately got intrigued. I have heard of that class being buried deep in the Papervision packages before, but really didn’t pay much attention to it, till now.

Before we jump into anything, check out the example below. It uses the FirstPersonCamera3D class so you can move around and get a sense of what the Sound3D class does.

How cool is that?! I recently developed a game in which that would have worked out pretty well. No complaints though :) One thing about the Sound3D class in the Papervision3D packages is that it does not have the ability to load sounds, it only accepts a sound object as a parameter in the constructor, so, I decided to to add the functionality in there myself by creating an EnhancedSound3D class which extends the Sound3D, and here it is:

package
{
    import flash.events.Event;
    import flash.events.ProgressEvent;
    import flash.media.Sound;
    import flash.media.SoundLoaderContext;
    import flash.net.URLRequest;
   
    import org.papervision3d.objects.special.Sound3D;

    public class EnhancedSound3D extends Sound3D
    {
       
        private var soundObjectParams:Object = {};
       
        public function EnhancedSound3D(soundObj:Sound = null)
        {
            super(soundObj);
        }
       
        public function load(soundPath:String, startTime:Number = 0, loops:int = 0, soundTransform:SoundTransform = null):void{
            var soundRequest:URLRequest = new URLRequest(soundPath);
            sound = new Sound();
            sound.load(soundRequest, new SoundLoaderContext());
            sound.addEventListener(Event.COMPLETE, handleSoundLoaded);
            sound.addEventListener(ProgressEvent.PROGRESS, handleLoadProgress);
            soundObjectParams = {startTime:startTime, loops:loops, soundTransform:soundTransform}
        }
       
        private function handleSoundLoaded(e:Event):void
        {
            sound.removeEventListener(ProgressEvent.PROGRESS, handleLoadProgress);
            sound.removeEventListener(Event.COMPLETE, handleSoundLoaded);
            play(soundObjectParams.startTime, soundObjectParams.loops, soundObjectParams.soundTransform);
            dispatchEvent(e.clone());
        }
       
        private function handleLoadProgress(e:ProgressEvent):void
        {
            dispatchEvent(e.clone());
        }
       
    }
}

The Sound3D class extends DisplayObject3D, so it inherits all its properties such as x, y and z so you can basically place the sound anywhere you like in 3D space. A few added properties are maxSoundDistance and soundDistance. “maxSoundDistance” determines the maximum distance the sound can travel and “soundDistance” is used to control the volume. It puts out values ranging from -1 to 1 where 0 to -1 represent the sound being behind the camera.

You can basically attach your sound to anything, a car, a plane an enemy walking behind in your first person shooter game [wink, wink].

Below is the code I used for the example you just saw. Remember that you’ll need to grab the FirstPersonCamera3D class if you don’t have it yet.

package
{
    import flash.events.Event;
    import flash.events.ProgressEvent;
    import flash.text.TextField;
   
    import org.papervision3d.materials.ColorMaterial;
    import org.papervision3d.materials.WireframeMaterial;
    import org.papervision3d.materials.special.CompositeMaterial;
    import org.papervision3d.materials.utils.MaterialsList;
    import org.papervision3d.objects.primitives.Cube;
    import org.papervision3d.objects.primitives.Plane;
    import org.papervision3d.view.BasicView;

        import FirstPersonCamera3D;
        import EnhancedSound3D;

    public class CameraTest extends BasicView
    {
       
        private var firstPersonCamera:FirstPersonCamera3D;
        private var cubes:Array = [];
        private var sounds:Array = [];
       
        private var SIZE:Number;
        private var DISTANCE:Number;
       
        public function CameraTest()
        {
            super(640, 480, true, false);
            SIZE = 30000;
            DISTANCE = (SIZE * 0.5) - 250;
            setupCamera();
            createObjects3D();
            startRendering()
        }
       
        private function setupCamera():void
        {
            firstPersonCamera = new FirstPersonCamera3D();
            firstPersonCamera.initialize(viewport, 0.8, 0.2);
            firstPersonCamera.mode = "manual";
            firstPersonCamera.y = 750;
            firstPersonCamera.moveBounds = {left:-DISTANCE + 750, right:DISTANCE - 750, front:DISTANCE - 750, back:-DISTANCE + 750};
            firstPersonCamera.mapKeys(FirstPersonCamera3D.WASD_AND_ARROWS);
        }
       
        private function createObjects3D():void
        {
            var positions:Array = [{x:DISTANCE, y:750, z:DISTANCE},
                                   {x:-DISTANCE, y:750, z:DISTANCE},
                                   {x:DISTANCE, y:750, z:-DISTANCE},
                                   {x:-DISTANCE, y:750, z:-DISTANCE}];
           
            var cubeMat:WireframeMaterial = new WireframeMaterial(0xFF0000);
            var colorMat:ColorMaterial = new ColorMaterial(0x00FF00);
            var compMat:CompositeMaterial = new CompositeMaterial();
            compMat.addMaterial(cubeMat);
            compMat.addMaterial(colorMat);
           
            var materialsList:MaterialsList = new MaterialsList({ all:compMat });
           
            for(var a:Number = 0; a<4; a++){
                var cube:Cube = new Cube(materialsList);
                cube.x = positions[a].x;
                cube.y = positions[a].y;
                cube.z = positions[a].z;
                cube.lookAt(firstPersonCamera);
                cubes[a] = cube;
                               
                var sound3D:EnhancedSound3D = new EnhancedSound3D();
                sound3D.load(a+".mp3");
                sound3D.x = positions[a].x;
                sound3D.y = positions[a].y;
                sound3D.z = positions[a].z;
                sound3D.maxSoundDistance = SIZE * 0.75;
                sound3D.addEventListener(ProgressEvent.PROGRESS, handleSoundLoadProgress);
                sounds[a] = {sound:sound3D, tf:TextField(this["soundLoad"+a])};        
               
                scene.addChild(cube);
                scene.addChild(sound3D);
            }
           
            var floor:Plane = new Plane(new WireframeMaterial(0xFFFFFF), SIZE, SIZE, SIZE*0.001, SIZE*0.001);
            floor.rotationX = 90;
            scene.addChild(floor);
        }
       
        private function focusCubesOnCamera():void
        {
            for(var a:Number = 0; a<cubes.length; a++){
                cubes[a].lookAt(firstPersonCamera)
            }
        }
       
        private function handleSoundLoadProgress(e:ProgressEvent):void
        {
            for(var a:Number = 0; a<sounds.length; a++){
                if(e.target == sounds[a].sound){
                    sounds[a].tf.text = "Sound"+a+" -- "+Number(Math.round((e.bytesLoaded/e.bytesTotal)*100))+"% loaded."
                }
            }
        }
       
        override protected function onRenderTick(event:Event=null):void
        {
            firstPersonCamera.look(0.5);
            focusCubesOnCamera();
           
            renderer.renderScene(scene, firstPersonCamera, viewport);
        }
       
    }
}

Lab, as3, papervision3d