Getting Started with Proscenium

So I had a chance this weekend to sit down and play around with Adobe’s new 3D framework for Stage3D, Proscenium, and thought I’d share a few of the results (a word of caution, there are no preloaders for any of the examples and may load a bit slowly).

The first shows some reflections and shadows. Move your mouse around to raise and lower the camera.

In the second, I thought I’d play around with some physics. Use the keyboard arrow keys to roll the red ball around and knock over some crates. Note: there seems to be something buggy with the Proscenium keyboardEventHandler() method and you may have to refresh the page to get control of the ball.

In the third, I thought I’d play around with a video texture. If you have a webcam attached, you can see yourself in a sphere with a mirror orbiting around (look hard enough and you may see the back of your head).

My first impressions of Proscenium are generally pretty favorable. There are a few things I’d like to see worked out, such as the keyboard event handler mentioned above. Also, there doesn’t seem to be an easy way to apply a texture to a cube. If you want the same thing on every side, such as my crates in the physics example, that’s easy enough, but if you want a different texture per side (e.g. a die), there’s no obvious way of getting it done simply. Finally, while Proscenium supports particles, there’s no easy or high level way to get them working. Prepare yourself for writing some AGAL. It would be nice if the folks working on the project took a cue from those working on the Starling framework and created a method of creating particles based on a config / xml file.

Proscenium is still in early stages though and is coming along very nicely. I’ll be really interested in seeing how it develops. As I mentioned in a tweet earlier this week, it may just give Away3D a run for it’s money. It still has a good ways to go though.

For anyone interested, the commented source code for all three examples is posted below:

Example 1 (shadows and reflections):

