Open Source Flash Pano Viewer based on PV3D (update)

This is a continuation -- the latest release! -- of our Open Source Saga, whereby we are creating a set of libraries sat atop Papervision 3D... all with the intention of making it possible and desirable for the community at large to co-develop a Flash-based Panorama Viewer.

[UPDATE]
PanoSalado now has its very own blog. For the latest news, source files and examples head on over to the PanoSalado Blog. You can also directly subscribe to the blog feed and never be without your PanoSalado. Wink
[END UPDATE]

Continue on for links to the latest source and code. Enjoy! (See the original post introducing the open-source initiative at: http://flashpanos.com/content/open-source-flash-panorama-viewer-based-pa... )

Any and all interested folks are welcome to leave comments, send code snippets and advice, kudos and kritiques our way. We look forward to it!

The latest revision is practically a rewrite. Note the prevalence of XML description, which will hopefully pave the way for interesting things. Wink

Cheers,

Z and P

Source, as of June 12, 2008:
http://www.flashpanos.com/flashpanos_files/Papervision/PanoSalado/trunk/...

Live example (subject to change):
http://flashpanos.com/flashpanos_files/Papervision/PanoSalado/trunk/exam...

Code:

package
{

        import br.com.stimuli.loading.BulkLoader;
        import br.com.stimuli.loading.BulkProgressEvent;
        import br.com.stimuli.loading.BulkErrorEvent;
       
        import flash.system.System;
       
        import flash.utils.Dictionary;
       
        import flash.display.*;
        import flash.text.TextField;
        import flash.text.TextFieldAutoSize;
        import flash.text.TextFormat;
        import flash.display.StageAlign;
        import flash.display.StageScaleMode;
       
        import zephyr.cameracontrol.CameraController;
        import zephyr.cameracontrol.CameraControllerEvent;
       
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.events.ProgressEvent;
        import flash.events.MouseEvent;
        import org.papervision3d.cameras.FreeCamera3D;
       
        import zephyr.objects.primitives.Cube;
        import org.papervision3d.objects.primitives.Plane;
        import org.papervision3d.objects.primitives.Sphere;
        import org.papervision3d.render.BasicRenderEngine;
        import org.papervision3d.scenes.Scene3D;
        import org.papervision3d.view.Viewport3D;
        import org.papervision3d.events.InteractiveScene3DEvent;
        import org.papervision3d.objects.DisplayObject3D;
        import org.papervision3d.materials.MovieMaterial;
        import org.papervision3d.materials.ColorMaterial;
        import org.papervision3d.materials.BitmapFileMaterial;
        import org.papervision3d.materials.BitmapMaterial;
        import org.papervision3d.materials.utils.MaterialsList;
        import org.papervision3d.core.proto.MaterialObject3D;
        import org.papervision3d.events.RendererEvent;
        import org.papervision3d.core.render.data.RenderStatistics;
       
        import gs.TweenLite;

        import zephyr.transitions.None;
        import zephyr.transitions.Quad;
        import zephyr.transitions.Cubic;
        import zephyr.transitions.Quart;
        import zephyr.transitions.Quint;
        import zephyr.transitions.Sine;
        import zephyr.transitions.Circ;
        import zephyr.transitions.Expo;
        import zephyr.transitions.Elastic;
        import zephyr.transitions.Back;
        import zephyr.transitions.Bounce;
       
        import zephyr.utils.FPS;
        import org.papervision3d.view.stats.StatsView;

        public class PanoSalado extends Sprite
        {
               
                public var bulkLoader : BulkLoader;
               
                public var spaces:Array = new Array();
                public var viewports:Sprite = new Sprite();
               
                public var cameraController:CameraController;
               
                public var currentScene:String;
               
                private var interactionEquivalents:Object = new Object();
               
                private var unclaimedMaterials:Dictionary = new Dictionary(true);
               
                private var _worldDirty:Boolean = false;
               
                public var settings : XML =
                <settings
                onStart="loadScene:preview"  
                cameraContinuity="lock"
                zoom="7"
                transition="3,alpha,0,Expo.easeInOutExpo"
                >
                        <preview
                                interactive="0"
                                transition="3,alpha,0,Expo.easeInOutExpo"
                                onTransitionEnd="loadScene:concert1"
                                pan="0"
                                tilt="0"
                                zoom="7"
                        >
                                <sphere id="preview" radius="100000" segments="24" oneSide="false">
                                        <file>images/preview.jpg</file>
                                </sphere>
                        </preview>
                        <concert1
                                interactive="true"
                                onTransitionEnd="removePreviousScene"
                        >
                                <cube id="concert1" width="100000" segments="9" oneSide="true" smooth="true" precise="true">
                                        <file id="front">images/Concert_1-sm_f.jpg</file>
                                        <file id="right">images/Concert_1-sm_r.jpg</file>
                                        <file id="left">images/Concert_1-sm_l.jpg</file>
                                        <file id="top">images/Concert_1-sm_u.jpg</file>
                                        <file id="bottom">images/Concert_1-sm_d.jpg</file>
                                        <file id="back">images/Concert_1-sm_b.jpg</file>
                                </cube>
                                <plane id="toConcert2"
                                        interactive = "true"
                                        pan="-20"
                                        tilt="-10"
                                        segments="2"
                                        oneSide="false"
                                        smooth="true"
                                        onClick="set:a=b;loadScene:preview"
                                >
                                        <file>graphics/hotspot.png</file>
                                </plane>
                        </concert1>
                        <concert2>
                                <plane id="toConcert2"
                                pan="20"
                                tilt="-10"
                                segments="2"
                                >
                                        <file>graphics/hotspot.png</file>
                                </plane>
                        </concert2>
                </settings>
                ;
               
               
                private var tf:TextField;
               
                public function PanoSalado()
                {      
                        ///DEBUG
                        tf = new TextField();
                        tf.x = 10;
                        tf.y = 10;
                        tf.text = "testing";
                        //addChild(tf);
                        /// END DEBUG
                       
                        interactionEquivalents = { mouseClick:"onClick", mouseOver:"onOver", mouseOut:"onOut", mousePress:"onPress", mouseRelease:"onRelease", mouseMove:"onOverMove" };
                       
                        stage.align = StageAlign.TOP_LEFT;
                        stage.scaleMode = StageScaleMode.NO_SCALE;
                        stage.frameRate = 30;
                        stage.quality = StageQuality.MEDIUM
                       
                        addChild(viewports);
                       
                       
                        cameraController = new CameraController( this, false  );
                        cameraController.sensitivity = 60;
                        cameraController.friction = 0.3;
                        cameraController.autoRotateIncrement = 15;
                       
                        cameraController.addEventListener(CameraControllerEvent.DECELERATING, changeQuality);
                        cameraController.addEventListener(CameraControllerEvent.ACCELERATING, changeQuality);
                        cameraController.addEventListener(CameraControllerEvent.STOPPED, changeQuality);
                        cameraController.addEventListener(CameraControllerEvent.MOVING, moveCamera);
                       
                        addEventListener(Event.ENTER_FRAME, doRender);
                       
                       
                        bulkLoader = new BulkLoader("bulkLoader");
                       
                        bulkLoader.addEventListener(BulkLoader.PROGRESS, onAllProgress, false, 0, true);
                        bulkLoader.addEventListener(BulkLoader.COMPLETE, onAllLoaded, false, 0, true);
                       
                       
                        XMLCodeHook("onStart", false);
                       
                       
                       
                }
               
               
                private function onAllProgress(evt : BulkProgressEvent) : void
                {
                        //trace("progress event: loaded" , evt.bytesLoaded," of ",  evt.bytesTotal);
                }
               
                private function onAllLoaded(evt : BulkProgressEvent) : void
                {
                        trace("PS: scene loaded");
                       
                       
                       
                        var idx:int = getNewSpace();
                       
                        var stats:StatsView = new StatsView( spaces[idx]["renderer"] );
                        addChild(stats);
                       
                        setupCamera(spaces[idx]["camera"], spaces[idx-1]);
                       
                        for each (var xml:XML in settings.child( currentScene ).children() )
                        {
                               
                                var primitive:Object = this[xml.name().localName.toString()].call(null, xml);
                               
                                primitive.name = xml.@id.toString()
                               
                                primitive.x += getIntInXML(xml.@x, 0);
                                primitive.y += getIntInXML(xml.@y, 0);
                                primitive.z += getIntInXML(xml.@z, 0);
                               
                                primitive.rotationX += getIntInXML(xml.@rotationX, 0);
                                primitive.rotationY += getIntInXML(xml.@rotationY, 0);
                                primitive.rotationZ += getIntInXML(xml.@rotationZ, 0);
                               
                                primitive.visible = getBooleanInXML(xml.@visible, true);
                               
                                if ( getStringInXML(xml.@onClick) != null ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, interactionHandler); }
                                if ( getStringInXML(xml.@onOver) != null ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, interactionHandler); }
                                if ( getStringInXML(xml.@onOut) != null ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, interactionHandler); }
                                if ( getStringInXML(xml.@onPress) != null ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_PRESS, interactionHandler); }
                                if ( getStringInXML(xml.@onRelease) != null ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_RELEASE, interactionHandler); }
                                if ( getStringInXML(xml.@onOverMove) != null ) { primitive.addEventListener(InteractiveScene3DEvent.OBJECT_MOVE, interactionHandler); }
                               
                               
                               
                                spaces[idx]["scene"].addChild( primitive, xml.@id.toString() );
                        }
                       
                        bulkLoader.removeAll();
                       
                        spaces[idx]["viewport"].interactive = findBooleanInXML("interactive", false);
                       
                        viewports.addChild( spaces[idx]["viewport"] );
                                               
                        XMLCodeHook("onTransitionStart");
                       
                        var transArr:Array = findStringInXML("transition").split(",");
                        var time:Number = transArr[0] != null ? Number( transArr[0] ) : 3;
                        var type:String = transArr[1] != null ? transArr[1] : "alpha";
                        var val:Number = transArr[2] != null ? transArr[2] : 0;
                        var ease:String = transArr[3] != null ? transArr[3] : "Expo.easeInOutExpo";
                        var easeArr:Array = ease.split(".");
                        var easeEq:String = easeArr[0];
                        var easeFunc:String = easeArr[1];
                        var initObject:Object = new Object();
                        initObject.onComplete = XMLCodeHook;
                        initObject.onCompleteParams = new Array("onTransitionEnd")
                        initObject.ease = [easeEq][easeFunc];
                        initObject[type] = val;
                       
                        TweenLite.from(spaces[idx]["viewport"], time, initObject );
                       
                       
                        if ( ! cameraController.enterFrameListenerActive )
                        {      
                                _worldDirty = true;
                                doRender();
                               
                                _worldDirty = true;
                                TweenLite.delayedCall( 0.1, doRender );
                               
                        }
                       
                       
                        XMLCodeHook("onDisplay");
                       
                        settings.@onDisplay = "";
                       
                }
               
                private function setupCamera(camera:FreeCamera3D, lastSpace:Object=null):void
                {
                        var cameraContinuity:String = findStringInXML("cameraContinuity");
                        var pan:Number = findNumberInXML("pan", 0 );
                        var tilt:Number = findNumberInXML("tilt", 0);
                        var zoom:Number = findNumberInXML("zoom", 5);
                        var camX:Number = findNumberInXML("cameraX", 0);
                        var camY:Number = findNumberInXML("cameraY", 0);
                        var camZ:Number = findNumberInXML("cameraZ", 0);
                       
                       
                        if (cameraContinuity == "free" || cameraContinuity == "")
                        {
                                camera.rotationX = tilt;
                                camera.rotationY = pan;
                                camera.rotationZ = 0;
                                camera.zoom = zoom;
                                camera.x = camX;
                                camera.y = camY;
                                camera.z = camZ;
                                }
                        else if ( cameraContinuity == "lock" )
                        {
                                camera.rotationX = lastSpace != null ? lastSpace["camera"].rotationX : tilt ;
                                camera.rotationY = lastSpace != null ? lastSpace["camera"].rotationY : pan ;
                                camera.rotationZ = lastSpace != null ? lastSpace["camera"].rotationZ : 0 ;
                                camera.zoom = lastSpace != null ? lastSpace["camera"].zoom : zoom ;
                                camera.x = lastSpace != null ? lastSpace["camera"].x : camX ;
                                camera.y = lastSpace != null ? lastSpace["camera"].y : camY ;
                                camera.z = lastSpace != null ? lastSpace["camera"].z : camZ ;
                        }
                }
               
                private function getNewSpace():int
                {
                        spaces.push( new Object() );
                       
                        var idx:uint = spaces.length-1;
                       
                        var viewport:Viewport3D = new Viewport3D( 640, 480, true, false, true, true);
                       
                        spaces[idx]["viewport"] = viewport;
                       
                        var scene:Scene3D = new Scene3D();
                       
                        spaces[idx]["scene"] = scene;
                       
                        var camera:FreeCamera3D = new FreeCamera3D();
                       
                        spaces[idx]["camera"] = camera;
                       
                        var renderer:BasicRenderEngine = new BasicRenderEngine();
                       
                        spaces[idx]["renderer"] = renderer;
                       
                        //renderer.addEventListener(RendererEvent.RENDER_DONE, onRenderDone);
                       
                        return idx;
                }
               
                private function createBitmapMaterial(xml:XML):BitmapMaterial
                {
                       
                        var material:BitmapMaterial =  unclaimedMaterials[xml.file.toString()];
                       
                        material.oneSide = getBooleanInXML( xml.@oneSide, true );
                       
                        material.smooth = getBooleanInXML( xml.@smooth, false );
                       
                        material.interactive = getBooleanInXML( xml.@interactive, false );
                       
                        material.precise = getBooleanInXML( xml.@precise, false );
                       
                        material.precision = getIntInXML( xml.@precision, 8 );
                       
                        return material
                }
               
                private function sphere(xml:XML):Object
                {
                       
                        var material:BitmapMaterial = createBitmapMaterial(xml);
                       
                        var segments:int = getIntInXML( xml.@segments, 24 );
                       
                        var radius:int = getIntInXML( xml.@radius, 1000 );
                       
                        var sphere:Sphere = new Sphere(material, radius, segments, segments, { rotationY:77, rotationZ:1 } );
                                               
                        return sphere;
                }
               
                private function cube(xml:XML):Object
                {
                       
                        var materials:MaterialsList = new MaterialsList();
                       
                        for each (var file:XML in xml.file)
                        {
                                               
                                var material:BitmapMaterial =  unclaimedMaterials[file.toString()];
                               
                                material.oneSide = getBooleanInXML( xml.@oneSide, true );
                       
                                material.smooth = getBooleanInXML( xml.@smooth, false );
                       
                                material.interactive = getBooleanInXML( xml.@interactive, false );
                               
                                material.precise = getBooleanInXML( xml.@precise, true );
                               
                                material.precision = getIntInXML( xml.@precision, 8 );
                               
                                materials.addMaterial( material, file.@id.toString() );
                        }
                       
                        var insideFaces  :int = Cube.ALL;
                        var excludeFaces :int = Cube.NONE;
                       
                        var segments:int = getIntInXML( xml.@segments, 24 );
                       
                        var width:int = getIntInXML( xml.@width, 100000 );
                       
                        var cube:Cube = new Cube( materials, width, width, width, segments, segments, segments, insideFaces, excludeFaces );
                       
                        return cube;
                }
               
                private function plane(xml:XML):Object
                {
                        var bmd:BitmapData = bulkLoader.getBitmapData(xml.file.toString(), false);
                        var width:Number = (1.5 / bmd.width ) * 40000;
                        var height:Number = (1.5 / bmd.height ) * 40000;
                       
                        var material:BitmapMaterial = createBitmapMaterial(xml);
                       
                        var segments:int = getIntInXML( xml.@segments, 24 );
                       
                        var pan:Number = getNumberInXML( xml.@pan, 0 );
                       
                        var tilt:Number = getNumberInXML( xml.@tilt, 0 );
                       
                        //Plane( material:MaterialObject3D=null, width:Number=0, height:Number=0, segmentsW:Number=0, segmentsH:Number=0, initObject:Object=null )
                        var plane:Plane = new Plane(material, width, height, segments, segments,
                        pinToSphere(40000,pan,tilt) );
                       
                        plane.lookAt(spaces[0]["camera"] as DisplayObject3D);
                       
                        return plane;
                       
                        //tooltipTexts[plane.name] = "Alternate View";
                }
               
               
                private function changeQuality(e:CameraControllerEvent):void
                {
                        for (var i:uint = 0; i < spaces.length; i++)
                        {
                                var objects:Array = spaces[i]["scene"].objects;
                                for ( var j:int=0; j < objects.length; j++ )
                                {
                                        var obj:DisplayObject3D = objects[ j ];
                                       
                                        if (obj is Cube)
                                        {
                                                for each(  var mo3d:MaterialObject3D in obj.materials.materialsByName )
                                                {
                                                        if (mo3d is BitmapMaterial)
                                                        {
                                                                var bm:BitmapMaterial = BitmapMaterial(mo3d);
                                                                if (e.type == CameraControllerEvent.ACCELERATING)
                                                                {
                                      &n