In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Sprite3DSet for Alternativa3D v8

A high performance sprite batch renderer in Alternativa3D that you can use to help render old-school sprites, globally axis-locked sprites, z-locked sprites, etc. in batches, You can use the class to create starfields (star streaks/hyperspace jump), dust, particle systems, etc. The Sprite3DSet Object3D class isn't prescriptive, since the rendering engine is seperate from the particle/material system itself. Do whatever you wish in your own material/particle system, than pump the raw numeric data into the Sprite3DSet's sprite data arrays.
Get Adobe Flash player
by Glidias 01 Aug 2013
/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/2r8A0
 */

package 
{
    //import alternativa.engine3d.spriteset.Sprite3DSet;
    //import alternativa.engine3d.spriteset.util.SpriteGeometryUtil;
    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.Transform3D;
    import alternativa.engine3d.materials.FillMaterial;
    import alternativa.engine3d.materials.TextureMaterial;
    import alternativa.engine3d.primitives.Plane;
    import alternativa.engine3d.resources.BitmapTextureResource;
    import com.bit101.components.TextArea;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.geom.Vector3D;
    import flash.ui.Keyboard;
    use namespace alternativa3d;
    /**
     * A high performance sprite batch renderer in Alternativa3D that you can use to help render old-school sprites, globally axis-locked sprites, z-locked sprites, etc. in batches,
     * You can use the class to create starfields (star streaks/hyperspace jump), dust, particle systems, etc. The Sprite3DSet Object3D class isn't prescriptive, since the rendering
     * engine is seperate from the particle/material system itself. Do whatever you wish in your own material/particle system, than pump the raw numeric data into the Sprite3DSet's sprite data arrays.
     * @author Glenn Ko
     */
    public class SpriteSetTest extends Sprite
    {
        
         private var _template:Template
        private const RADIAN:Number = Math.PI / 180;
         private const DEGREE:Number = 180 / Math.PI;
    
        //[Embed(source = "../../resources/images/test/leaf.png")]
         public static var LEAF:Class;
        
        static public const WORLD_SCALE:Number = 4;
                static private const SPEC_CAM_Z_OFF:Number = 23;
        
        private var ASSET_PATH:String;
        
        private var cardinal:CardinalVectors = new CardinalVectors();
        
        private var _pitchAmount:Number = 0;
        private var _turnAmount:Number = 0;
        private var _horizontalAmount:Number = 0;
        private var _verticalAmount:Number = 0;
        
        private var specCameraController:OrbitCameraController;

        
        public function SpriteSetTest() 
        {
           Wonderfl.disable_capture();
            
            var localMode:Boolean = (loaderInfo.url.indexOf("file://") >= 0);
           localMode = false;

            setup3DView();
        
            stage.addEventListener(Event.RESIZE, onStageResize);
            onStageResize();
            
            
        }
        
        private var _sw:Number;
        private var _sh:Number;
        private var _swPadd:Number;
        private var _shPadd:Number;
        private function onStageResize(e:Event=null):void 
        {
            var xPadd:Number = .2 * stage.stageWidth;
            var yPadd:Number = .2 * stage.stageHeight;
            scrollRegion.x = xPadd;
            scrollRegion.y = yPadd;
            scrollRegion.width = stage.stageWidth - xPadd;
            scrollRegion.height = stage.stageHeight - yPadd;
            _swPadd = 1/xPadd;
            _shPadd = 1/yPadd;
            
            _sw =stage.stageWidth;
            _sh = stage.stageHeight;
            
            scrollSpeed.x = intendedScrollSpeed.x / _sw * .5;
            scrollSpeed.y = intendedScrollSpeed.y / _sh * .5;
        }
        


        private function setupStuff():void {
            
                floor = new Plane(99999, 99999, 1, 1, false, false, null, new FillMaterial(0xDDDDDD));
                
                _template.scene.addChild(floor);
                
            specCameraController = new OrbitCameraController(_template.camera, _specFollowTarget, stage, stage, stage, false, true);
        specCameraController.minDistance = .1;
        specCameraController.setDistance(313, true);
        specCameraController.maxAngleLatidude = 45;
        specCameraController.minAngleLatitude = 2;
        specCameraController.maxPitch = 0;
        
            _template.controller = specCameraController;
            
            
            //_specFollowTarget.addChild(_floor);
            
            //_template.scene.removeChild(_template.rootControl);
            
            var leafData:BitmapData;
            if (LEAF != null) {
                throw new Error( BitmapEncoder.encodeBase64( new LEAF().bitmapData) );
                leafData = new LEAF().bitmapData;
            }
            else leafData = BitmapEncoder.decodeBase64( new Bmp_Leaf().str);
            var mat:TextureMaterial = new TextureMaterial(new BitmapTextureResource(leafData));
            mat.alphaThreshold = .99;
            
            var viewAligned:Boolean = true;
            var child:Object3D = _template.scene.addChild( spriteSetTest =  new Sprite3DSet(1000, viewAligned, mat, 32, 32, 120) );
            spriteSetTest.viewAlignedLockUp = true;
            spriteSetTest.randomisePositions(4);  // bitmask 4 (100), to ensure Z position is clamped to Zero floorn!
            // set normalized tree alignment to bottom ( last 2 parameters for originX,originY (right/up) respectively (0,-1) ) with custom geometry setting
            
            spriteSetTest.geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(spriteSetTest.getMaxSprites(), 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(mat), 1, 0, -1);
            spriteSetAxis = spriteSetTest.setupAxisAlignment(0,0, 1);
            spriteSetTest.bakeSpriteData(true);  
            
            var spriteSetTest2:Sprite3DSet;
            _template.scene.addChild( spriteSetTest2 = new Sprite3DSet(3000, true, mat, 16 , 16, 120));
            spriteSetTest2.randomisePositions(0,0,1200,0,0,33);
            spriteSetTest2.bakeSpriteData();
            
            
            
            refreshCamPosition();
            
            _template.preUpdate = preUpdate;
            _template.preRender = preRender;
            
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        
            _template.initialize();
        }
            
        
        private function preUpdate():void 
        {
            var mx:Number = mouseX;
            var my:Number = mouseY;
            if (autoScrollCamera && ( mx < scrollRegion.x || my < scrollRegion.y || mx > scrollRegion.width || my > scrollRegion.height) ) {
                mx -= _sw * .5;
                my -= _sh * .5;
                translateCamera( 
                mx / _sw * scrollSpeed.x
                ,my / _sh * scrollSpeed.y
                );
            }
            
            
            //specCameraController.setDistance( (specCameraController._angleLatitude-specCameraController.minAngleLatitude)/(specCameraController.maxAngleLatidude - specCameraController.minAngleLatitude) * 600 + 155, true);
        
        }
        
        private function preRender():void {
            
            //throw new Error("A");
        //    if (spriteSetTest) spriteSetTest.rotationX+=.01;
            //debugField.text = String( (specCameraController.maxAngleLatidude ) );
        }
        
    
                
        private var _scrnie:Bitmap;
        private function onKeyDown(e:KeyboardEvent):void 
        {
            var kc:uint = e.keyCode;
            switch(kc) {
                case Keyboard.F6:    
                      _template.takeScreenshot(screenieMethod);
                    
                    
                return;
                case Keyboard.F7:
                      _scrnie= _template.takeScreenshot(screenieMethod2);
                return;
                case Keyboard.O: 
                    
                    pitchAmount += .01 * Math.PI;
                    return
                    
            
                case Keyboard.P: 
                    pitchAmount -= .01 * Math.PI;
                    
                return;
                
                case Keyboard.K: 
                    turnAmount += .01 * Math.PI;
                    return;
                case Keyboard.L:
                    turnAmount -= .01 * Math.PI;
                    return;
                case Keyboard.UP:
                    verticalAmount--;
                    return;
                case Keyboard.W: // diff
                    translateCamera(0, -8);
                    //verticalAmount--;
                //    _template.rootControl.x--;
                    return;
                case Keyboard.DOWN:
                    verticalAmount++;
                    return;
                case Keyboard.S: // diff
                        translateCamera(0, 8);
                    //verticalAmount++;
                //    _template.rootControl.x++;
                    return;
                case Keyboard.LEFT:
                    horizontalAmount--;
                return;
                case Keyboard.A: // diff
                    //horizontalAmount--;
                    translateCamera(-8, 0);
                    return;
                case Keyboard.RIGHT:
                    horizontalAmount++;
                return;
            case Keyboard.D: // diff
                    translateCamera( 8, 0);
                    //horizontalAmount++;
                    return;
                case Keyboard.NUMPAD_ADD:
                    //_template.camera.z++;
                    specCameraController.setDistance(specCameraController.getDistance() + 1);
                    return;    
                case Keyboard.NUMPAD_SUBTRACT:
                    specCameraController.setDistance(specCameraController.getDistance() - 1);
                    return;    
                default:return;
            }
            
            
        }
        
        
        
        private function screenieMethod():Boolean 
        {
        //    Wonderfl.capture(); //
            return true;
        
        }
        
         private function screenieMethod2():Boolean 
        {

            stage.addEventListener(MouseEvent.CLICK, removeScreenie);
            return false;
        }
        
        private function removeScreenie(e:Event=null):void {
            if (_scrnie == null) return;
            stage.removeEventListener(MouseEvent.CLICK, removeScreenie);
            _scrnie.parent.removeChild(_scrnie);
            _scrnie = null;
        }

        
        private function setup3DView():void 
        {
            _template = new Template();
            _template.addEventListener(Template.VIEW_CREATE, initialize);
            addChild(_template);

            //camera.orthographic = true;
            _template.rootControl.z = 254;
            
        //    _template.rootControl.rotationX = Math.PI;
        //    _template.rootControl.rotationZ = DEFAULT_ROT_Z;
            
            //_template.rootControl.rotationY = -.45 * Math.PI;
            
            
        }
        
        private function initialize(event:Event):void {
            _template.removeEventListener(Template.VIEW_CREATE, initialize);
            
                _template.scene.addChild(_specFollowTarget);
        //        _specFollowTarget.addChild( new Box(10, 10, 10, 1, 1, 1, false, new FillMaterial(0xFF0000)));
                
            //マテリアル用のリソースの用意
            var bmd:BitmapData = new BitmapData(256, 256, true, 0xFF000000);
            bmd.perlinNoise(64, 64, 1, 1, true , true);
            var textureResource:BitmapTextureResource = new BitmapTextureResource(bmd);

            
            //Textureの作成
            var diffuseMap:BitmapData = new BitmapData(16, 16, false, 0xFF6666);
            var normalMap:BitmapData = new BitmapData(16, 16, false, 0x8080FF);

            //マテリアルの作成
            bitmapResource = new BitmapTextureResource(diffuseMap);
            normalResource = new BitmapTextureResource(normalMap);
            var materialA:TextureMaterial = new TextureMaterial(bitmapResource);
            var materialB:TextureMaterial = new TextureMaterial(normalResource);
            //var material:TextureMaterial = new TextureMaterial(bitmapResource);
        materialB.alpha = .6;
            materialB.alphaThreshold = .99;    
            
        
            bitmapResource.upload(_template.stage3D.context3D);
            normalResource.upload(_template.stage3D.context3D);
         
           setupStuff();
            

        }

        
        private function onEnterFrameRefresh(e:Event):void 
        {
            removeEventListener(Event.ENTER_FRAME, onEnterFrameRefresh);
                uiSpr.graphics.beginFill(0xFF0000, 0);
                uiSpr.graphics.drawRect(0, 0, uiSpr.width, uiSpr.height);
        }
        
        
        
        private function onVLayoutOver(e:MouseEvent):void 
        {
            
            //trackGridPos = false;
            
            autoScrollCamera = false;
        }
        
        private function onVayoutOUt(e:MouseEvent):void 
        {
        //    trackGridPos = true;
            autoScrollCamera = true;
            //checkGridPos();
        }
        
     
       
        
        
        //private var arcValueList:Array = [];  // for debuggign
        private var debugField:TextArea;

        private var normalResource:BitmapTextureResource;
        private var _floor:Plane;

        private var scrollRegion:Rectangle = new Rectangle();
        private var _specFollowTarget:Object3D = new Object3D();
        private var scrollSpeed:Point = new Point(234, 324);
        private var intendedScrollSpeed:Point = new Point(11222*2, 11222*2);
        private var autoScrollCamera:Boolean=true;
        private var uiSpr:Sprite;
    
        private var bitmapResource:BitmapTextureResource;
        private var spriteSetTest:Sprite3DSet;
        private var spriteSetAxis:Vector3D;
        private var floor:Plane;
        
        public function get pitchAmount():Number 
        {
            
            return _pitchAmount;
        }
        
        
        public function set pitchAmount(value:Number):void 
        {
            _pitchAmount = value;
            

        
                
                
        }
        
        public function get turnAmount():Number 
        {
            return _turnAmount;
        }
        
        public function set turnAmount(value:Number):void 
        {
            _turnAmount = value;

        
            
        }
        
        public function get horizontalAmount():Number 
        {
            return _horizontalAmount;
        }
        
        public function set horizontalAmount(value:Number):void 
        {
            _horizontalAmount = value;
            refreshCamPosition();
        }
        
        public function get verticalAmount():Number 
        {
            return _verticalAmount;
        }
        
        public function set verticalAmount(value:Number):void 
        {
            _verticalAmount = value;
            refreshCamPosition();
        }
        
        private function translateCamera(dispX:Number, dispY:Number):void {

            if (_template.camera.transformChanged) {
                _template.camera.composeTransforms();
            }
            var transform:Transform3D = _template.camera.transform;
    
            var dx:Number = transform.a * dispX + transform.b * dispY;
            var dy:Number =  transform.e * dispX + transform.f * dispY;

        //    var len:Number = 1 / Math.sqrt( dx * dx + dy * dy );
        //    dx *= len;
        //    dy *= len;
            
            _specFollowTarget._x += dx;
            _specFollowTarget._y +=dy;
            
            _horizontalAmount = _specFollowTarget._x * cardinal.east.x + _specFollowTarget._y * cardinal.east.y + _specFollowTarget._z * cardinal.east.z;
            _verticalAmount = _specFollowTarget._x * cardinal.south.x + _specFollowTarget._y * cardinal.south.y + _specFollowTarget._z * cardinal.south.z;
            
            _specFollowTarget.transformChanged = true;

        }
    
        
        private function refreshCamPosition():void 
        {

            _template.camera._x = cardinal.east.x * _horizontalAmount;
            _template.camera._y = cardinal.east.y * _horizontalAmount;
            _template.camera._z = cardinal.east.z * _horizontalAmount;
        
            _template.camera._x += cardinal.south.x * _verticalAmount;
            _template.camera._y += cardinal.south.y * _verticalAmount;
            _template.camera._z += cardinal.south.z * _verticalAmount;
            
            _specFollowTarget._x = _template.camera._x;
            _specFollowTarget._y = _template.camera._y;
            _specFollowTarget._z = _template.camera._z  + SPEC_CAM_Z_OFF;
            
            _template.camera.transformChanged = true;

            _specFollowTarget.transformChanged = true;
            
            
        }
        
        
    
        
        
        
    }
}
import alternativa.engine3d.controllers.SimpleObjectController;
import alternativa.engine3d.core.BoundBox;
import alternativa.engine3d.core.Resource;
import alternativa.engine3d.core.View;
import alternativa.engine3d.lights.AmbientLight;
import alternativa.engine3d.lights.DirectionalLight;
import flash.display.Bitmap;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
import flash.ui.Mouse;
import flash.ui.MouseCursor;