/**
 *	Copyright (c) 2011 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package 
{
	import com.adobe.scenegraph.BasicScene;
	import com.adobe.scenegraph.MaterialStandard;
	import com.adobe.scenegraph.MeshUtils;
	import com.adobe.scenegraph.RenderSettings;
	import com.adobe.scenegraph.RenderTextureCube;
	import com.adobe.scenegraph.SceneCamera;
	import com.adobe.scenegraph.SceneLight;
	import com.adobe.scenegraph.SceneMesh;
	import com.adobe.scenegraph.SceneSkyBox;
	import com.adobe.scenegraph.TextureMap;
	import flash.display.BitmapData;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3DRenderMode;
	import flash.events.Event;
	import flash.geom.ColorTransform;
	import flash.geom.Vector3D;
	
	/**
	 * Proscenium example with multiple lights, shadows, reflections, and custom texture.
	 * @author Devon O.
	 */
	
	[SWF(width='800', height='600', backgroundColor='#000000', frameRate='60')]
	public class Main extends BasicScene 
	{
		[Embed(source="../res/sky/back.jpg")]
		private const SKY0:Class;
		
		[Embed(source="../res/sky/up.jpg")]
		private const SKY1:Class;
		
		[Embed(source="../res/sky/front.jpg")]
		private const SKY2:Class;
		
		[Embed(source="../res/sky/down.jpg")]
		private const SKY3:Class;
		
		[Embed(source="../res/sky/right.jpg")]
		private const SKY4:Class;
		
		[Embed(source="../res/sky/left.jpg")]
		private const SKY5:Class;

		private var mCamera:SceneCamera;
		private var mLight1:SceneLight;
		private var mLight2:SceneLight;
		private var mShadowMapSize:int = 128;
		private var mCameraHeight:Number = 15.0;
		
		private var mLargePlanet:SceneMesh;
		private var mSmallPlanet:SceneMesh;
		
		public function Main(renderMode:String = Context3DRenderMode.AUTO):void 
		{
			super(renderMode);
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(event:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
		}
		
		override protected function initLights():void 
		{
			super.initLights();
			
			// set up some exponential fog
			instance.primarySettings.fogMode = RenderSettings.FOG_EXP;
			instance.primarySettings.fogDensity = 300;
			
			// add a point and distant light, set their colors, and prepare them for casting shadows
			mLight1 = new SceneLight(SceneLight.KIND_POINT);
			mLight1.color.setFromUInt(0x837749);
			mLight1.appendTranslation(0, 50, 20);
			mLight1.shadowMapEnabled = true;
			mLight1.setShadowMapSize(mShadowMapSize, mShadowMapSize);
			scene.addChild(mLight1);
			
			mLight2 = new SceneLight(SceneLight.KIND_DISTANT);
			mLight2.color.setFromUInt(0x837749);
			mLight2.appendTranslation(5, 60, -20);
			mLight2.shadowMapEnabled = true;
			mLight2.setShadowMapSize(mShadowMapSize, mShadowMapSize);
			scene.addChild(mLight2);
		}
		
		override protected function initModels():void 
		{
			super.initModels();
			
			// create a skybox from embedded bitmaps
			var skyboxData:Vector. = new Vector.( 6, true );
			skyboxData[0] = new SKY0().bitmapData;
			skyboxData[1] = new SKY2().bitmapData;
			skyboxData[2] = new SKY1().bitmapData;
			skyboxData[3] = new SKY3().bitmapData;
			skyboxData[4] = new SKY4().bitmapData;
			skyboxData[5] = new SKY5().bitmapData;
			var sky:SceneSkyBox = new SceneSkyBox( skyboxData, false );
			scene.addChild(sky);
			
			// create a nice looking terrain with fractal hills and lower it a little
			var ground:SceneMesh = MeshUtils.createFractalTerrain(100, 100, 500, 500, 65);
			ground.setPosition(0, -50, 0);
			scene.addChild(ground);
			
			// this will be used to reflect the entire scene
			var cubeMap:RenderTextureCube = new RenderTextureCube(256);
			cubeMap.addSceneNode(scene);
			
			// create a material which will use the cube map above as an environment map
			var material:MaterialStandard = new MaterialStandard();
			material.environmentMap = cubeMap;
			material.ambientColor.setFromUInt(0x000000);
			material.diffuseColor.setFromUInt(0x000000);
			material.environmentMapStrength = .98;
			
			// apply that material to a sphere and add the sphere to the scene
			mLargePlanet = MeshUtils.createSphere(8, 32, 32, material);
			mLargePlanet.appendTranslation(0, 10, 0);
			cubeMap.attachedNode = mLargePlanet;	// attach the cube map to the sphere (!)
			scene.addChild(mLargePlanet);
			
			// create a bitmapdata for a texture, make it a bit interesting with perlin noise and color it to fit in
			var texture:BitmapData = new BitmapData(256, 256, false);
			texture.perlinNoise(16, 16, 8, 93, true, true, 7, true);
			texture.colorTransform(texture.rect, new ColorTransform(0x29 / 0xFF, 0x1A / 0xFF, 0x0E / 0xFF));
			
			// create an actual texture that uses the above bitmap data
			var map:TextureMap = new TextureMap(texture);
			
			// create a material that uses that bitmap data texture as an emissive map
			material = new MaterialStandard();
			material.emissiveMap = map;
			material.ambientColor.setFromUInt(0x000000);
			material.diffuseColor.setFromUInt(0x000000);
			
			// apply that material to a smaller sphere
			mSmallPlanet = MeshUtils.createSphere(3, 32, 32, material);
			mSmallPlanet.appendTranslation(20, 12, 0);
			scene.addChild(mSmallPlanet);
			
			// and why not apply that same material to the ground created earlier
			ground.applyMaterial(material);
			
			// make the smaller sphere cast shadows from both lights in the scene
			mLight1.addToShadowMap(mSmallPlanet);
			mLight2.addToShadowMap(mSmallPlanet);
		}
		
		override protected function resetCamera():void
		{
			// just initializes the camera. Its position is constantly set during the onAnimate call
			mCamera = scene.activeCamera;
			mCamera.identity();
		}
		
		override protected function onAnimate(t:Number, dt:Number):void 
		{
			super.onAnimate(t, dt);
			
			
			// raise and lower the camera with a bit of easing but keeping it circling around and looking at the larger sphere
			var r:Number = ((stage.mouseY / stage.stageHeight) - .5) * 2;
			var th:Number = -15 * r;
			mCameraHeight += ((th + 5) - mCameraHeight) / 10;
			
			mCamera.setPosition(Math.sin(t * .5) * 55, mCameraHeight, Math.cos(t * .5) *55);
			mCamera.lookat(mCamera.position, mLargePlanet.position, Vector3D.Y_AXIS);
			
			// orbit the smaller planet around the larger
			mSmallPlanet.appendRotation(1.25, Vector3D.Y_AXIS);
		}
		
	}
}

