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); } } }
Post a comment