class CardinalVectors {
    public var east:Vector3D = new Vector3D(0, -1, 0);
    public var north:Vector3D = new Vector3D(1,0, 0);
    public var west:Vector3D = new Vector3D(0, 1, 0);
    public var south:Vector3D = new Vector3D(-1, 0, 0);
    
    public function transform(vec:Vector3D, obj:Object3D, distance:Number):void {
        obj.x += vec.x * distance;
        obj.y += vec.y * distance;
        obj.z += vec.z * distance;
    }
    
    public function set(vec:Vector3D, obj:Object3D, distance:Number):void {
        obj.x = vec.x * distance;
        obj.y = vec.y * distance;
        obj.z = vec.z * distance;
    }
    
    public function getDist(vec:Vector3D, boundBox:BoundBox, numGridSquares:uint=1, scaler:Number=2):Number {
        var val:Number =  (boundBox.maxX * vec.x +  boundBox.maxY * vec.y +   boundBox.maxZ * vec.z);
        val = val < 0 ? -val : val;
        val *= numGridSquares;
        val *= scaler;
        return val;
    }
    
}



class Template extends Sprite {
    public var rootControl:Object3D= new Object3D();
    public static const VIEW_CREATE:String = 'view_create'
    
    public var stage3D:Stage3D
    public var camera:Camera3D
    public var scene:Object3D

    public var controlObject:Object3D;
    public var controller:SimpleObjectController;
    
    protected var directionalLight:DirectionalLight;
    protected var ambientLight:AmbientLight;    
    
    //private var _previewCam:Camera3D:
    
    
    public function Template() {
        addEventListener(Event.ADDED_TO_STAGE, init);
    }
    
    private function init(e:Event = null):void 
    {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;
        stage.quality = StageQuality.HIGH;            
        
        //Stage3Dを用意
        stage3D = stage.stage3Ds[0];
        //Context3Dの生成、呼び出し、初期化
        stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
        stage3D.requestContext3D();
    }
    
    
    private function onContextCreate(e:Event):void {
        stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
        //View3D(表示エリア)の作成
        var view:View = new View(stage.stageWidth, stage.stageHeight);
        view.backgroundColor = 0xFFFFFF;
        view.antiAlias = 4
        addChild(view);
        
        //Scene(コンテナ)の作成
        scene = new Object3D();

        //Camera(カメラ)の作成
        camera = new Camera3D(1, 100000);
        camera.view = view;
        scene.addChild(camera)
        
        addChild(camera.diagram)
        
      
        
        //Lightを追加
        ambientLight = new AmbientLight(0xFFFFFF);
        ambientLight.intensity = 0.5;
        scene.addChild(ambientLight);
        
        //Lightを追加
        directionalLight = new DirectionalLight(0xFFFFFF);
        //手前右上から中央へ向けた指向性light
        directionalLight.x = 0;
        directionalLight.y = -100;
        directionalLight.z = 100;
        directionalLight.lookAt(0, 0, 0);
        scene.addChild(directionalLight);
        //directionalLight.visible = false;
        
    
        //コントロールオブジェクトの作成
        
    //    rootControl.rotationX = Math.PI * .5;
        scene.addChild(rootControl);
        controlObject = new Object3D()
        rootControl.addChild(controlObject);
        dispatchEvent(new Event(VIEW_CREATE));
        
        //customiseScene();
        
    }
    
    
    
    public function initialize():void {
        for each (var resource:Resource in scene.getResources(true)) {
     
            resource.upload(stage3D.context3D);
        }
        
        //オブジェクト用のコントローラー(マウス操作)
      //  objectController = new SimpleObjectController(stage, controlObject, 100);
      //  objectController.mouseSensitivity = 0.2;
        
        //レンダリング
        camera.render(stage3D);
        
        addEventListener(Event.ENTER_FRAME, onRenderTick);
    }
    
    public function takeScreenshot( method:Function=null) : Bitmap  //width:int, height:int,
     {
          var view:View = camera.view;
          /*
          var oldWidth:Number = view.width;
          var oldHeight:Number = view.height;  
          view.width = width;
          view.height = height; 
          */
          view.renderToBitmap = true;
          camera.render(stage3D);
         var canvas:BitmapData =  view.canvas.clone();
         // var bitmapData:BitmapData = view.canvas.clone();
          view.renderToBitmap = false;
        //  view.width = oldWidth;
        //  view.height = oldHeight;   
            var child:Bitmap = new Bitmap(canvas);
            stage.addChildAt( child,0 );
         // take screenshot here
             if (method!= null && method() ) {
                stage.removeChild(child);
             }
          return child;
     }

    
    public function onRenderTick(e:Event):void {

        if (preUpdate !=null) preUpdate();
        if (controller) controller.update();
        if (preRender != null) preRender();
        camera.render(stage3D);
        
    }
    
    public var preRender:Function 
    public var preUpdate:Function;

}


//package alternativa.engine3d.spriteset 
//{
    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.DrawUnit;
    import alternativa.engine3d.core.Light3D;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.materials.compiler.Linker;
    import alternativa.engine3d.materials.compiler.Procedure;
    import alternativa.engine3d.materials.compiler.VariableType;
    import alternativa.engine3d.materials.Material;
    import alternativa.engine3d.materials.TextureMaterial;
    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.objects.Mesh;

    import alternativa.engine3d.resources.Geometry;
//    import alternativa.engine3d.spriteset.util.SpriteGeometryUtil;
    import flash.display3D.Context3D;
    import flash.display3D.Context3DVertexBufferFormat;
    import flash.geom.Vector3D;
    import flash.utils.Dictionary;
    use namespace alternativa3d;
    
    /**
     * A 3d object to support batch rendering of sprites.
     * 
     * @author Glenn Ko
     */
    //public
    class Sprite3DSet extends Object3D
    {
        /**
         * Raw sprite data to upload to GPU if number of renderable sprites is lower than batch
         */
        public var spriteData:Vector.<Number>;
        /**
         * Raw sprite data to upload to GPU in batches if number of renderable sprite is higher than batch amount. (If you called bakeSpriteData(), this is automatically created)
         */
        public var staticBatches:Vector.<Vector.<Number>>;
        
        alternativa3d var uploadSpriteData:Vector.<Number>;
        private var toUploadSpriteData:Vector.<Number>;
        private var toUploadNumSprites:int;
        alternativa3d var maxSprites:int;
        alternativa3d var _numSprites:int;
        
        public var height:Number;
        public var width:Number;

    
        
        private var material:Material;
        private var surface:alternativa.engine3d.objects.Surface;
        public function setMaterial(mat:Material):void {
            this.material = mat;
            surface.material = mat;
        }
        
        private static var _transformProcedures:Dictionary = new Dictionary();
        public var geometry:Geometry;
        
        /**
         * Default maximum batch setting (number of uploadable sprites) per batch.
         */
        public static var MAX:int = 80;    
        
        private var NUM_REGISTERS_PER_SPR:int = 1;
        
        private var viewAligned:Boolean = false;
        /**
         * An alternative to "z-locking", if viewAligned is enabled, this flag can be used to lock axis along the local up (z) direction, but still keep the rightward-aligned orientation to camera view.
         */
        public var viewAlignedLockUp:Boolean = false;
    
        private static var UP:Vector3D = new Vector3D(0, 0, 1);
        
        alternativa3d var axis:Vector3D;

        /**
         *  Sets an arbituary normalized axis direction vector along a given direction for non-viewAligned option. The default setting is z-locked (0,0,1), but using
         * this option will allow alignemnt of sprites along a specific editable axis vector.
         * This method automatically disables viewAligned option. and updates the transform procedure to ensure arbituary axis alignment works.
         * @param    x
         * @param    y
         * @param    z
         * @return The axis reference which you can change at runtime
         */
        public function setupAxisAlignment(x:Number, y:Number, z:Number):Vector3D {
            viewAligned = false;
            axis =  new Vector3D(x, y, z);
            if (transformProcedure != null) validateTransformProcedure();
            return axis;
        }
    
          // TODO:
         // create specialised material that uses smallest possible vertex buffer data (2 tuple, quad-corner index and sprite index and spritesheet-animation support) that works with this class.
        
          
        /**
         * Constructor
         * @param    numSprites    The total number of sprites to render in this set
         * @param   viewAligned (Boolean) Whether to fully align sprites to camera screen orienation, or align to a locked axis (up - z) facing towards camera.
         * @param    material    Material to use for all sprites
         * @param    width        Default width (or scaling factor) of each sprite to use in world coordinate 
         * @param    height        Default height (or scaling factor) of each sprite to use in world coordinate
         * @param    maxSprites  (Optional) Default 0 will use static MAX setting. Defines the maximum uploadable batch amount of sprites that can upload at once to GPU for this instance.
         * @param    numRegistersPerSprite  (Optional) Default 1 will only use 1 constant register per sprite (which is the first register assumed to contain xyz position of each sprite). 
         *                                     Specify more registers if needed depending on material type.
         * @param    geometry   (Optional)   Specific custom geometry layout for the spriteset if needed, else, it'll try to create a normalized (1x1 sized geometry sprite batch geometry) to fit according to available material types in Alternativa3D. 
         */
        public function Sprite3DSet(numSprites:int, viewAligned:Boolean, material:TextureMaterial, width:Number, height:Number,maxSprites:int=0, numRegistersPerSprite:int=1, geometry:Geometry=null) 
        {
            super();
            
            this.geometry = geometry;
            this.viewAligned = viewAligned;
            
            NUM_REGISTERS_PER_SPR = numRegistersPerSprite;
            if (maxSprites <= 0) maxSprites = MAX;
            
            
            uploadSpriteData = new Vector.<Number>(((maxSprites*NUM_REGISTERS_PER_SPR) << 2),true);

            this.material = material;
            surface = new alternativa.engine3d.objects.Surface();
            surface.material = material;
            surface.object = this;
            surface.indexBegin = 0;
        
            this.width = width;
            this.height = height;
            
            this.maxSprites = maxSprites;
            
            
            _numSprites = numSprites;
            spriteData = new Vector.<Number>(((numSprites * NUM_REGISTERS_PER_SPR) << 2), true);
        }
        
        /*
        alternativa3d override function calculateVisibility(camera:Camera3D):void {
            
        }
        */
        
        alternativa3d override function setTransformConstants(drawUnit:DrawUnit, surface:alternativa.engine3d.objects.Surface, vertexShader:Linker, camera:Camera3D):void {
            drawUnit.setVertexBufferAt(vertexShader.getVariableIndex("joint"), geometry.getVertexBuffer(SpriteGeometryUtil.ATTRIBUTE), geometry._attributesOffsets[SpriteGeometryUtil.ATTRIBUTE], Context3DVertexBufferFormat.FLOAT_1);
            
            if (!viewAligned) {
                drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("cameraPos"), cameraToLocalTransform.d, cameraToLocalTransform.h, cameraToLocalTransform.l, 0);
                var axis:Vector3D = this.axis || UP;
                drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("up"), axis.x, axis.y, axis.z, 0);  
            }
            else {                
                if (!viewAlignedLockUp) drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("up"), -cameraToLocalTransform.b, -cameraToLocalTransform.f, -cameraToLocalTransform.j, 0)
                else  drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("up"), 0, 0, 1, 0);
                drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("right"), cameraToLocalTransform.a, cameraToLocalTransform.e, cameraToLocalTransform.i, 0);  
            }
            drawUnit.setVertexConstantsFromNumbers(vertexShader.getVariableIndex("spriteSet"), width*.5, height*.5, 0, 0);  
            drawUnit.setVertexConstantsFromVector(0, toUploadSpriteData, toUploadNumSprites*NUM_REGISTERS_PER_SPR ); 
        }
        
        override alternativa3d function collectDraws(camera:Camera3D, lights:Vector.<Light3D>, lightsLength:int, useShadow:Boolean):void {
        
            var spriteDataSize:int;
            var i:int;
            var numSprites:int = _numSprites;
        
            // setup defaults if required
            if (geometry == null) {
                geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(maxSprites, 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(material), 1);
                geometry.upload( camera.context3D );
            }
            if (transformProcedure == null) validateTransformProcedure();
        

                if (_numSprites  <= maxSprites) {
                    toUploadSpriteData = spriteData;
                    toUploadNumSprites = _numSprites;
                    surface.numTriangles = (toUploadNumSprites << 1);
                     surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow);
                }
                else if (staticBatches) {
                    spriteDataSize = NUM_REGISTERS_PER_SPR * 4;
                    for (i = 0; i < staticBatches.length; i++) {
                        toUploadSpriteData = staticBatches[i];
                        toUploadNumSprites = toUploadSpriteData.length / spriteDataSize;
                        surface.numTriangles = (toUploadNumSprites << 1);
                        surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow);
                    }
                }
                else { 
                
                    spriteDataSize = (NUM_REGISTERS_PER_SPR << 2);
                    toUploadSpriteData = uploadSpriteData;
                    for (i = 0; i < _numSprites;  i += maxSprites) {
                        var limit:int = _numSprites - i;  // remaining sprites left to iterate
                        
                        if (limit > maxSprites) limit = maxSprites;
                        toUploadNumSprites = limit;
                        limit += i;
                    
                        var count:int = 0;
                        for (var u:int = i; u < limit; u++ ) {   // start sprite index to ending sprite index
                            var bu:int = u * spriteDataSize; 
                            var d:int = spriteDataSize;
                            while (--d > -1) toUploadSpriteData[count++] = spriteData[bu++];
                        }
                        surface.numTriangles = (toUploadNumSprites << 1);
                    
                        surface.material.collectDraws(camera, surface, geometry, lights, lightsLength, useShadow);
                    
                    }
                }
            
                // Mouse events
                //if (listening) camera.view.addSurfaceToMouseEvents(surface, geometry, transformProcedure);
                //    }
            
                // Debug
                /*
                if (camera.debug) {
                    var debug:int = camera.checkInDebug(this);
                    if ((debug & Debug.BOUNDS) && boundBox != null) Debug.drawBoundBox(camera, boundBox, localToCameraTransform);
                }
                */
        }
        

        /**
         * Sets up geometry according to settings found in this instance.
         * @param    context3D
         */
        public function setupDefaultGeometry(context3D:Context3D = null):void {    
            if (geometry != null) {
                geometry.dispose();
            }
            geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(maxSprites, 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(material), 1);
            if (context3D) geometry.upload(context3D);
        }
        
        /**
         * Sets up transform procedure according to settings found in this instance.
         */
        public function validateTransformProcedure():void {
            transformProcedure = viewAligned ? getViewAlignedTransformProcedure(maxSprites) : axis!= null ? getAxisAlignedTransformProcedure(maxSprites) :  getTransformProcedure(maxSprites);
        }
        
    
        /**
         * Randomise positions of sprites of spriteData, assuming 1st register of each sprite refers to it's x,y,z position. Good for previewing spriteset.
         * @param   mask  (Optional) bitmask of x,y,z (1st,2nd and 3rd value) to set value to zero if mask hits.
         */
        public function randomisePositions(mask:int = 0, maskValue:Number = 0, range:Number = 1200, offsetX:Number = 0, offsetY:Number = 0, offsetZ:Number = 0 ):void {
            var multiplier:int = NUM_REGISTERS_PER_SPR * 4;
            var hRange:Number = range * .5;
            for (var i:int = 0; i < _numSprites; i++ ) {
                var baseI:int = i * multiplier;
                spriteData[baseI] = (mask & 1) ? maskValue :  -hRange + Math.random() * range  +offsetX;
                spriteData[baseI + 1] = (mask & 2) ? maskValue : -hRange + Math.random() * range+offsetY;
                spriteData[baseI + 2] = (mask & 4) ? maskValue : -hRange +  Math.random() * range +offsetZ;
            }
        }
        
        /**
         * Adjust number of sprites in spriteData. This would truncate sprites or add more to the list that can be editable.
         */
        public function set numSprites(value:int):void 
        {
            spriteData.fixed = false;
            spriteData.length  = ((value * NUM_REGISTERS_PER_SPR) << 2);
            spriteData.fixed = true;
            _numSprites = value;
                
        }
        
        /**
         * Will permanently render baked static sprite data information into a set of static batches, if total number of sprites to be drawn exceeds the batch size.
         * This can improve performance a bit for larger sets since you don't need to re-read data one-by-one from existing spriteData, if spriteData isn't changing,
         * or you might wish to use the static batches for your own direct manual editing.
         * @param     flushOldSpriteDataIfPossible (Boolean) Optional. Whether to null away spriteData reference if it exceeds batch size.
         * @return  The baked staticBatches reference for the current instance.
         */
        public function bakeSpriteData(flushOldSpriteDataIfPossible:Boolean = false):Vector.<Vector.<Number>> {
            
            // setup defaults if required
            if (geometry == null) geometry = SpriteGeometryUtil.createNormalizedSpriteGeometry(maxSprites, 0, SpriteGeometryUtil.guessRequirementsAccordingToMaterial(material), 1);
            if (transformProcedure == null) validateTransformProcedure();
            
            staticBatches = new Vector.<Vector.<Number>>();
            
            if (_numSprites <= maxSprites) {
                staticBatches.push(spriteData);
                return staticBatches;
            }
            
            var batch:Vector.<Number>;
            var i:int;

            var spriteDataSize:int = NUM_REGISTERS_PER_SPR * 4;
                    
                    for (i = 0; i < _numSprites;  i += maxSprites) {
                        var limit:int = _numSprites - i;  // remaining sprites left to iterate    
                        if (limit > maxSprites) limit = maxSprites;
                        limit += i;
            
                        var count:int = 0;
                        batch = new Vector.<Number>();
                        for (var u:int = i; u < limit; u++ ) {   // start sprite index to ending sprite index
                            var bu:int = u * spriteDataSize; 
                            var d:int = spriteDataSize;
                            while (--d > -1) batch[count++] = spriteData[bu++];
                        }
                        batch.fixed = true;
                        staticBatches.push(batch);
                    }
            
            staticBatches.fixed = true;
            
            if (flushOldSpriteDataIfPossible) spriteData = null;
            return staticBatches;
        }
        
        public function getMaxSprites():int 
        {
            return maxSprites;
        }
        
        
        
        alternativa3d override function fillResources(resources:Dictionary, hierarchy:Boolean = false, resourceType:Class = null):void {
            if (geometry != null && (resourceType == null || geometry is resourceType)) resources[geometry] = true;
            material.fillResources(resources, resourceType);
            
            super.fillResources(resources, hierarchy, resourceType);
        }
        
        
        private function getTransformProcedure(maxSprites:int):Procedure {
            var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + "_z";
            var res:Procedure = _transformProcedures[key];
            if (res != null) return res;
            res = _transformProcedures[key] = new Procedure(null, "Sprite3DSetTransformProcedure");
            res.compileFromArray([
                "mov t2, c[a0.x].xyz",  // origin position in local coordinate space
                
                "sub t0, c3.xyz, t2.xyz",
                "mov t0.z, c1.w",  // #if zAxis
                "nrm t0.xyz, t0",  // look  (no longer needed after cross products)
                
                "crs t1.xyz, c1.xyz, t0.xyz",  // right      // cross product vs perp dot product for z case
                        
                /* #if !zAxis  // (doesn't work to face camera, it seems only axis locking works)
                "crs t0.xyz, t0.xyz, t1.xyz",  // get (non-z) up vector based on  look cross with right
                "mul t0.xyz, t0.xyz, i0.yyy",   // multiple up vector by normalized xyz coodinates
                "mul t0.xyz, t0.xyz, c2.yyy",
                
                "add t2.xyz, t2.xyz, t0.xyz",
                */
                
                "mul t0.xyz, i0.xxx, t1.xyz",   // multiple right vector by normalized xyz coodinates
                "mul t0.xyz, t0.xyz, c2.xxx",   // scale according to spriteset setting (right vector)
                "add t2.xyz, t2.xyz, t0.xyz",
            
                
                ///*  // #if zAxis
                "mul t0.z, c2.y, i0.y",  // scale according to spriteset setting (fixed axis direction)
                "add t2.z, t2.z, t0.z",
                //*/
                
                "mov t2.w, i0.w",    
                "mov o0, t2",
                
                "#a0=joint",
                //"#c0=array",
                "#c1=up",  // up
                "#c2=spriteSet",
                "#c3=cameraPos"
            ]);
        
            res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
        
            return res;
        }
        
        private function getAxisAlignedTransformProcedure(maxSprites:int):Procedure {
            var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + "_axis";
            var res:Procedure = _transformProcedures[key];
            if (res != null) return res;
            res = _transformProcedures[key] = new Procedure(null, "Sprite3DSetTransformProcedure");
            res.compileFromArray([
                "mov t2, c[a0.x].xyz",  // origin position in local coordinate space
                
                "sub t0, c3.xyz, t2.xyz",
                //"mov t0.z, c1.w",  // #if zAxis
                "nrm t0.xyz, t0",  // look  (no longer needed after cross products)
                
                "crs t1.xyz, c1.xyz, t0.xyz",  // right      // cross product vs perp dot product for z case
                        
                ///* #if !zAxis  // (doesn't work to face camera, it seems only axis locking works)
                "crs t0.xyz, t0.xyz, t1.xyz",  // get (non-z) up vector based on  look cross with right
                "mul t0.xyz, t0.xyz, i0.yyy",   // multiple up vector by normalized xyz coodinates
                "mul t0.xyz, t0.xyz, c2.yyy",
                
                "add t2.xyz, t2.xyz, t0.xyz",
                //*/
                
                "mul t0.xyz, i0.xxx, t1.xyz",   // multiple right vector by normalized xyz coodinates
                "mul t0.xyz, t0.xyz, c2.xxx",   // scale according to spriteset setting (right vector)
                "add t2.xyz, t2.xyz, t0.xyz",
                
                /*  // #if zAxis
                "mul t0.z, c2.y, i0.y",  // scale according to spriteset setting (fixed axis direction)
                "add t2.z, t2.z, t0.z",
                */
                
                "mov t2.w, i0.w",    
                "mov o0, t2",
                
                "#a0=joint",
                //"#c0=array",
                "#c1=up",  // up
                "#c2=spriteSet",
                "#c3=cameraPos"
            ]);
        
            res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
        
            return res;
        }
        
        private function getViewAlignedTransformProcedure(maxSprites:int):Procedure {
            var key:String = maxSprites + "_" + (maxSprites * NUM_REGISTERS_PER_SPR) + "_view";
            var res:Procedure = _transformProcedures[key];
            if (res != null) return res;
            res = _transformProcedures[key] = new Procedure(null, "Sprite3DSetTransformProcedure");
            
            
            res.compileFromArray([
                "mov t2, c[a0.x].xyz",  // origin position in local coordinate space
                
                "mov t1, t2",  //dummy not needed later change
        
                "mul t0.xyz, c2.xyz, i0.xxx",
                "mul t0.xyz, t0.xyz, c3.xxx", // scale according to spriteset setting (right vector)
                "add t2.xyz, t2.xyz, t0.xyz",
                
                "mul t0.xyz, c1.xyz, i0.yyy",
                "mul t0.xyz, t0.xyz, c3.yyy",  // scale according to spriteset setting  (up vector)
                "add t2.xyz, t2.xyz, t0.xyz",
                
                "mov t2.w, i0.w",    
                "mov o0, t2",
                
                "#a0=joint",
                //"#c0=array",
                "#c1=up", 
                "#c2=right",
                "#c3=spriteSet"
            ]);
        
            res.assignConstantsArray(maxSprites*NUM_REGISTERS_PER_SPR);
        
            return res;
        }
        

    
    
           
    }