Example 2 (physics):

/**
 *	Copyright (c) 2011 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package  
{
	import com.adobe.scenegraph.BasicScene;
	import com.adobe.scenegraph.MaterialStandard;
	import com.adobe.scenegraph.PelletManager;
	import com.adobe.scenegraph.RenderSettings;
	import com.adobe.scenegraph.SceneCamera;
	import com.adobe.scenegraph.SceneLight;
	import com.adobe.scenegraph.SceneMesh;
	import com.adobe.scenegraph.SceneNode;
	import com.adobe.scenegraph.SceneSkyBox;
	import com.adobe.scenegraph.TextureMap;
	import flash.display.BitmapData;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3DRenderMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.geom.ColorTransform;
	import flash.geom.Vector3D;
	import flash.ui.Keyboard;
	
	/**
	 * Proscenium example with basic physics
	 * @author Devon O.
	 */
	
	[SWF(width='800', height='600', backgroundColor='#000000', frameRate='60')]
	public class Main extends BasicScene 
	{
		[Embed(source="../res/sky/Sky0000.jpg")]
		private const SKY0:Class;
		
		[Embed(source="../res/sky/Sky0001.jpg")]
		private const SKY1:Class;
		
		[Embed(source="../res/sky/Sky0002.jpg")]
		private const SKY2:Class;
		
		[Embed(source="../res/sky/Sky0003.jpg")]
		private const SKY3:Class;
		
		[Embed(source="../res/sky/Sky0004.jpg")]
		private const SKY4:Class;
		
		[Embed(source="../res/sky/Sky0005.jpg")]
		private const SKY5:Class;
		
		[Embed(source = "../res/crate1.jpg")]
		private const CRATE_MAT:Class;
		
		private const NUM_CRATES:int = 5;
		
		private var mPellet:PelletManager;
		
		private var mLight1:SceneLight;
		private var mLight2:SceneLight;
		private var mShadowMapSize:int = 1024;
		
		private var mCamera:SceneCamera;
		private var mBall:SceneMesh;
		private var mSceneObjs:Vector. = new Vector.();
		
		public function Main(renderMode:String = Context3DRenderMode.AUTO) 
		{
			super(renderMode);
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(event:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
		}
		
		override protected function initLights():void 
		{
			super.initLights();
			
			// some linear fog with a start and end
			instance.primarySettings.fogMode = RenderSettings.FOG_LINEAR;
			instance.primarySettings.fogStart = 0;
			instance.primarySettings.fogEnd = .01;
			
			
			// two scene lights point and distant, prepped for casting shadows
			mLight1 = new SceneLight(SceneLight.KIND_POINT);
			mLight1.color.setFromUInt(0x8C98A7);
			mLight1.appendTranslation(50, 150, 20);
			mLight1.shadowMapEnabled = true;
			mLight1.setShadowMapSize(mShadowMapSize, mShadowMapSize);
			scene.addChild(mLight1);
			
			mLight2 = new SceneLight(SceneLight.KIND_DISTANT);
			mLight2.color.setFromUInt(0xE2E2E2);
			mLight2.appendTranslation(5, 60, -20);
			mLight2.shadowMapEnabled = true;
			mLight2.setShadowMapSize(mShadowMapSize, mShadowMapSize);
			scene.addChild(mLight2);
		}
		
		override protected function initModels():void 
		{
			super.initModels();
			
			// skybox created from embedded bitmaps
			var skyboxData:Vector. = new Vector.( 6, true );
			skyboxData[0] = new SKY0().bitmapData;
			skyboxData[1] = new SKY2().bitmapData;
			skyboxData[2] = new SKY1().bitmapData;
			skyboxData[3] = new SKY3().bitmapData;
			skyboxData[4] = new SKY4().bitmapData;
			skyboxData[5] = new SKY5().bitmapData;
			var sky:SceneSkyBox = new SceneSkyBox( skyboxData, false );
			scene.addChild(sky);
			
			// used to control the physics and create scene meshes 
			mPellet = new PelletManager();
			
			// everything added to this scene node will cast shadows
			var caster:SceneNode = new SceneNode();
			scene.addChild(caster);
			
			// ground texture created from bitmapdata and mapped to a box mesh
			var texture:BitmapData = new BitmapData(128, 128, false, 0x002200);
			texture.noise(23, 0, 0xFF, 7, true);
			texture.colorTransform(texture.rect, new ColorTransform(0, .10, 0));
			var map1:TextureMap = new TextureMap(texture);
			var mat1:MaterialStandard = new MaterialStandard();
			mat1.emissiveMap = map1;
			mat1.specularIntensity = 0;
			mat1.specularColor.setFromUInt(0x000000);
			mat1.ambientColor.setFromUInt(0x000000);

			var ground:SceneMesh = mPellet.createBox(500, 1, 500, mat1);
			// set the ground's mass to 0 so that it won't be affected by the physics
			ground.physicsObject.mass = 0;
			scene.addChild(ground);
			
			// create a new texture from an embedded bitmap of a crate image
			texture = new CRATE_MAT().bitmapData;
			var map2:TextureMap = new TextureMap(texture);
			var mat2:MaterialStandard = new MaterialStandard();
			mat2.diffuseColor.setFromUInt(0x000000);
			mat2.ambientColor.setFromUInt(0x000000);
			mat2.emissiveMap = map2;
			
			// create 5 'crates', add them to the caster node and push them into a sceneObj collection
			for (var i:int = 0; i < NUM_CRATES; i++) 
			{
				var crate:SceneMesh = mPellet.createBox(8, 8, 8, mat2);
				crate.setPosition(0, 20 + i * 5, 0);
				crate.prependRotation(Math.random() * 360, Vector3D.Y_AXIS);
				mSceneObjs.push(crate);
				caster.addChild(crate);
			}
			
			// create a ball texture from bitmapdata and give it a slightly red color
			texture = new BitmapData(256, 256, false);
			texture.perlinNoise(16, 16, 8, 93, true, true, 7, true);
			texture.colorTransform(texture.rect, new ColorTransform(.4, .2, .2));
			var map3:TextureMap = new TextureMap(texture);
			var mat3:MaterialStandard = new MaterialStandard();
			mat3.emissiveMap = map3;
			mBall = mPellet.createSphere(4, 32, 32, mat3);
			mBall.setPosition(0, 10, 20);
			
			// add the ball to our collection of scene objects and caster
			mSceneObjs.push(mBall);
			caster.addChild(mBall);
			
			// make it so our caster node casts shadows
			mLight1.addToShadowMap(caster);
			mLight2.addToShadowMap(caster);
		}
		
		override protected function resetCamera():void
		{
			// just initialize the camera
			mCamera = scene.activeCamera;
			mCamera.identity();
		}
		
		override protected function onAnimate(t:Number, dt:Number):void 
		{
			super.onAnimate(t, dt);
			
			// step through the physics
			mPellet.stepWithSubsteps(dt, 2);
			
			// position the camera to watch the ball
			mCamera.setPosition( mBall.position.x - 100, 25, mBall.position.z );
			mCamera.lookat(mCamera.position, mBall.position, Vector3D.Y_AXIS);
			
			// loop through our collection of scene objects
			// if any have fallen off the edge of the ground clear their forces and set them back over the ground
			var i:int = mSceneObjs.length;
			while (i--)
			{
				var obj:SceneMesh = mSceneObjs[i];
				if (obj.position.y < -5)
				{
					obj.physicsObject.setLinearVelocity();
					obj.physicsObject.setLinearVelocity();
					obj.setPosition(0, 50, 0);
				}
			}
		}
		
		override protected function keyboardEventHandler(event:KeyboardEvent):void 
		{
			super.keyboardEventHandler(event);
			
			// use the arrow keys to apply an impulse to the ball object
			// this seems a bit buggy and stops working 1 / 10 times or so
			switch(event.type) 
			{
				case KeyboardEvent.KEY_DOWN :

					switch(event.keyCode) 
					{
						case Keyboard.UP :
							mBall.physicsObject.applyImpulseToCenter(3, 0, 0);
							break;
							
						case Keyboard.DOWN :
							mBall.physicsObject.applyImpulseToCenter(-3, 0, 0);
							break;
							
						case Keyboard.RIGHT :
							mBall.physicsObject.applyImpulseToCenter(0, 0, 3);
							break;
							
						case Keyboard.LEFT :
							mBall.physicsObject.applyImpulseToCenter(0, 0, -3);
							break;
					}
			}
		}
	}
}

Example 3 (video texture):

/**
 *	Copyright (c)  Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package  
{
	import com.adobe.scenegraph.BasicScene;
	import com.adobe.scenegraph.MaterialStandard;
	import com.adobe.scenegraph.MeshUtils;
	import com.adobe.scenegraph.RenderSettings;
	import com.adobe.scenegraph.RenderTextureReflection;
	import com.adobe.scenegraph.SceneCamera;
	import com.adobe.scenegraph.SceneLight;
	import com.adobe.scenegraph.SceneMesh;
	import com.adobe.scenegraph.SceneNode;
	import com.adobe.scenegraph.SceneSkyBox;
	import com.adobe.scenegraph.TextureMap;
	import flash.display.BitmapData;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3DRenderMode;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.geom.Vector3D;
	import flash.media.Camera;
	import flash.media.Video;
	
	/**
	 * Proscenium example with video texture and mirror (plane) reflection
	 * @author Devon O.
	 */
	
	[SWF(width='800', height='600', backgroundColor='#000000', frameRate='40')]
	public class Main extends BasicScene 
	{
		
		[Embed(source="../res/sky/Sky0000.jpg")]
		private const SKY0:Class;
		
		[Embed(source="../res/sky/Sky0001.jpg")]
		private const SKY1:Class;
		
		[Embed(source="../res/sky/Sky0002.jpg")]
		private const SKY2:Class;
		
		[Embed(source="../res/sky/Sky0003.jpg")]
		private const SKY3:Class;
		
		[Embed(source="../res/sky/Sky0004.jpg")]
		private const SKY4:Class;
		
		[Embed(source="../res/sky/Sky0005.jpg")]
		private const SKY5:Class;
		
		private var mLight1:SceneLight;
		private var mLight2:SceneLight;
		private var mShadowMapSize:int = 256;
		
		private var mCamera:SceneCamera;
		private var mBall:SceneMesh;
		
		// video
		private var mVideo:Video;
		private var mVideoData:BitmapData;
		private var mVideoTexture:TextureMap;
		private var mIsVideo:Boolean = false;
		private var mScreen:SceneMesh;
		private var mMat:Matrix = new Matrix();
		
		public function Main(renderMode:String = Context3DRenderMode.AUTO) 
		{
			super(renderMode);
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(event:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			initVideo();
		}
		
		private function initVideo():void
		{
			// create a bitmapdata and texture that uses that uses that data
			mVideoData = new BitmapData(128, 128, false, 0x000000);
			mVideoTexture = new TextureMap(mVideoData);
			
			// get the webcam
			var camIndex:int = 0;
			for ( var i:int = 0 ; i < Camera.names.length ; i++ ) {
				if ( Camera.names[ i ] == "USB Video Class Video" ) {
					camIndex = i;
					break;
				}
			}
			var cam:Camera = Camera.getCamera(String(camIndex));
			
			// if a webcam exists, create a video object
			if (cam)
			{
				mVideo = new Video();
				mVideo.attachCamera(cam);
				mVideo.smoothing = true;
				mMat.scale(mVideoData.width / mVideo.width, mVideoData.height / mVideo.height);
				mIsVideo = true;
			}
		}
		
		override protected function initLights():void 
		{
			super.initLights();
			
			// some fog
			instance.primarySettings.fogMode = RenderSettings.FOG_EXP2;
			instance.primarySettings.fogDensity = 200;
			
			// some lights
			mLight1 = new SceneLight(SceneLight.KIND_POINT);
			mLight1.color.setFromUInt(0x837749);
			mLight1.appendTranslation( 0, 50, 20 );
			mLight1.shadowMapEnabled = true;
			mLight1.setShadowMapSize(mShadowMapSize, mShadowMapSize);
			scene.addChild(mLight1);
			
			mLight2 = new SceneLight(SceneLight.KIND_DISTANT);
			mLight2.color.setFromUInt(0x837749);
			mLight2.appendTranslation( 5, 60, -20 );
			mLight2.shadowMapEnabled = true;
			mLight2.setShadowMapSize(mShadowMapSize, mShadowMapSize);
			scene.addChild(mLight2);
		}
		
		override protected function initModels():void 
		{
			super.initModels();
			
			// skybox
			var bitmapDatas:Vector. = new Vector.( 6, true );
			bitmapDatas[0] = new SKY0().bitmapData;
			bitmapDatas[1] = new SKY2().bitmapData;
			bitmapDatas[2] = new SKY1().bitmapData;
			bitmapDatas[3] = new SKY3().bitmapData;
			bitmapDatas[4] = new SKY4().bitmapData;
			bitmapDatas[5] = new SKY5().bitmapData;
			var sky:SceneSkyBox = new SceneSkyBox( bitmapDatas, false );
			scene.addChild(sky);
			
			// fractal ground set low
			var ground:SceneMesh = MeshUtils.createFractalTerrain(100, 100, 1000, 1000, 85);
			ground.setPosition(0, -150, 0);
			scene.addChild(ground);
			
			// create a material that uses our video texture and apply it to a sphere
			var mat:MaterialStandard = new MaterialStandard();
			mat.emissiveMap = mVideoTexture;
			mat.specularExponent = 30;
			mat.diffuseColor.setFromUInt(0x000000);
			mat.ambientColor.setFromUInt(0x000000);
			mat.environmentMapStrength = .99;
			mBall = MeshUtils.createSphere(32, 32, 32, mat);
			scene.addChild(mBall);
			
			// used to apply reflective material to a plane
			var ref:RenderTextureReflection = new RenderTextureReflection(256, 256);
			
			// create a material that uses the reflective texture
			var mat2:MaterialStandard = new MaterialStandard();
			mat2.emissiveMap = ref;
			
			// create a plane with the above material
			mScreen = MeshUtils.createPlane(40, 40, 2, 2, mat2);
			mScreen.setPosition(50, 20, 0);
			scene.addChild(mScreen);
			
			// specify the reflective texture's geometry and that it should reflect the entire scene
			ref.reflectionGeometry = mScreen;
			ref.addSceneNode(scene);
			
			// allow the ball to cast shadows
			mLight1.addToShadowMap(mBall);
			mLight2.addToShadowMap(mBall);
		}
		
		override protected function resetCamera():void
		{
			// initialize the camera
			mCamera = scene.activeCamera;
			mCamera.identity();
		}
		
		override protected function onAnimate(t:Number, dt:Number):void 
		{
			super.onAnimate(t, dt);
			
			// rotate the 'mirror' around the ball while keeping it rotated in a way that it always reflects the ball
			mScreen.lookat(mScreen.position, mBall.position, Vector3D.Y_AXIS);
			mScreen.appendRotation(2, Vector3D.Y_AXIS);
			mScreen.prependRotation(-90, Vector3D.X_AXIS);
			
			// if a webcam is running, draw the video into the video bitmap data and update the texture with that bitmapdata
			if (mIsVideo) 
			{
				mVideoData.draw(mVideo, mMat);
				mVideoTexture.update(mVideoData);
			}
			
			// orbit the camera around the ball
			mCamera.setPosition(Math.sin(t * .5) * 200, 10, Math.cos(t * .5) * 200);
			mCamera.lookat(mCamera.position, mBall.position, Vector3D.Y_AXIS);
		}
		
	}
}