//}



//package alternativa.engine3d.spriteset.util 
//{
    import alternativa.engine3d.core.VertexAttributes;
    import alternativa.engine3d.resources.Geometry;
    import flash.utils.ByteArray;
    import flash.utils.Dictionary;
    import flash.utils.Endian;
    import alternativa.engine3d.alternativa3d;
    import flash.utils.getQualifiedClassName;
    use namespace alternativa3d;
    
    /**
     * Utility to help work with Sprite3DSet and your own custom sprite materials!
     * @author Glenn Ko
     */
    //public 
    class SpriteGeometryUtil 
    {
        
        public static const REQUIRE_UVs:uint = 1;
        public static const REQUIRE_NORMAL:uint = 2;
        public static const REQUIRE_TANGENT:uint = 4;
        
        public static const ATTRIBUTE:uint = 20;  // same attribute as used in MeshSet
        
        public static var MATERIAL_REQUIREMENTS:Dictionary = new Dictionary();
        
        public static function guessRequirementsAccordingToMaterial(material:*):int {
            if (MATERIAL_REQUIREMENTS && MATERIAL_REQUIREMENTS[material.constructor]) return MATERIAL_REQUIREMENTS[material.constructor];
            var classeName:String = getQualifiedClassName(material).split("::").pop();
            if (MATERIAL_REQUIREMENTS && MATERIAL_REQUIREMENTS[classeName]) return MATERIAL_REQUIREMENTS[classeName];
            
            switch (classeName) {
                case "Material":
                case "FillMaterial": 
                        return 0;  
                case "TextureMaterial": return ( REQUIRE_UVs );
                case "StandardMaterial": return ( REQUIRE_UVs ); return ( REQUIRE_UVs | REQUIRE_NORMAL | REQUIRE_TANGENT );
                default: return (  REQUIRE_UVs | REQUIRE_NORMAL | REQUIRE_TANGENT );
            }
        }
        
        public static function createNormalizedSpriteGeometry(numSprites:int, indexOffset:int, requirements:uint = 1, scale:Number=1, originX:Number=0, originY:Number=0 ):Geometry 
        {
            var geometry:Geometry = new Geometry();
            var attributes:Array = [];
            var i:int = 0;
            
            originX *= scale;
            originY *= scale;
            
            var indices:Vector.<uint> = new Vector.<uint>();
            var vertices:ByteArray = new ByteArray();
            vertices.endian = Endian.LITTLE_ENDIAN;
            
            var requireUV:Boolean = (requirements & REQUIRE_UVs)!=0;
            var requireNormal:Boolean = (requirements & REQUIRE_NORMAL)!=0;
            var requireTangent:Boolean = (requirements & REQUIRE_TANGENT)!=0;
            
            attributes[i++] = VertexAttributes.POSITION;
            attributes[i++] = VertexAttributes.POSITION;
            attributes[i++] = VertexAttributes.POSITION;
            if ( requireUV) {
                attributes[i++] = VertexAttributes.TEXCOORDS[0];
                attributes[i++] = VertexAttributes.TEXCOORDS[0];
            }
            if (requireNormal) {
                attributes[i++] = VertexAttributes.NORMAL;
                attributes[i++] = VertexAttributes.NORMAL;
                attributes[i++] = VertexAttributes.NORMAL;
            }
            if ( requireTangent) {
                attributes[i++] = VertexAttributes.TANGENT4;
                attributes[i++] = VertexAttributes.TANGENT4;
                attributes[i++] = VertexAttributes.TANGENT4;
                attributes[i++] = VertexAttributes.TANGENT4;
            }
            attributes[i++] = ATTRIBUTE;
            
        
            for (i = 0; i<numSprites;i++) {
                vertices.writeFloat(-1*scale - originX);
                vertices.writeFloat(-1*scale - originY);
                vertices.writeFloat(0);
                if ( requireUV) {
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                }
                if ( requireNormal) {
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(1);
                }
                if ( requireTangent) {
                    vertices.writeFloat(1);
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(-1);
                }
                vertices.writeFloat(i+indexOffset);
                
                vertices.writeFloat(1*scale - originX);
                vertices.writeFloat(-1*scale - originY);
                vertices.writeFloat(0);
                if ( requireUV) {
                    vertices.writeFloat(1);
                    vertices.writeFloat(0);
                }
                if ( requireNormal) {
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(1);
                }
                if ( requireTangent) {
                    vertices.writeFloat(1);
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(-1);
                }
                vertices.writeFloat(i+indexOffset);
                
                vertices.writeFloat(1*scale - originX);
                vertices.writeFloat(1*scale - originY);
                vertices.writeFloat(0);
                if ( requireUV) {
                    vertices.writeFloat(1);
                    vertices.writeFloat(-1);
                }
                if ( requireNormal) {
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(1);
                }
                if ( requireTangent) {
                    vertices.writeFloat(1);
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(-1);
                }
                vertices.writeFloat(i+indexOffset);
                
                vertices.writeFloat(-1*scale - originX);
                vertices.writeFloat(1*scale - originY);
                vertices.writeFloat(0);    
                if ( requireUV) {
                    vertices.writeFloat(0);
                    vertices.writeFloat(-1);
                }
                if (requireNormal) {
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(1);
                }
                if ( requireTangent) {
                    vertices.writeFloat(1);
                    vertices.writeFloat(0);
                    vertices.writeFloat(0);
                    vertices.writeFloat(-1);
                }
                vertices.writeFloat(i+indexOffset);
                
                var baseI:int = i * 4;
                indices.push(baseI, baseI+1, baseI+3,  baseI+1, baseI+2, baseI+3);
            }
            
        
            geometry._indices = indices;
            
            geometry.addVertexStream(attributes);
            geometry._vertexStreams[0].data = vertices;
            geometry._numVertices = numSprites * 4;
            
            
            return geometry;
        }
        
    }

//}


    
    

//package alternativa.engine3d.controller {
    



    /**
     * GeoCameraController は 3D オブジェクトの周りに配置することのできるコントローラークラスです。
     * 緯度・経度で配置することができます。
     *
     * @author narutohyper
     * @author clockmaker
     *
     * @see http://wonderfl.net/c/fwPU
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     */
    
 //  public 
 class OrbitCameraController extends SimpleObjectController
    {

        //----------------------------------------------------------
        //
        //   Static Property 
        //
        //----------------------------------------------------------

        /** 中心と方向へ移動するアクションを示す定数です。 */
        public static const ACTION_FORWARD:String = "actionForward";

        /** 中心と反対方向へ移動するアクションを示す定数です。 */
        public static const ACTION_BACKWARD:String = "actionBackward";

        /** イージングの終了判断に用いるパラメーターです。0〜0.2で設定し、0に近いほどイージングが残されます。 */
        private static const ROUND_VALUE:Number = 0.1;
        
        
        private var _lockRotationZ:Boolean = false;
        private var _mouseWheelHandler:Function;
        

        //----------------------------------------------------------
        //
        //   Constructor 
        //
        //----------------------------------------------------------

        /**
         * 新しい GeoCameraController インスタンスを作成します。
         * @param targetObject    コントローラーで制御したいオブジェクトです。
         * @param mouseDownEventSource    マウスダウンイベントとひもづけるオブジェクトです。
         * @param mouseUpEventSource    マウスアップイベントとひもづけるオブジェクトです。推奨は stage です。
         * @param keyEventSource    キーダウン/キーマップイベントとひもづけるオブジェクトです。推奨は stage です。
         * @param useKeyControl    キーコントロールを使用するか指定します。
         * @param useMouseWheelControl    マウスホイールコントロールを使用するか指定します。
         */
        public function OrbitCameraController(
            targetObject:Camera3D,
            followTarget:Object3D,
            mouseDownEventSource:InteractiveObject,
            mouseUpEventSource:InteractiveObject,
            keyEventSource:InteractiveObject,
            useKeyControl:Boolean = true,
            useMouseWheelControl:Boolean = true, mouseWheelHandler:Function=null
            )
        {
            _target = targetObject;
            _followTarget = followTarget;

            super(mouseDownEventSource, targetObject, 0, 3, mouseSensitivity);
            super.mouseSensitivity = 0;
            super.unbindAll();
            super.accelerate(true);

            this._mouseDownEventSource = mouseDownEventSource;
            this._mouseUpEventSource = mouseUpEventSource;
            this._keyEventSource = keyEventSource;

            _mouseDownEventSource.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
            _mouseUpEventSource.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    
            // マウスホイール操作
            _mouseWheelHandler = mouseWheelHandler;
            
            if (useMouseWheelControl)
            {
                _mouseDownEventSource.addEventListener(MouseEvent.MOUSE_WHEEL, (_mouseWheelHandler=mouseWheelHandler || this.mouseWheelHandler));
            }

            // キーボード操作
            if (useKeyControl)
            {
                _keyEventSource.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
                _keyEventSource.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
            }
        }
        
        public function reset():void {
            _angleLongitude = 0;
            _lastLongitude = 0;
            _angleLatitude = 0;
        //    _mouseMove = false;
            if (useHandCursor)
                Mouse.cursor = MouseCursor.AUTO;
            
        }

        //----------------------------------------------------------
        //
        //   Property 
        //
        //----------------------------------------------------------

        //--------------------------------------
        // easingSeparator 
        //--------------------------------------

        private var _easingSeparator:Number = 1.5;

        /**
         * イージング時の現在の位置から最後の位置までの分割係数
         * フレームレートと相談して使用
         * 1〜
         * 正の整数のみ。0 を指定しても 1 になります。
         * 1 でイージング無し。数値が高いほど、遅延しぬるぬるします
         */
        public function set easingSeparator(value:uint):void
        {
            if (value)
            {
                _easingSeparator = value;
            }
            else
            {
                _easingSeparator = 1;
            }
        }
        /** Camera から、中心までの最大距離です。デフォルト値は 2000 です。 */
        public var maxDistance:Number = 2000;

        /** Camera から、中心までの最小距離です。デフォルト値は 200 です。 */
        public var minDistance:Number = 200;

        //--------------------------------------
        // mouseSensitivityX 
        //--------------------------------------

        private var _mouseSensitivityX:Number = -1;

        /**
         * マウスの X 方向の感度
         */
        public function set mouseSensitivityX(value:Number):void
        {
            _mouseSensitivityX = value;
        }

        //--------------------------------------
        // mouseSensitivityY 
        //--------------------------------------

        private var _mouseSensitivityY:Number = 1;

        /**
         * マウスの Y 方向の感度
         */
        public function set mouseSensitivityY(value:Number):void
        {
            _mouseSensitivityY = value;
        }

        public var multiplyValue:Number = 10;

        //--------------------------------------
        // needsRendering 
        //--------------------------------------

        private var _needsRendering:Boolean;

        /**
         * レンダリングが必要かどうかを取得します。
         * この値は update() メソッドが呼び出されたタイミングで更新されます。
         *
         * @see GeoCameraController.update
         */
        public function get needsRendering():Boolean
        {
            return _needsRendering;
        }

        //--------------------------------------
        // pitchSpeed 
        //--------------------------------------

        private var _pitchSpeed:Number = 5;

        /**
         * 上下スピード
         * 初期値は5(px)
         */
        public function set pitchSpeed(value:Number):void
        {
            _pitchSpeed = value
        }

        public var useHandCursor:Boolean = true;

        //--------------------------------------
        // yawSpeed 
        //--------------------------------------

        private var _yawSpeed:Number = 5;

        /**
         * 回転スピード
         * 初期値は5(度)
         */
        public function set yawSpeed(value:Number):void
        {
            _yawSpeed = value * Math.PI / 180;
        }

        /**
         * Zoomスピード
         * 初期値は5(px)
         */
        public function set zoomSpeed(value:Number):void
        {
            _distanceSpeed = value;
        }
        
        public function get lockRotationZ():Boolean 
        {
            return _lockRotationZ;
        }
        
        public function set lockRotationZ(value:Boolean):void 
        {
            _lockRotationZ = value;
        }
        
        public function get followTarget():Object3D 
        {
            return _followTarget;
        }
        
        public function get angleLatitude():Number 
        {
            return _angleLatitude;
        }
        
        public function set angleLatitude(value:Number):void 
        {
            _angleLatitude = value;
            _lastLatitude = value;
        }
        
        public function get angleLongitude():Number 
        {
            return _angleLongitude;
        }
        
        public function set angleLongitude(value:Number):void 
        {
            _angleLongitude = value;
            _lastLongitude = value;
        }
        
        public function get minAngleLatitude():Number 
        {
            return _minAngleLatitude;
        }
        
        public function set minAngleLatitude(value:Number):void 
        {
            _minAngleLatitude = value;
        }
        
        public function get maxAngleLatidude():Number 
        {
            return _maxAngleLatidude;
        }
        
        public function set maxAngleLatidude(value:Number):void 
        {
            _maxAngleLatidude = value;
        }
        


        private var _minAngleLatitude:Number = -Number.MAX_VALUE;
        private var _maxAngleLatidude:Number = Number.MAX_VALUE;

        public var _angleLatitude:Number = 0;
        public var _angleLongitude:Number = 0;
        private var _distanceSpeed:Number = 5;
        private var _keyEventSource:InteractiveObject;
        private var _lastLatitude:Number = 0;
        private var _lastLength:Number = 700;
        private var _lastLongitude:Number = _angleLongitude;
        private var _lastLookAtX:Number = _lookAtX;
        private var _lastLookAtY:Number = _lookAtY;
        private var _lastLookAtZ:Number = _lookAtZ;
        private var _length:Number = 700;
        private var _lookAtX:Number = 0;
        private var _lookAtY:Number = 0;
        private var _lookAtZ:Number = 0;
        private var _mouseDownEventSource:InteractiveObject;
        private var _mouseMove:Boolean;
        private var _mouseUpEventSource:InteractiveObject;
        private var _mouseX:Number;
        private var _mouseY:Number;
        private var _oldLatitude:Number;
        private var _oldLongitude:Number;
        private var _pitchDown:Boolean;
        private var _pitchUp:Boolean;
        private var _taregetDistanceValue:Number = 0;
        public var _taregetPitchValue:Number = 0;
        private var _taregetYawValue:Number = 0;
        public var _target:Camera3D
        private var _yawLeft:Boolean;
        private var _yawRight:Boolean;
        private var _zoomIn:Boolean;
        private var _zoomOut:Boolean;
        public var _followTarget:Object3D;

        //----------------------------------------------------------
        //
        //   Function 
        //
        //----------------------------------------------------------

        /**
         * 自動的に適切なキーを割り当てます。
         */
        public function bindBasicKey():void
        {
            bindKey(Keyboard.LEFT, SimpleObjectController.ACTION_YAW_LEFT);
            bindKey(Keyboard.RIGHT, SimpleObjectController.ACTION_YAW_RIGHT);
            bindKey(Keyboard.DOWN, SimpleObjectController.ACTION_PITCH_DOWN);
            bindKey(Keyboard.UP, SimpleObjectController.ACTION_PITCH_UP);
            bindKey(Keyboard.PAGE_UP, OrbitCameraController.ACTION_BACKWARD);
            bindKey(Keyboard.PAGE_DOWN, OrbitCameraController.ACTION_FORWARD);
        }

        /** 上方向に移動します。 */
        public function pitchUp():void
        {
            _taregetPitchValue = _pitchSpeed * multiplyValue;
        }

        /** 下方向に移動します。 */
        public function pitchDown():void
        {
            _taregetPitchValue = _pitchSpeed * -multiplyValue;
        }

        /** 左方向に移動します。 */
        public function yawLeft():void
        {
            _taregetYawValue = _yawSpeed * multiplyValue;
        }

        /** 右方向に移動します。 */
        public function yawRight():void
        {
            _taregetYawValue = _yawSpeed * -multiplyValue;
        }

        /** 中心へ向かって近づきます。 */
        public function moveForeward():void
        {
            _taregetDistanceValue -= _distanceSpeed * multiplyValue;
        }

        /** 中心から遠くに離れます。 */
        public function moveBackward():void
        {
            _taregetDistanceValue += _distanceSpeed * multiplyValue;
        }

        /**
         * @inheritDoc
         */
        override public function bindKey(keyCode:uint, action:String):void
        {
            switch (action)
            {
                case ACTION_FORWARD:
                    keyBindings[keyCode] = toggleForward;
                    break
                case ACTION_BACKWARD:
                    keyBindings[keyCode] = toggleBackward;
                    break
                case ACTION_YAW_LEFT:
                    keyBindings[keyCode] = toggleYawLeft;
                    break
                case ACTION_YAW_RIGHT:
                    keyBindings[keyCode] = toggleYawRight;
                    break
                case ACTION_PITCH_DOWN:
                    keyBindings[keyCode] = togglePitchDown;
                    break
                case ACTION_PITCH_UP:
                    keyBindings[keyCode] = togglePitchUp;
                    break
            }
            //super.bindKey(keyCode, action)
        }

        /**
         * 下方向に移動し続けるかを設定します。
         * @param value    true の場合は移動が有効になります。
         */
        public function togglePitchDown(value:Boolean):void
        {
            _pitchDown = value
        }

        /**
         * 上方向に移動し続けるかを設定します。
         * @param value    true の場合は移動が有効になります。
         */
        public function togglePitchUp(value:Boolean):void
        {
            _pitchUp = value
        }

        /**
         * 左方向に移動し続けるかを設定します。
         * @param value    true の場合は移動が有効になります。
         */
        public function toggleYawLeft(value:Boolean):void
        {
            _yawLeft = value;
        }

        /**
         * 右方向に移動し続けるかを設定します。
         * @param value    true の場合は移動が有効になります。
         */
        public function toggleYawRight(value:Boolean):void
        {
            _yawRight = value;
        }

        /**
         * 中心方向に移動し続けるかを設定します。
         * @param value    true の場合は移動が有効になります。
         */
        public function toggleForward(value:Boolean):void
        {
            _zoomIn = value;
        }

        /**
         * 中心と反対方向に移動し続けるかを設定します。
         * @param value    true の場合は移動が有効になります。
         */
        public function toggleBackward(value:Boolean):void
        {
            _zoomOut = value;
        }

        private var testLook:Vector3D = new Vector3D();
        /**
         * @inheritDoc
         */
        override public function update():void
        {
            const RADIAN:Number = Math.PI / 180;
            var oldAngleLatitude:Number = _angleLatitude;
            var oldAngleLongitude:Number = _angleLongitude;
            var oldLengh:Number = _lastLength;

            //CameraZoom制御
            if (_zoomIn)
            {
                _lastLength -= _distanceSpeed;
            }
            else if (_zoomOut)
            {
                _lastLength += _distanceSpeed;
            }

            // ズーム制御
            if (_taregetDistanceValue != 0)
            {
                _lastLength += _taregetDistanceValue;
                _taregetDistanceValue = 0;
            }

            if (_lastLength < minDistance)
            {
                _lastLength = minDistance;
            }
            else if (_lastLength > maxDistance)
            {
                _lastLength = maxDistance;
            }
            if (_lastLength - _length)
            {
                _length += (_lastLength - _length) / _easingSeparator;
            }

            if (Math.abs(_lastLength - _length) < ROUND_VALUE)
            {
                _length = _lastLength;
            }

            //Camera回転制御
            if (_mouseMove)
            {
                _lastLongitude = _oldLongitude + (_mouseDownEventSource.mouseX - _mouseX) * _mouseSensitivityX
            }
            else if (_yawLeft)
            {
                _lastLongitude += _yawSpeed;
            }
            else if (_yawRight)
            {
                _lastLongitude -= _yawSpeed;
            }

            if (_taregetYawValue)
            {
                _lastLongitude += _taregetYawValue;
                _taregetYawValue = 0;
            }

            if (_lastLongitude - _angleLongitude)
            {
                _angleLongitude += (_lastLongitude - _angleLongitude) / _easingSeparator;
            }

            if (Math.abs(_lastLatitude - _angleLatitude) < ROUND_VALUE)
            {
                _angleLatitude = _lastLatitude;
            }

            //CameraZ位置制御
            if (_mouseMove)
            {
                _lastLatitude = _oldLatitude + (_mouseDownEventSource.mouseY - _mouseY) * _mouseSensitivityY;
            }
            else if (_pitchDown)
            {
                _lastLatitude -= _pitchSpeed;
            }
            else if (_pitchUp)
            {
                _lastLatitude += _pitchSpeed;
            }

            if (_taregetPitchValue)
            {
                _lastLatitude += _taregetPitchValue;
                _taregetPitchValue = 0;
            }

            _lastLatitude = Math.max(-89.9, Math.min(_lastLatitude, 89.9));

            if (_lastLatitude - _angleLatitude)
            {
                _angleLatitude += (_lastLatitude - _angleLatitude) / _easingSeparator;
            }
            if (Math.abs(_lastLongitude - _angleLongitude) < ROUND_VALUE)
            {
                _angleLongitude = _lastLongitude;
            }
            
            
            if (_angleLatitude < _minAngleLatitude) _angleLatitude = _minAngleLatitude;
            if (_angleLatitude > _maxAngleLatidude) _angleLatitude = _maxAngleLatidude;
            

            var vec3d:Vector3D = this.translateGeoCoords(_angleLatitude, _angleLongitude, _length);
                testLook.x = _followTarget.x;
                testLook.y = _followTarget.y;
                testLook.z = _followTarget.z;
            //    testLook = _followTarget.localToGlobal(testLook);
    
            _target.x = testLook.x + vec3d.x;
            _target.y = testLook.y + vec3d.y;
            _target.z = testLook.z + vec3d.z;

            //lookAt制御
            if (_lastLookAtX - _lookAtX)
            {
                _lookAtX += (_lastLookAtX - _lookAtX) / _easingSeparator;
            }

            if (_lastLookAtY - _lookAtY)
            {
                _lookAtY += (_lastLookAtY - _lookAtY) / _easingSeparator;
            }

            if (_lastLookAtZ - _lookAtZ)
            {
                _lookAtZ += (_lastLookAtZ - _lookAtZ) / _easingSeparator;
            }
            
        

            //super.update()
            updateObjectTransform();
            
            
            
            lookAtXYZ(_lookAtX + testLook.x, _lookAtY + testLook.y, _lookAtZ + testLook.z);
            
    

            _needsRendering = oldAngleLatitude != _angleLatitude
                || oldAngleLongitude != _angleLongitude
                || oldLengh != _length;
        }

        /** @inheritDoc */
        override public function startMouseLook():void
        {
            // 封印
        }

        /** @inheritDoc */
        override public function stopMouseLook():void
        {
            // 封印
        }

        /**
         * Cameraの向く方向を指定します。
         * lookAtやlookAtXYZとの併用は不可。
         * @param x
         * @param y
         * @param z
         * @param immediate    trueで、イージングしないで変更
         */
        public function lookAtPosition(x:Number, y:Number, z:Number, immediate:Boolean = false):void
        {
            if (immediate)
            {
                _lookAtX = x
                _lookAtY = y
                _lookAtZ = z
            }
            _lastLookAtX = x
            _lastLookAtY = y
            _lastLookAtZ = z
        }

        /**
         * 経度を設定します。
         * @param value    0で、正面から中央方向(lookAtPosition)を見る
         * @param immediate    trueで、イージングしないで変更
         */
        public function setLongitude(value:Number, immediate:Boolean = false):void
        {
            if (immediate)
            {
                _angleLongitude = value;
            }
            _lastLongitude = value;
        }

        /**
         * 緯度を設定します。
         * @param value    0で、正面から中央方向(lookAtPosition)を見る
         * @param immediate    trueで、イージングしないで変更
         */
        public function setLatitude(value:Number, immediate:Boolean = false):void
        {
            if (immediate)
            {
                _angleLatitude = value;
            }
            _lastLatitude = value;

        }

        /**
         * Cameraから、targetObjectまでの距離を設定します。
         * @param value    Cameraから、targetObjectまでの距離
         * @param immediate trueで、イージングしないで変更
         */
        public function setDistance(value:Number, immediate:Boolean = false):void
        {
            if (immediate)
            {
                _length = value;
            }
            _lastLength = value;
        }
        
        public function getDistance():Number {
            return _length;
        }

        /**
         * オブジェクトを使用不可にしてメモリを解放します。
         */
        public function dispose():void
        {
            // イベントの解放
            if (_mouseDownEventSource)
                _mouseDownEventSource.removeEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);

            // マウスホイール操作
            if (_mouseDownEventSource)
                _mouseDownEventSource.removeEventListener(MouseEvent.MOUSE_WHEEL, _mouseWheelHandler);

            // キーボード操作
            if (_keyEventSource)
            {
                _keyEventSource.removeEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
                _keyEventSource.removeEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
            }

            // プロパティーの解放
            _easingSeparator = 0;
            maxDistance = 0;
            minDistance = 0;
            _mouseSensitivityX = 0;
            _mouseSensitivityY = 0;
            multiplyValue = 0;
            _needsRendering = false;
            _pitchSpeed = 0;
            useHandCursor = false;
            _yawSpeed = 0;
            _angleLatitude = 0;
            _angleLongitude = 0;
            _distanceSpeed = 0;
            _keyEventSource = null;
            _lastLatitude = 0;
            _lastLength = 0;
            _lastLongitude = 0;
            _lastLookAtX = 0;
            _lastLookAtY = 0;
            _lastLookAtZ = 0;
            _length = 0;
            _lookAtX = 0;
            _lookAtY = 0;
            _lookAtZ = 0;
            _mouseDownEventSource = null;
            _mouseMove = false;
            _mouseUpEventSource = null;
            _mouseX = 0;
            _mouseY = 0;
            _oldLatitude = 0;
            _oldLongitude = 0;
            _pitchDown = false;
            _pitchUp = false;
            _taregetDistanceValue = 0;
            _taregetPitchValue = 0;
            _taregetYawValue = 0;
            _target = null;
            _yawLeft = false;
            _yawRight = false;
            _zoomIn = false;
            _zoomOut = false;
        }

        protected function mouseDownHandler(event:Event):void
        {
            _oldLongitude = _lastLongitude;
            _oldLatitude = _lastLatitude;
            _mouseX = _mouseDownEventSource.mouseX;
            _mouseY = _mouseDownEventSource.mouseY;
            _mouseMove = true;

            if (useHandCursor)
                Mouse.cursor = MouseCursor.HAND;

        
        }

        protected function mouseUpHandler(event:Event):void
        {
            if (!_mouseMove) return;
            
            if (useHandCursor)
                Mouse.cursor = MouseCursor.AUTO;

            _mouseMove = false;
            
        }
        
        

        private function keyDownHandler(event:KeyboardEvent):void
        {
            for (var key:String in keyBindings)
            {
                if (String(event.keyCode) == key)
                {
                    keyBindings[key](true)
                }
            }
        }

        private function keyUpHandler(event:KeyboardEvent = null):void
        {
            for (var key:String in keyBindings)
            {
                keyBindings[key](false)
            }
        }

        private function mouseWheelHandler(event:MouseEvent):void
        {

            _lastLength -= event.delta * 20;
            if (_lastLength < minDistance)
            {
                _lastLength = minDistance
            }
            else if (_lastLength > maxDistance)
            {
                _lastLength = maxDistance
            }
        }

        /**
         * 経度と緯度から位置を算出します。
         * @param latitude    緯度
         * @param longitude    経度
         * @param radius    半径
         * @return 位置情報
         */
        private function translateGeoCoords(latitude:Number, longitude:Number, radius:Number):Vector3D
        {
            const latitudeDegreeOffset:Number = 90;
            const longitudeDegreeOffset:Number = -90;

            latitude = Math.PI * latitude / 180;
            longitude = Math.PI * longitude / 180;

            latitude -= (latitudeDegreeOffset * (Math.PI / 180));
            longitude -= (longitudeDegreeOffset * (Math.PI / 180));

            var x:Number = radius * Math.sin(latitude) * Math.cos(longitude);
            var y:Number = radius * Math.cos(latitude);
            var z:Number = radius * Math.sin(latitude) * Math.sin(longitude);

            return new Vector3D(x, z, y);
        }
    }
//}

//package com.foxarc.images {   
  
    import flash.display.BitmapData;   
    import flash.geom.Rectangle;   
    import flash.utils.ByteArray;          
   // import com.foxarc.util.Base64;   
       
    class BitmapEncoder {   
           
        public static function encodeByteArray(data:BitmapData):ByteArray{   
            if(data == null){   
                throw new Error("data parameter can not be empty!");   
            }   
            var bytes:ByteArray = data.getPixels(data.rect);   
            bytes.writeShort(data.width);   
            bytes.writeShort(data.height);   
            bytes.writeBoolean(data.transparent);   
            bytes.compress();   
            return bytes;   
        }   
        public static function encodeBase64(data:BitmapData):String{   
            return Base64.encodeByteArray(encodeByteArray(data));   
        }   
           
        public static function decodeByteArray(bytes:ByteArray):BitmapData{   
            if(bytes == null){   
                throw new Error("bytes parameter can not be empty!");   
            }   
            bytes.uncompress();   
            if(bytes.length <  6){   
                throw new Error("bytes parameter is a invalid value");   
            }              
            bytes.position = bytes.length - 1;   
            var transparent:Boolean = bytes.readBoolean();   
            bytes.position = bytes.length - 3;   
            var height:int = bytes.readShort();   
            bytes.position = bytes.length - 5;   
            var width:int = bytes.readShort();   
            bytes.position = 0;   
            var datas:ByteArray = new ByteArray();             
            bytes.readBytes(datas,0,bytes.length - 5);   
            var bmp:BitmapData = new BitmapData(width,height,transparent,0);   
            bmp.setPixels(new Rectangle(0,0,width,height),datas);   
            return bmp;   
        }   
           
        public static function decodeBase64(data:String):BitmapData{               
            return decodeByteArray(Base64.decodeToByteArray(data));   
        }          
           
        public function BitmapEncoder() {   
            throw new Error("BitmapEncoder  is a static class!");   
        }   
           
    }   
       
//}   


  import flash.utils.ByteArray;
    
    class Base64 {
        
        private static const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

        public static const version:String = "1.1.0";

        public static function encode(data:String):String {
            // Convert string to ByteArray
            var bytes:ByteArray = new ByteArray();
            bytes.writeUTFBytes(data);
            
            // Return encoded ByteArray
            return encodeByteArray(bytes);
        }
        
        public static function encodeByteArray(data:ByteArray):String {
            // Initialise output
            var output:String = "";
            
            // Create data and output buffers
            var dataBuffer:Array;
            var outputBuffer:Array = new Array(4);
            
            // Rewind ByteArray
            data.position = 0;
            
            // while there are still bytes to be processed
            while (data.bytesAvailable > 0) {
                // Create new data buffer and populate next 3 bytes from data
                dataBuffer = new Array();
                for (var i:uint = 0; i < 3 && data.bytesAvailable > 0; i++) {
                    dataBuffer[i] = data.readUnsignedByte();
                }
                
                // Convert to data buffer Base64 character positions and 
                // store in output buffer
                outputBuffer[0] = (dataBuffer[0] & 0xfc) >> 2;
                outputBuffer[1] = ((dataBuffer[0] & 0x03) << 4) | ((dataBuffer[1]) >> 4);
                outputBuffer[2] = ((dataBuffer[1] & 0x0f) << 2) | ((dataBuffer[2]) >> 6);
                outputBuffer[3] = dataBuffer[2] & 0x3f;
                
                // If data buffer was short (i.e not 3 characters) then set
                // end character indexes in data buffer to index of '=' symbol.
                // This is necessary because Base64 data is always a multiple of
                // 4 bytes and is basses with '=' symbols.
                for (var j:uint = dataBuffer.length; j < 3; j++) {
                    outputBuffer[j + 1] = 64;
                }
                
                // Loop through output buffer and add Base64 characters to 
                // encoded data string for each character.
                for (var k:uint = 0; k < outputBuffer.length; k++) {
                    output += BASE64_CHARS.charAt(outputBuffer[k]);
                }
            }
            
            // Return encoded data
            return output;
        }
        
        public static function decode(data:String):String {
            // Decode data to ByteArray
            var bytes:ByteArray = decodeToByteArray(data);
            
            // Convert to string and return
            return bytes.readUTFBytes(bytes.length);
        }
        
        public static function decodeToByteArray(data:String):ByteArray {
            // Initialise output ByteArray for decoded data
            var output:ByteArray = new ByteArray();
            
            // Create data and output buffers
            var dataBuffer:Array = new Array(4);
            var outputBuffer:Array = new Array(3);

            // While there are data bytes left to be processed
            for (var i:uint = 0; i < data.length; i += 4) {
                // Populate data buffer with position of Base64 characters for
                // next 4 bytes from encoded data
                for (var j:uint = 0; j < 4 && i + j < data.length; j++) {
                    dataBuffer[j] = BASE64_CHARS.indexOf(data.charAt(i + j));
                }
                  
                  // Decode data buffer back into bytes
                outputBuffer[0] = (dataBuffer[0] << 2) + ((dataBuffer[1] & 0x30) >> 4);
                outputBuffer[1] = ((dataBuffer[1] & 0x0f) << 4) + ((dataBuffer[2] & 0x3c) >> 2);        
                outputBuffer[2] = ((dataBuffer[2] & 0x03) << 6) + dataBuffer[3];
                
                // Add all non-padded bytes in output buffer to decoded data
                for (var k:uint = 0; k < outputBuffer.length; k++) {
                    if (dataBuffer[k+1] == 64) break;
                    output.writeByte(outputBuffer[k]);
                }
            }
            
            // Rewind decoded data ByteArray
            output.position = 0;
            
            // Return decoded data
            return output;
        }
        
        public function Base64() {
            throw new Error("Base64 class is static container only");
        }
        
    }
    
    
    
class Bmp_Leaf {
    public var str:String = 'eNrtvQlwrOtZHtjad7W61ZK61VK3WlJL6lZr7VZLrd6l3vd9Vau162z3+Nzje6/t2JjYDNjGJICHSYpJhZoaAmQmIRSTIcnMZKgQimIGB0xMmSIeXJCJzVBQw2KwDcbcd5737UMmCxiTAvvcK/2n/upWn16/d3ued/l+heLtfbSWVyZ1w8OK++PuHcbBicGv1B+unC+uTtyvxt079ENDiq8ULo3/0pecvV+Nu3cMLiwoPhNvGL6Yas3NQRfuj7t19Futil+IVUxUfWJq6U3j9yty947/wenT0+nzpX94GNTfr8bdO963vqenk6eGXy3UdMr75bhzx7t2PAaqP5qji+eT37PhuI8Bd+z4wP7xHNUfztPJK5PUeGX2W01rg/ercneOlN4wRsWrOSpcqyl5PkCJ08Ef3A9MNfcCi865Fe39Cr29jzGcnwnltZD/BGXONZQ+0yAeaKn6YPXNXHv57264ZmdGpkfuV+rteSwMDg7+aihnoMKVkjKQffZ8knIX01S+teCxRcpc6n81mDPtDiv77lfr7XW4x1RDvx6rTlHxUkmJ1gDFTvop0x6kdFtF2YtlClV15C8NU+V2/v8ttpeyS9bJP+89e3EuD4wP9Nwv70t9TODfL0WKk1R7CJ9/MknR+iD5Ct04eyhQHKJobY7c2RHy5gcpeaqh8sPZr5RvdCdzf3aeiGPJ9zn8xq80H+/6zJap+1V+eY/vsNkR5x9rKHU2RMeVfsi/n47K3eTJ9lO4pKNgQUeOWB95coMUrs5QuKahzMXol0vXutdXN1UD/8n7Hahnxn8mlF+lm/fuvtl4suoyLqvvV/nrfzB5+/PyOOujo/1fLJzPIcYD87eGKFjqhc13k7/YQ97sAPmyk2SPDtJ2qIsOM9CLioZClRmKNVgHlFR8oPzlaGH8/0jUZn4iXJn7qaOi4Yv1xyt09cYW1Z+afzd1YtYq7iHjN+L4rm2X7qdDGeNXi78f23Kq6PTZAnCeiqKNQfj4HnKlusmdHiB3ZpA86QnypqboIIX72WEKl+fhH+bwXDV0oZ8O813kr3VR7LSfkhejlL0dh04YqHS7ijhhpvZT15Fy8j6P8A04/nmkaKDb96yeG81jf9r/+yamBr+UPhuD3AYo3hqlUG2YDjI95IgryBnvpj34fH9WQ+HCGu3Hx2g/hXhQ0VOyuYQYMA18MES7iS7aS/dToKKkSBM+pK2n1PkM5S6XKH+9SNfvcr9vw35fT/g6HwzM/l2kpKfT12bfrD00/bd7fo1bNd2r0xgVo1NTirJptf/XIgXgvLqCHFnIO6cgf7mX3PkB2o500faxAjLvpWhlmvYjU2Tz99JBshu+X02JphlxQkOubB+5gQkCpUnoA/xCUwfeoKMM4kn2Ukv5SxN8y/bfPwzNvyzr8mRtS/uRzV3DzvDE277R6SddMR1lb9SUv52g3IOhN33lnt9wJPr+rTXY90c76W5yFhS0m1LQVkJBOzjtuO/KDNBOtJ9WvNCL8BD5Mkra8PXR1lEX8F8PxRvzwIALwALj8BW9FG6oIPMZ6MQi5c83qXBlo9LVGnQAPuDCQK13WH8xlNO/DAmD11d3tfT4m/fesGzP3gVA+gNr9nFKtJV0VO8lX7Wb9iHvraSCFo4UNAv5LuF2M6YgawiP+fE3zvWjbmD9IdoM9UEHumgz2EOHwAKh0ghFqlMUqy1RoKAnZ2II+jBGWfj7wuUCZc7MVLjYwH0r5S+siCdaSrdn6OQVy28Xzy1Gi+Ub1lfG4OMHNw9n6eavbf9G7XZbd0diwHvnzaN0VAN+Kw0Bpw2QDbK2hDuy1h4qaN4HeUP2ZtxqnAqadinI5MZz8P9b4W6yx/rlDBTVsPlZnHN0VFwANpwGRhwGV5yl1OkiZc9WIPstKl5s49ZKhXMjxU8mKHuuo9bT9Tcbj9Y3VdP936h1+HbL7iy1X1+n63dvvW91/a6IX3G5vDkFTKenw+wIbUd7aTc+RPZ4H63B7k2wfyNOvs++3hyATkD+uv2ODljx+F6qB7h/jLw5NQULc/ABRjoqLNFhSgMfMEnx2gqlW6uUaa3LWb7cQwywwScsgB9A/mfz1Hhko8t3bj02rai+3r9/Q6Xq/2FP2PhHVeDQ0+emjx9llu8SE22ZLDrEazNkpUW8HoPclOD007ivhFxVsP2OLliC3bD3HvH3M3YFGQ4UZAt20S50xh4Zgr0r6bhgBvdbo0B+GbY/QcnGCuVaO5RqWih9sk7Z1iZ0AP7/3I6/rRSpqah4uQr5b9DVu3Z+yB3+unIALlZ+OlG30fnrO1R5MPfHzZtNz9SU6g6JXxHUaEfhow045ylYnIUdK8HfTIjjC5DPPHC7mg7SI+RI9NOar4us/i5a80IPPD20E+mlvfgweKASvB8YogTOX7GQNzNLvpwK8rdQ9nQL8rfKWTjbpSLwXwbxIH26RKnWrOB/cA+6eH3jk9mTVe4x/HocnO/4/m23gapP1il/o6fG05UfdHgX7hoH1IyNjX3OkzJQ8sQMP7BCoeoMzmnoA3haYxK4fYqO8Lc3P0muxAgdxEaA64ZpN9wPDAjZJ5TggJPAeUpwfgNF6ybyl8YoUV+h7Mk2pZs2yN4mdp9tI/5f2qhyvUVZ4P7S1TxVbleo/thC5+/c+K3mww2VJ2n4q/7NBzML6n8VSK4BhyIOXSxQ4WbuC/nzFYfybtYs/77DaxC5ZE45Rq9B/pOQP/M1vXD1SN1AsfoyJSHTSHmZ/Hkd4sMo4sMo/P4U7H2efPkp6NA8JU6M8O1rlDu1U77tgE5YEf83KHeGOIDHky0jZdprwITzVHtgpvojK/A/fPDzrf871zKPWq1/JXUgAxD+7dKG7sf2j81fSJ/uUPXWju+xROkzPTWf2n5oz2dQ3NHj1rCkQUw2w1ebqXRhp1RjicIlcLNTxO3TNcTtXdjxFuS/QZHKKoXKi4gLU+QHvvPnpxHvZ4H/p4HngPVPNgTnF845vi8IH0ieWKBbmxILcqfblEHsT5xowQFXqXyzQo0nVsh/9xOJsqX3LzO+j0yNtAwrun/sCq18PtXYptL1NhWvNqGLNnwnyL6tofyVlorXiynDouquyt81bRx6M16bpPzZAvA5+BnsNVqC74cvL5w5RfaZE8i/uUmhyiLkv4DTAJ5ngPznoAeIEWU14ocJfh+vbQBPwIf4CmrBEdHaIuQP/8+yZwyI+8kT/iwHYoCN6g836ezZ7v+VaVqH5ub+wt9/ZUwz8Lc2nXqcutqCRfV8eUP9487jpd89LnKOyQY/s0+Vq33ospkK8EmMRY9rakqeaaj0cP7Tx3nNXa4+LSmnB34vUdWDmxupcgGOdrZBmTp8QFlLGZY9zuzpLuKBlWJVtn8T9MAAX9Cx70hdB/lrETf0FK1uiE6wP2AcGWusARMAWyB2cEwonu+L7PPwD7VbF84dyGeT2s+2fy5WNP5F7T+inlZ+Ln++TVfvXqfmswVKno9TrDUCbMn1Jx101ozP3aXyuVs4Rwq+KFZbhfyVlD7XAXvM/h37wZ3vPfixg+AUtR7DF19vUB0+oNjegf3rIbdFKpweQO4rlJeY7sTjWL/ynPiBGGQbBcfnnE+wNIO4AJ9Q0OGxVcT9LbF3wQDAgqnmOvzJPmXgZ4qsZ1fbVL0BHrxZpdN3bH86WV/4i9j/ic6g+nL11kaNd8xR5kJF8ZYGWHWY/MVBcBHcr0zjsy3yPQrtA/nux+VZfE81dFJDqfY0eJ/m6ermne9jvzSZ1HTzqoPaj2CPVxuIBTbI2wb5Y21PNuXMn+7BD+xQorEqPj5cwRoXtYj/iAG5WfgAFdbXCB+wAu7HOR4H5dp28R2MBXMtRwdPnJmpeu2C/IHDbtj+bXTydO1TicrCwPLy1/R9r00r6j/KXy1S7mYM9j4I2U9SuK4C9xgBFxkBj52kYJkxrAW/oXOmT3aASebgk6YF+5WuoHdPbdfbuzN3Xf6GwcHB32pcbNLFKxtU4rrsObDZ5S6w0hpkZhLfzXZcON+D7ToF2wVKSgoWNYgTi9ADE+xqAjqxBP1YFz+bbtnw2o0OrwD2y0IXEuAIBcTk0sWu5IGrt1vUesVO7VfX/uVx+mvKu76xtD5G6QvwzfN+CjX6KdZUQ/Ya8pdHyVsco8PsKLnAT45KOuCRTch+HXFqCTHLCF6DuH9qpNzlIlUfLgJ3bFZXbPe9Zzh+KpIy0/WzDdgl5+qX4avXqXIJ3NRawGlGDHXhbw9iuBNyXMI6aikOfhiCzI+KS+TJTsD3a0Q3UidmxOBV8D1g/zbH/nn4DBPOKbzejs+wI/bbqfl4l06e7NDZc8u/CGX+XOf/gbUtLdVegQ23eylU70EcH4YejkLmA3SYGyB3DrLPDtN+ekTyUcn6DuzeAb1cgU8Yw3fV4jstUPZyHtxzFva/XJ1fGL2XvkLxdMk8QWevbCIeL77wAWb4bc4LrMCO9YgJ4PPAznn4iTw4Xu5sBf51Tmp9nPNzYc2DZeCqk2XYJDhCjXOKwAkVyKupo+P6GF6/iPfZwft3fH/xeo0Sbfju0/6P70eGvlofknN8cgg+30ClR5N0BNkHawPkr4yQI9VNO7FuciR6aC/ZR84U5I/zqGQEd3GAz+4Aj+rgn6bgBwyS00ieaSl1rqLiA9WPOPyT36g178bJMW/UGpga2dyc6VdYv2Hyt46q+/8gVp8ALlZR9nwWcdooOKBwvgtb5tzeFDDcquC2THsd2G9NuMBRwQD56+kgOUqe/JjU/JgL+IADuZbgAz/0c/9HlV+P+IL4Ub7aA/9eIF+pjw7yCjosKv7NXrR3aM71p3433nviF5yxUYqe9YnP95R6IPth8uZHyJnupy2WfwqyTw9A9iOw/2F83iI+zwFMsozvAD6aB/Yr6yjSnMGppHCzn6KnPRQ7Hf7w8sY3ZEOD5uKq6ieCmfkv1B6bfq1wufypeGPh2/ZD+vCsQelQavqUqv+ytEQXznG1WjHwF3jNsE6n+OXDRDfF4VszFyNUupmn0rWV8peM27fB72cgcy3iODB/3Si5II77/hxknZknV3KCDjPwAflFrLWBDtPT5MnMIS7MIi6PwAcvSi6pfAEO0LZQsDJOe2kF2aIK2kwqPrt13DsxsfCnYRPFJ/bjo3Tc6iZfpYv2c13kyveTuzAEeffRfqaf9rMs+0H4/nHEgXHEgUnIHfGmBj5a0MIX4Pvg/3zFUWCFGeDFKUq2OQcwTsmLiS9lzmYs/V//MDA9OqqI6OZHfsQVmvzNaG2GCtezdPa6GfHQRrUnll9J1le/ZXV9Jru6MVk7DC94Fq2q2Oru1OvmTVXL4Zn2rmyqtqf0IzMj04o/8R3cS/3PXOGFz2ZOlz8VLZs+uu3SLoLjfy3f538LJhET37FE9YdL1HyFc/NWaj2xUvFyDXFdT5GTIcntJGrgBuD6AcjZl+V88DTtxcbF73rSc/h7FvKfoYPUlGCxY2CxaAVYssl1gG2pMxxke2g9oiDrsYKckGH+Yua7bfbJ/7DpihtCfs6TAFe76iNPWSGyt6e7yFceoIPMANkTkDve57AAfwAdc2VH6DCvBAeYgdxh78V5yF4PPV2EPuiB/eckD5S/tFH5Zl3mlmqPVqj1dMOjmvqG9qCy9rnU6qHPxRA3W8/mqPJQD4wKTv7YBqxiAVaxUPWBATFTSaevasGZZql0q/6j4o3686n2+C8elyc/Hshqv5Q71wMfT4EXTVDuWg1+PPvbmfbMe9bsqhHFV6c6H9twTFH7+RJVbhexNrh9gM9/sEq580WKNGZh95BldYiC8Pn+3ALsG7JPsZ3P0l5UBfkPic27UjPkjKvxN2yuoKRYzQLbd+C0S735IDNI1rCCLCGF9Ihmz3iu3EyNh8bPJspzr1o21DuzxuEf3z+epMLDCelPcuW7aDumEJl7CqPw8UPQoUHoRC/k3wf+ybY/iRjEvl5LvpxW8lOxmhUYxCwcMNWy4rPgzy62qXztEPzReoft9+oP7EsDAy8FDrMrlX0/40/NUeOVVcpfcZ+sDidzFu6ZVCE+j1D2gmeuZiVWpy/7qXDVD5mPUupsUrjwUQVYvKiSvGzyZILKD0ao8or6l4JZTWR6fuTPzgPY+ija7qFwoxf6MwwfyTMfBkq0wJ9qU8BRwNXAeO7cMLnTWsR9+NWUtmPrCTUwwLDI3hGdoN3IKLmz8BfAYYm6Fba/I3rggYy2ol3ST8J15Rj0Kn9hptrNNlWvzHTKGPRW95V4Y5girT4KViHvYi/ZU13yun28515yDOcwsN4IdIDtXkkHaSV0QkWBMr5nCfZfnIbP4lzlCkWra9A7i3DYZGsdOrAF/LEmdcj28w34PfPLhMXZB/6Ndaf6S5ynKlyZKHU6i7ilht9UUqQ+DgzL99WScwmUhyjUHAAXgp2UNfjtKuHmrPuhCnxfGby8MUSBag/H9jdTZxP/ldmm6v5TPpd7fmHrI8D2E8BpRpzLwPjT4FoqYKZZrKdR8L7kWPKjkPkM4r6W9uGj+f4B5OGBXuyExqUX/Kg0S4kqOFjDIVw8WDLRbrxPespWgwrIbEhyBYVz8M7LLapeg1+cgz804DdKiPM5YMM8MCLi+wZixWakS/j9fmqCnMAbjgQwRFIJPVDRXpx54AT80xxwHmygqqN4c1lqjuG6Aeu2jO+/itOMv+EbmmqKtlSUv5m9mjO+lL2+DxeWVdR4BL4FrhWr4TtXIPcK11uAq4FzvMx3EQe5J9udH5S8uzc3Lb4wJb9bK33YHjyXe7X2MwoKn/QgLowDI6i0I/+xK3iwYB6G/Cew/hOC1/0FFfzpMPzqoGC7QHFS8rxByDVYGZO+MUdMRfbIGOx9XOTviCuljywIznVUxJpXgBVqW/ADi5DXCG2Eu2kDMX87Cg5XUQFTAheerkEHLPidG/AzOnzPPnIku4Dp+eyhXXA7G163m+wBhpuAvWtg6xq8nxp6oIYOjOJzEfvTk/gOs/A7kD/jv8aicM8j6ATjwUAJfBU2EqzgrMEmGl2/c5jtNg2+nOMnTEp+OphagD80YY2WcK5AB6akT8efU0MmLJdh/OZ+2KNS6rGeLMtICbs1SO3+CPJygQ8JN073IPYCRwNLJc97f8WXHXm8vDF6NGPs/ZZVe//nrcfdtAo7WzxS0Npxp//P5FfQCv62hboRe/uxjkrEbNaLCfIWhkUHdiIjtAt+xo9zL6m/CB2B7MMlK3zvunAwTx5xITlIW5Fe2gr34n4/fDLXBrcpfbIBfV2HbOBL8Hs2oixv2Du4wW6ae5ChM6Feee94c5L8iGvOzBh+C+IAfIAT/mA/OUn7cRViEPxgEetUXocfWAb30MJG1LAPxET2CzU1PgfftdxLoVbPj266+hUv8XGonh56k/1w/YEZNrJKmROsK2LcYXII+o44zPnOFK85fENNg1MFOYzDT3A/7jzWQIv1GRIdOABHYruzw7YOCwryVxXkrUIfEBvswNZrWG89ZD7lUpABtwZPp+9X7VDQNG5XELOdsMc96JEDOrePOMy9P97CiMTjw+ywxKoQuKHgLu4HLCzgO05BVqOQVT9tHvfSDuTPHCB1YqN0axO4kjm6Bj4KsSHc4QTsJ7bj+JwsuH0atp/oBQ7A78VnHeSGRE/2M0r4ABVsYAY2MUdu3HIfqju9iLg0C58AHBLjmaUR4YEcG5LtGcQYLW6ngadm6jPzL7X8OTn9O/nWCl0+X6fqzRrlgFtyLRuFYNeHaWAfYJ7D9Bh8swprynUvyCM/Dn+L+I/f6y+pBRN48BhjZmca9oc4uhOHbwWX2k7AvnCy3W9A/kbIeBLy17shb8ToGZb/roJ0uF3m/v8w59t6aTvSR/Y4OFh8UGY9vCVgw9qgzP4dl1cl1ntywArw1S58P3ucZwZ6If8B+OxBqc3l2y6Jx76iRrC8Dd9rFb5mI9IteroTQxxI98N+OxjABa7AsvdxzjevguwR+xPw+Wn+jCngUvzG5BQ+Z1x8kiMxSt4ssGB+Ht9jGjaioejJjNQga4/Xfyt/aptXz77Um1kmpubG6ezZFl09t1EVnCzTAh5kXFMHjkEcdmNt3cA/QfBdf2EGNoD1SI7A/3IvH+yf57Mhf1eG+/VG5Zbj6Tb88CZkbsO5BDnPuTu+fiHY6f3nv1kWNshixddNS17cBuAjjrrEF7Psme/tJTo9oK60GnxEA96AdQbm9xfmoZOd/LArAzuMDsPuh2g7PCCcnGvCiaYN382IeD4m9m057uBC7kXfiuE74nvuJLvhF3pFP5zwL4z3/WVgD/jz/TTPnU4D+03ie4wi5s9A52ahj1rJSXiyOtgC41UtdAM4CFzUz3Wq+gwVH8x98jg997LvPfHBPa+JHr7bSSePt6hywzW4NcSAFUrBZ8a5rgWcFciPSS0uVOrUYvchfz9w0jGwAuMwD7CCK8293biPdXGmBmCH3WSFLG2hLlr0dfr9lwMdjL0S6ujEor8z/8H59c3QIK0f9ZHFz7Y/2OFf+Jz95LjEXM73HVVVlGrrYNOL0D2T9AO4s4g/wOgd+Q8LXonKXIADz4HfL2olZ7+H7+RMI0akxqCn0Bfo1wb4nvW4699jj4OMCnEccpRTC7ufgdxV8PEjkt/zZDkGzOJxtfSviw7ktKJ/LvgoD3yGD3HyqDYG/DPyLxyBl77282DVpqUHrx9Q85EFHHmDqpe7lD9dB6cyU6xqpEhpnsJFPfnw+4Pci5czgBsMUwIcKgQMH8zPSS6EH/fn5iEr4KTUqMzt2I57ZI7DEuwS+ZtxuxXDmsMnWHCuR3i+Y1i4xXaMdQbxIwbsLbibsRf3A4/Le7rA+zjGR5pTwGgLsEXwsKIJcRn2D5/siI+JjLmvLFFfl16yYFkP7DApdcMwdCFa57gxD53RiA478f4dveuVOOfNGsmXN+HUiz2z/u1Gh/D8centCBRnBevx444E450J4YrMO/aAfQ6yHbzKMRLy/5Xj/NTL3vyhGRtT/Lv65R5dv2anyvUyFc/g+7m+VgavKcDXcq8D1tjPug48zBwgXJ2Af9VijeEbgIv8sHsf7JBjoQ864IbdOGBfW+E+yLVb4u0y+3bY2i54gj3F+GpUcij+EstoVri1YLzkjMTYHdjzXmIM9yck9+OIquGPx2TOm206UIT/LyzCJg14nroj+5MpmQFMAb9Ea2sia67Thiom0ZejEvcS6yBPNV7HuWOO65MS132II77cEk4j/o/9uUrkzP3oAcQ6N3wc456D9PgL+Q/CLwwCB/TJ72T/wfWhgywwBPDEUa2fircTry6svPTtf8+WLWq6eX2Pmg+tVDw3gTdNgQeCyxTgyyDvI3Cro4KOjiXeKl/EAvD/AmwF/tCf1cseLdyz6YOPdCU1kqs/gC+OlIENC/C9GeB6nC7gKy/eNyhyWUAMWYRcuL4PvMm4OwX/kZhCzFVL/z/79j3wLs75uVLjkCP0sWiW1wTyi/AfM1j7QejROFWubHTycBM4AXGryu+tlVyNt8j8TC0yPcwyf5kVPsDx2geZMq9gfO/PL4teHYLjOuHDGIOyrhwkEdPi4/geQzKTwvOpdsSanUg/9LRP9EHmm+An3Kwj2QF8JjDo2dBvxKpjBpXqpd5/gnN1/5M3bKKzd3A/vQryH4MfnaRIBXi2YqAo5zrKWE/EAY4BPtiqG37vKLNIAZE/fDHWj2s1rA/e7DxkxWs9hPVUgjOO4ByQPKvkknJKsUWu33tyjKFmpM8rAEzHtdUD2L/k+BF7hX/zyToQZ93hPAR8e2FZ8sN7jEUKU5SorcscSOYM3LBpFGwa4j0igOX30n3g+j2CKRmnHHI+N8O9HAPwQcO4HZQcsxe+y1vq9HkxB7EnBuRzd0Kwd85BhcEz2C9BB9gveRH3vIgTPnx/9iesX+7chNSJPOCRgfIAVa/nv89zNK9SvNw7GnOTwqd8iLmpdh/FGiOQP/SgrqVodRZy5z48+EzwYR84sC89K/uyHDA3TsDGciboAGJnlnVhXnL2B7BdN+w+2pwFJ9JQoDosuWRfYURyrGzvQeC3jh+fldm+YMEsuN4NvM75V+ZeTsbe4Nl7sY78jysq6OU6/PSi5OQD4CTxKmR/4gDftwv24/0BgogrzMf8xXGp424D7zOX2E9y3IbMM0MyVyr2jb8PuZYIjOHO4z2L4B7ZXsHyhyn2O0q8DnqT0Uj8kJhSZh+0CF3US+1fakIFrfQjsA/wFMbxe8coA7yauxr7BU9q+LV588jL3Ax4MWPsoez5ECVOhuD/YWeQlYdrYPF++N9eYDPYA/R/P6okT0pPB5wTZW6enpc5HW9WL/7fh5jMNTtPnvdu08CfwE5KU7K2rsyIzPcw3hK8DfvxF/Aa7vPMG2H7WmAOneDpPWA6lv++xH/43egAZD9D3HvPtsZ9N+HSOsVrG8AsNpzrIvujMnNDfGZhDL4FMQVx2ZkalHi0lwLfz/ZDt0cFp3GMsSOOu1L4jNiE7DuwFe+SuBEocH2Pazysowb4K/aBi3KGykvSA3QE/eUa1FHJ1OlbxnPDtVmZcQvBdriueQSd5TpT/Ez9XtPq5Msqf+6D+c1QYRS8uR963Ad760IMBF8O9dAWsLyd411oiHYjw7jPuFyNdeGc6Jj4ax9s3w8ewHGZdYHrOIy9GT/5iuznpyH3iRfxUi15dM7feHLT8AkaeUy4NHyMh0/YppP5WpxrAJAV9CEEbuUtTgnWCOaXIIsl6KoZdrYsc1/MvzzQDX4t6xnXAHfi4JRRBTBnr8j+kGsZ8M3eUh/wxqD0FjHPXA+CCwS7xW+Eyqsd+y7yDIpecjyBF2cQn80yD5WNuIVOwI+x7GP1BXATEyVPFynZMkDvl6VX3QcfdVSZ/HV3UrOpUr3U+cB/YHcNQW+7ECu74C+7YB/d4gN47y0vZOcB93WnNOTmGA1exjHACzs+TI2QC7jNndZ1+nLSs1LT8QJL8/xeANghILnySanLca7QlR0Vf+krApOBcwdLsP0iP39a8j1c+/flgL0Rr+1xjsnwTS219F2zDwgCr4VKZukLz57O4nGVzBPvJzkPpRSMzjxkB37DDt/lKwxK//4+1zXyI/BH/fgewO3JAYkHtuN+iQnhMs+hLFOkugh/wn29cyJzHzCOl2McfL70HpYXBcOGK8vSpxytz8s+NDyDkjldAg/lHBrXiRa+HK0uhdXTAy+z7E2DSsVPuI7Ad/PdUh9j+R8iDh4XgeMKw7BvcCDmTpmOze/FYDfAxZ6Ujvxco0/2i0/wZxdhwzrJBbrTmg4vzM+Knz9M6yQGsI1y78ZRdYzSp535Hp77i9bUWO9OnYnlz+/Hc6A8A+7Oj8K21PDJazILzjVX7gk/Li+Kv+Ya5Da42CHHFs71xAdl/wjOI3D8cIOT+HJjkg/sYFCeT8fv5d9W6qfj6nhn9izPNm0Cplwg7pc5RjzhGRSWfxD/Jzon/t4onx+pmoFBrMKJeIaJa+Phqhb2vyA1kkht7sOrG1MvOwf4p3vHA5S87JI6riOlEB1w8R588Jc+7nvKMuYDJ4bcd0OIAcDFzohK6mJs+9yPecC1IOA/X3ZBenY7fnxGML4bt86kSuyTn3tc4/1euZYInw7+xnkWrjHzvBfbmDfD76mRuis/n2svzE3TTTuw3halzyzws2uwPbPoihv4jONBsGiUmsBuZBDxqx9nRy92weccnMuDH3OBxzM2CHDOH3p6DI7C/cMR4NBoeYUiJbPYNNs4x/YAsEmwgP+vrFEMusf9ZtxzyHs8RCureP4aPnteOFK0Cs4EvxFrcG+L6UMr6y/1/CfPZ3z/lneEEhfdwOmQebZj/45k74tZbPhr9qWxftoLD9DuEXxqEFwYMdmV0EgcYPzkeZELcOK57K8PwQE5HrjgAzyQ5SHn08GT92GbXEvgPfxidegV7wOHGOHPc1+fRrADYwDG1Fzbc+O1bHsH0LEj+Iw41p77rfJtC3RhAf5nRvpEU81NmQkKII5I/omxHHz6TnhQsL8XvMydUkpucVfyS4OyH40z0Qdbhu5A5iHOLeQ7GC9c4viyjM80ST9qVOS+LvnFdGMbNs958i153nFxRnpZMqdG6OaK9LAWr0z/sysw16t4uY9vW7apKH0zKvtzOTPdWB+cWBeeZ/GXhkTujAF5D9ZN4CM7YoQLfMydnJa9mNy8P2eigwe9iP3HBeDhMvta2C54gis1KfLzAB86U0rBgJ4MeCNwnL841okDjKew/p35f+CLzLRgLrZ7rjlwXmEfn3lchL+Ar003diEH+OAS4jVwWrq5Cw4IuTSNEpsc0RHZQ4L3j3FGR8XmvczNocuc/3cAv24Dy/I+I/78BOS+KHJmG2cd4O8ici11ZpLjwHFJ2HqsYqNUDXrWclKqYYXv59+rgy4YqXC+RIULI/wI70Ng/vlAzKh5C8xmcB/sT+4GVFKnd+UVwEuIAfDz4SrnZ/tlLwY7fOduGPg5DhknOAcwL/25wnfKwMe5aenX9KYNgs3Yltm/cp3EKzkhPuf+fx6d1UsuOQi87pOeSp300x5XOL4uSE6F474P78n5JT84pTfNnzMu8o/V1uWz43Wr2H1cZLEsMub8zEEcnD02LvjUleQcHnMIYIIY1xgmIX9wvaM++ABwSsg3Ut7Aae3gijLHcu7xQGwHtos32N+YIX98TtUCXTALtks2gPXqi/ABiEP1Vambplsm7jf99UjeZBsd7Ve8RQ6VUqn4ofV97v3kay+oxR9yjGTf7wQ/3gPn88Inhwvwg5BvogS+XduSGd1YzSh8jPugO3mgeazfquAhf3FU8nqBPPfyL0q+xS1cj2PCJGyH836dnArzKObv3FPJdVv2IwHoDeeTmU+4Ep2+tAA4Q6RskpxPHP43WuvwrOMS5+91Emf4+QeJDoZkveO6xEF8Er9lHHhFTfYw23837H0KNr0JrLcB7LYB+W5CB2yQvVXmT+JNM2Xb+L0nnAvQQ8d4btkE/2OlTHNd9i1JNfBdWmbpn+K5z8rlZkWvf0tu8fgDDvcsnTxdAmeBbIqz8IXAPYzVsjo6ho0GYLdxrE22ukWZyg4lccs2Ey4i5uHx4wLipfDyNcjfJr35vM8Tx80AHveA4/sQq7l2yr0EQfD9I7wv2zznWHiWQvIpuB8pm1/sCTAr+WTu+/RzfqIBnlXfwf9twj5XoKsLgs8Zax4Ai3INKQAOchCHniW1kpti/Mb1epb9fmJM8hg++IpY1YbPYb++ju9oFdkLlitwPhK+rc4xXQ/ZLlO2BczRsuPcQ5yBzTe4V8KAeI+Y35qXWfP248Nnlo1FxVv04IL1P9rzTUGPdfht4LTgwHHIIVvfoCzwcbwCXlOeo3QF/Ai4OMYYCTFTYj5uOzMbRmACA/HeT0eMpyoTwHqQEWTM/aNSI5TaWye36st28jmMtyPgU7E6n0bZ20P2/Whw7k0vufZj8MUIz4GLja7K7KX0gPGekNyrA/m7wUfdkLszOi741J2aFWzKXJT3DuT953zQI9avANf8xGcZpG4h/TzwCdzXGgGOTzSWZb68eO6iwtmB7DXGe83l2xti79I32Z6jxqMlunkWecVqW1O8xQ+e7/pOy7aaSpc2Kp4B6zQ3KHe6SI3rJapeWCkJGWWAfbOwzTS4UrRogp/Qiy/lnl431wbAu92ZCam3cd948pT7N8YlFnCOkHtFWB4HyfHOfo45o+TUOd4yr5I5P/hf7rGPn8zCD89JHiZcVQGXwf5r2xKnGa+xP+HawwHigzM20eEkPC8Q18h9D+TPM4SMJ7g2wXuPxU9Ukvt18f6SWe5v0kA/ud6xAN/Smenn/WYYV6Z5f4Jz3m9gG+uwKXtO5sA9Cxc2mWe+fc37m+3bbGNxcVHxNjqertpmvlhs7VDtxoLfrqfSBbBOFWtUGKM4YkManCgHLJzFmaxxHn6544/BwY84V1ro2NMxsBTX4BMnGukb4z6LAGOBFM/3qF9wSLXk+oIFvcQClj3v9cG54eOKBv5DLTklD3QqkOfnLcl8INv+MWIP+3jGd9vHA5KX2APm576xQ3BTN+cRuSaZnJKZkdIV5+fA5apLss9MlHEe+xJgfo4B8Zqts9cQfl8a2DLVANZpmCH3Ndg6z6zuyP4CJcj+wRvef5UuBJenpt6Wc/52pWb4k8cpE3RgAZxHiTigplzdAPtfxLmKv8G5cPsnGDhaMQsfC0uOzCh13XCJdcIsmJr3ceBeUa/EiE6/GPtmzi/xPu7M9Tq14M58L9fluLeScwFOjufcA4L3YLmHSmtSB2aeyD0i20f94Kf9UqvlWgHHF+aknLOSOaIMzy1MS84m1WDsuIEYYsF7GAUfMCdhnWXuz7gjVgXOl7kei/SScz5P9vaCzy/erND1a+4f9oXe9hcqVSpUiu91+ZbebN1u0dWzdWpem6mMNciD72SaiAXAwJkG12B5Txf2AeDMnB/lfkGulxaMgguDOe7ZAIauz8hM1R7k6YVNc27AX+C6zajM+XIfGe/zxByB80qsJwF5Lb8n8Ed9ttN/XzAJZmT92GOuHx4WXu+AH3AI1xsF1lMJ9+Oac6wxJXvMcP4wzjoAPMe94Uelzhwnz3RyHumoMPeivjMve5fyXFIYt/7ipOwD4qsNUO2h5fsOPFt36dpiXvi4n4ln9ujJ8y1q3EzDhibBiYATgdfSvB/vyToeWxEMd8xrCl/O8vbLyf2Bc/CzFsHtgaIGGGxY6r7M/RKNGcEOB1JDmodOGKS/4pDzhqlZ+bvz2CRkPw28sSpzoqw/3B/KeZ5dzuccDwu3d0SV4hM4F80xJtue6ewbc8Lx3C41Y+aNwjerPLs/LfNHgQLnHzv1Bo/MQKk7mCbX6UH3V7t5n5GPbDvWuxV37+Aulm9dXJ7/gjfNeWEF1qZf+gGTtVngRKvsvxhFrD/mfEDhRb28tCi8LsB1U87PI3YnalvA0SvklvkvpeQbODfgSs7IKft+AhOy7PcT09L/wX3Y3HvO8ymsC5wj3geG2w0P0bq/F/Ifkd6svTg4fmK8MzsW4x4EJeVPeV+QQ0rJvlMv9pmEfh6VpoFNlRRuDFOgMiq9qNyTyrUL7uniGjb3t4dq4IonfJ0y/T/2+O/8tWhrM7ND4EJjVL3VUPFKS9mzGfjVWch/Cb5/Xno4josmqY9LTwbWmft8uMeD86zM35I1OzDDkvRgbUUGyR4dl94vzs85Y1z/1cnf7BN4/m83wlh9HDFYL7rBj/O+ALZAN1l9veQIj0n9l3tvOJbwjCjLnvdkzLUOKdHcgs9fkr4ml9QhBhEX+vEd+2TG3wVsyHOnPHPE+4+GSjpwQK30OUebwA0tPcv/52OZqbt+AVIeaP1MHj770etzVH80IzOEyROezeKeSt6zf1x6MLw53hdHh/iplTjve4H9mbdxnXgvzvtnTFCkwf0+wASxadqPacDNpqTvk/tA2fZ3w4jpsY5P5nyLH3hgD7a/E+6H/Lk/ZRA+n32SUurOXOcv3/A1QOYlvnO/DmMFrjNxX6Ij2Ud7nNvEyT173L/K8w1h8A7mn8wLEg2z1PTDzBdbaoqewM+dK6n2ZPpUb7jTF5VbHhhQ/E4SPrH+aBpYWEPx9hBiKF+zbQg22CdzGL7c9Iscv1Zq+978lNR1ZJ4OeJ77gXy8t1/BTDGsc6TO/hqxOzYlWN8e49nPYekFZ9nuc29BSUnxpl5sn2s8W+B7G0GuTQ1IL4IDPmQn2gNbHadMi+tEe7D3Nenj4D3bmDvyfKuX+8fyXM/H53LvRnlZTt5bPF5nH8Hy5zyoljJnOspcasX/R5rjVHgw+fFAcuouX1TuAytbw5S+6IVf5GvvdVGw0o2Y2UPOZA85uG8o3i99Ih0d0L/omWK7X5BaD/dPcH8V47ijwkqnj6+xKv2dO8Dyey9iN8+C8cl1e473R5UpkT/X8HYg841AL20dDQrXt0fH5NpRcdhr8YL3Yba9qAvMC3/kegbXBLkvxJ1RvpA993mYwfGW5bpyXNNJt42UahukhyvR0gI3QAfOJyUGxBDjkmdqqjye/dubzsm7iAFZ73/Rlx2h6Cn3C3TmfJ0p3juFb/m6PQrY6gBkzzMTEy/6o7XSY88zm51eUeZ8BsED/Djz+FBxTTBDpD7xQlbw75EBmevkXlDmeeE65+6mBOdxXX8TnH8PPmM/MSk6kmpPSH4u23LKdQdiDZ3sVcKzyY54n9SyvXlVZ6+2GteNGbOuIaZA/id4DDEtfTpF6XPEjnMDpc708P0q6NwM/IFJ9qyNsw6czlDpVvPfWx1j/XdI9isa/cA/3HQDV7W7EbO7yV1SkKuAM9tFe8kurHPPi/07JmQvFk9WJTyu098Lued0MkPJc1veF7OUnd4RyClnEszHef/kqV72XbDDXne47zgBbI7npk4XKHfeif270UHIfkzqfbwvW/pUI3v/Jho26QkNVaYFj3C/mSuNOJ9RSU2H9YKvI8CyTDUsEieSLZPU7eONzn6CydM5yrQX8HlamTnmeJBscFwwyH51SWDBZGuaCucL/4vTP8/7YnW9jeXu1M4N/bDzSPnFwtU4FR/1UazdQ8d1yL8IeRdxm++RXkGekeI+T39hujMzBR3w5mZkpo5jvU9mJqYl18Y5+0C+MzfIeUDO0XPfJc/t8j5ebGO+wgQd8Hxlmnm4ETxjGb54UWZ0uMbA9X3m6vkL2OPluvD7SG1JrinizatlzifAvQXwNdyLmahapE8ke7ou+w2KDpyw/8f/NZck9vOeIXw/1jDivSDrBr5TY0Vkn26a/6TGT3nECJ6Xa9yuU/3G+qO+yLxldPJt5Q742sn/9a5X+we1x/N09rqGmk+ngPfUlD5Twk4QQ8tDiMnDsm/bEXg574PGPZRy3Qapo810emfznflAjgOsB9wnJPMC0i++9O/nBrkfnHF6jGsKLTMVrhAzihPSHx5ErM5emGS/qk4/oUZmrsrXWtlDjq8hwLPBxzWdzH5xHirCe3PVILsq5Mb7Rrc2KQfZF87BCdu2jtx5HxfuY6hzX8ea9HEneR/qWqeHNyH9pvPQG95nbF04buliieqXuD1fw2mhxoM1unzn+u9XL83Plr9x1xr8yzq4UflmaUPz2czpGj147za1n69S9dECZA//ewk7OINvbIEnVTXSDyFYGevGc7JcMw5zTw3P9sl8z5zgPr/0zpuEA0jOn3M4ae4P08l+X7LXG+uH7KkwC9y2SoWLTSpe87464GeQbf5qAbzOLLM2XC/g3EP1ZlP2juXrA/HsKssn0VwR356qw9ZPdnBuyB7zhbMdKl9YYbMbeO1259ph8BnJBvf1rcoeXrHasvT5xKqL0t8Rq5ooVjFQBvqT4b4PufbYKuXgN3LNNcq3LPADa1S5tlLz8RKdPJ37EWdAOa1462z9qx4fV/gmdKPR6fmRB+Yt1Se4Nn75xg7OTTp5B37bQ9jiNfDS+bzsyxxt6Clc08sMMPtQrtnz/otcU+P6WbxmgU7w/jwW8b3cVyv5oOKy9On4CybImOe3DRL7eWZAZkdyxhe9tguiK+HqKlVu16B3RvBw3jMQ582KzHaXbvRUe2CVPeD5+h98Dahse+NFnXZb9pQvnh3CZrepgP8rX7jgs3c6tdsr3qvPKn6ebZxn3dm/83WmuL8kUp7HY4jx7Avq+L8qfncFOl8DN6jNUwr4MYXH0owhBUdyj+As7IH3UOun3FXfZ4K50fbCyshbIVdU0hqU1Hplj27/2j49fr+THr1/j67eZaeL1xyQv42ar2xS84mdyljvwrWZsucrgsV4n93cGfDRyQx+N35/XSv74HPNhK/Pwj71mGfJeS9XngHId3ro/TJHapA4IHu4ghPw3q5hqQ+wLW5DDp391XkPp7NnuIW/rT9aptrDVfiEeTp5DDmeuSDbfcjVLvsGp1srlDjB50I/eQ4j1eRrRs3hce7hmYdeLHWuEdfifcRNuM/xfFkwQYavJdG0SA0j19yCjW9ITSPfwv0TK841OfOIS/kTPleogL+z8DXpBmNJI3zHNPzSIGJQP6Wv+ih3O/Czh/GRU8PK4NL0/MDL3Atsm19SvWfPvfwjkfTuLySKjs8UWr5fq1z4v1y/8dHF8yA9fE8A8X+Nqo/XqP7YBru0Uvl2mUrXzIc0wGBzlLvQY12XxA6TTa77z8g+gZxP5f1TOP/G1+3luUyew+W5f95zjffo8fL+b+Vp6JBe9lFPtZflekrlq32qP9il9rMFfA8D3by+QKdPTVLD52sI8NwX13V5joRxeQa4LA/9zOH1BehE5WKXalcHuN2ixrWbajjLFwdyzcDSJfd18PUj14QHcl8fc4NsG7I95ZjB1xa0UZHr/oiFZfiM6qWFKldmvBY6cM68YY77gP/wuKj/yW23+qMrm8q/a3WoP7HjUf1rR3Di3xwcaz7ticz9lMM75x1T9b4V4kH3i16wKe2ydl2r1UZ1Ot3HHAcbny2feun6tQA9+2YftV+1UPpCQ9HWKCXbk/DLesh/Qa7DnTvbgC0sSB61Myc3JzNTXI+VOfrkkMzdHqSgB5nOrCXvCcD9Ppk27wnM1/PjGG2V63mXb0309L1G+uC3rtCHvsNG589m8TzG/AvSc9l66KSTB27RldLFHpUv96kKmfM1Yeu3BzhdiPkeue4EP1a+3BG5Fi5g08wpTg0vejgRy8EjqrfrshdOEfIuAS+U8bwKMGb9gQUxh68ttv4Hsarl44ehlW/b3FtMaOdU1tHR/p6v0lPV/2Jd38rH1Ojo6KnJZPjRUMzzu82bIFUfLFDifIDSlyrEVL7WnwXy2EBsXZL9kPl6j7wXG8/Dcl8d5+Z5Xyeez3DwXHFiQPYP4l4fmQMHZmT58x66Tcjt5MEh8J0T8rDQzWuz9K7Xpun5G1yH0YpcaldOcC+XyLd6vQ8MdoDP34N9OnHfCTnboQe7+D++JgT8CJ5TvLDItcjKl5t4LnQN+sN7xPLsBu8/XoS+nTyyUv3hFuS9g3OdTl/ZpYtXPX9QOHf/E19099q0YlgZ04zdpfr/f3oYJyYm3re6vvSp4wxs5toEjGDBuUGNJ5tUuFwBXlMhDo5SBLe8Jxbv0897+LtyA5KP4z0DeU/H46pW4nWk0en55Dms8qVJrhFQgp+t3lhlX4/ShYFum71UqSnp/B0uOn0CW77iPauWIbcl+PEVKp7zNYA3Ifs9yNwJm3dR84ETPh/yv7Hj/gGdPnTLfsB1PH7yCDrxkK/fB58PbJtAHEuda4Hf5oE116j1ZI9aj32/XW6H/o7TvbOuVqsV98d/lg+GT9B/IpFz0u1rYXr1m46hAwt0BFkdn/TAniYocz5DR9UJ8vBMfrlP9vYM1TinrpM9mjmnGmFOwXtONGYQx3USV4uIsa1Hdnr8zjQ9fWeF3jibpmcPHfTGe5/Qa+87o3d+U4muX/XCxoHP2ibo3bLYb+FiRWTK14BrPnLihNyfeKmNU26f+ujkCe975oRt78l1QvNXi1+IlBZ/2Rtf/llPaO3Hgkn7x+wHttbWjk03PDx8L+mvfnB8CxqNxu9xuXd/sdoK/3ah7f98prn9x7WHRmB3JQUqg+TO87xtLx3VRyFbHWIvOGRNS6G6RuprqZZWrq0Xa+rF9q+ehX7v7EHhI4du58PV5QX6plPjF1+5PGjrDYbG3Nzcz6bSIXr2xiW951su6PF74uAHwGrXwG48h3HL12cE5nu0BV5uh4xhyyxz+PKTp4gLj1d5nz568m7/rxQa/sfLK0bz1NTUvaD/EnTBarWqDAYDJ8JeMVtnKXUBzF/ukZl7nicONflailMv6up8zQzY+zlfO4f3S1mG7z78zXIr8rddXueqRiNjdHsr8+P04QvLP0kEjP9hniqI//8uu333k7lS4vOXjyuQZ5Zu3gjQ+fMDunx+SC3IuvlkW+JS45V1nLwv/wadPbfT1fOjfxZNeueHhobupfZXczCP+NmDKPz9aRcdlrtoP99LXviCMNs7OGP+hmdn5iB74/8TzFp+4MC/Wd/cWZ8C7/iPahBRl4k+dLH+Uc/O9J/FWVbGxsauLBbLPziO+j/TOMv/fvu28MXzJ8U/PH+c+8Ozx9kvtR8mv1C/in+6UA//j+Gkv2K1rvbdi+iv/FgdGBv4IYdP8683PCOfsroGPml1DX9iy6P8XKiw9PPBlPF7HK65st4wBUMf+zNrUI0jK33b2cYH9re+prFabtDR42SfvjEzN8Mn+xKedx+a+y+4GPD98ZdycAKk5wUnntTrv+b6ucj/Qyfb77Ov32PwO3g4G94t+vbWAeQ/c78ad1D+Zcj/o6ee9+1Z7q+peift/4jlf/Bex7387+JxsKyfob954XrNZr6P/3fwCBum1fSd5+4b65LqfjXu3tHcXdbRRxr72UPH7P1q3L3j3YmdZfrWkt1uMCjvV+PuHX+rYF+lDxbtuvuluJPHD6Y3zV95f8kxsbAwcb8ad+/4p22P7Vefp7f7rdb7xbiDx889j+/9r2eRtfuVuHsHF4V+7/25w7+XPTDdr8bdOzjhTx+ueL83uK2/X427dyxOKwfoO9uBm33rPfy/g0fKs7ZIf6PlSxjnxu9X4+4dD9zmefqOpmd/cvK+T+sOHh9JbC798beU9g2Li/e5/zt4/NSNf/tzz2Nb9725d+/g/rB/+0bE/tOnXvP9aty9Y3NaNUIfu4x+0L8xf78ad+842TLp6HsfptOGmfvYfweP/67oWvvyB8uu+6avu3fwbMZn3l08/PET3+r9aty9wzgxMUjf3vZ/yLdxP69xB4/q1sosffdVIGSYHrlfjbt3fOB4b/nND7XdRp1u8H417t7xv1/nd3/pWWn3fiXu5vGZ9196/1HtaOV+Je7eodHpxn7/o8/C3xzYvcd+d/DYCfms9N3Pw/fzHnfzOFwyTtN/80YstGOZvF+Nu3c0nBtz9F2vRde12tH71bh7x4cr6R360CtHs4r7qu9dPL7v6YX3zQ89Cao8G4b71bh7x/ef5He//OGnwYnA9sL9aty945/fNg9+8lHdcb8Sd/P4+WfXnr93krTdr8TdO3gvv//z1Qfuv9lKWu5X48Xx1xV/vev/A1RdM9Y=';
}