MeshSetClonesContainer for Alternativa3D v8

by Glidias forked from forked from: MeshUtility (diff: 4179)
A modified MeshSet class that allows you to create a dynamic hierachy of meshes (or just a single mesh) (all sharing the same material) which can than be cloned, and than dynamically added  and removed from this container to support batch rendering at varying numbers. Depending on your settings (meshes per surface) and how many meshes are found in your hierachy, you could batch render a certain number of mesh-hierachies at once, saving on your draw calls! The advantage of using this over MeshSet is that this container is supports dynamic cloning, allowing you to add/remove the clones as you wish, as well as reusing a much smaller geometry footprint per batch draw call instead of naively saving entirely static duplicate geometry like what MeshSet does. These 2 differences make it work great with particle systems and crowds. This container isn't recursive, though, and it should not exist under another MeshSet or MeshSetClonesContainer.
♥0 | Line 3144 | Modified 2013-08-07 04:19:06 | MIT License
play

ActionScript3 source code

/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/pp5A
 */

package 
{
    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.Resource;
    import alternativa.engine3d.loaders.events.TexturesLoaderEvent;
    import alternativa.engine3d.loaders.ParserA3D;
    import alternativa.engine3d.loaders.ParserMaterial;
    import alternativa.engine3d.loaders.TexturesLoader;
    import alternativa.engine3d.materials.FillMaterial;
    import alternativa.engine3d.materials.Material;
    import alternativa.engine3d.materials.TextureMaterial;
    import alternativa.engine3d.materials.VertexLightTextureMaterial;
    import alternativa.engine3d.objects.Mesh;
    import alternativa.engine3d.objects.MeshSet;
    //import alternativa.engine3d.objects.MeshSetClone; 
    //import alternativa.engine3d.objects.MeshSetClonesContainer;
    import alternativa.engine3d.objects.Surface;
    import alternativa.engine3d.primitives.Box;
    import alternativa.engine3d.primitives.Plane;
    import alternativa.engine3d.resources.BitmapTextureResource;
    import alternativa.engine3d.resources.ExternalTextureResource;
    import alternativa.engine3d.resources.TextureResource;
    import com.bit101.components.CheckBox;
    import com.bit101.components.Label;
    import com.bit101.components.PushButton;
    import com.bit101.components.Slider;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.filters.DropShadowFilter;
    import flash.ui.Keyboard;
    import flash.utils.ByteArray;
    import alternativa.engine3d.materials.StandardMaterial;
    import flash.utils.getTimer;
    use namespace alternativa3d;
    /**

     * Crowds plenty!
     * @author Glenn Ko
     */
    public class DanboClonesDemo extends Sprite
    {
        
         private static var AREA_W:uint = 12000;
        private static var AREA_H:uint = 12000;
        private static var FPS:uint = 60;
         private var _lastTime:int = 0;
        private static const MS:Number = 1 / 1000;
        

        //[Embed(source="../../bin/resources/danbo_textures/atlas.png")]
        public static var DANBO_TX:Class;
        
        
        
        private var _scrnie:Bitmap;
        private var ASSET_PATH:String;
        private var _template:Template;
        
        private var bitmapResource:BitmapTextureResource;
        private var normalResource:BitmapTextureResource;
        
        
        private var injectMaterial:StandardMaterial;
        private var previewMaterial:StandardMaterial;
        private var _textureLoader:TexturesLoader;
        
        private var danboAdam:Danbord;
        private var danboSet:MeshSetClonesContainer;
        private var player:DanboPlayer;
        

        
          //ボタン、ラベル
        private var btnAdd:PushButton;
        private var btnRemove:PushButton;
        //private var btnCamera:PushButton;
        private var labelTotal:Label;
       // private var labelCamera:Label;
        private var labelZoom:Label;
        private var sliderZoom:Slider;
        
        
        public function DanboClonesDemo() 
        {
           Wonderfl.disable_capture();
            
            var localMode:Boolean = (loaderInfo.url.indexOf("file://") >= 0);
           localMode = false;
            
            ASSET_PATH =  (localMode ? "" : "http://glidias.uphero.com/") + "assets/";
          
        
            setup3DView();
            setupGUI();
            setupCrosshair();
            
        }
        
        private function setupCrosshair():void 
        {
               
            var child:DisplayObject = addChild( new CrossHair() );
            child.x = stage.stageWidth * .5;
            child.y = stage.stageHeight * .5;
            child.filters = [new DropShadowFilter(1,45,0,1,0,0,1,1,false,false, false)];
        }
        
        private function addDanboHandler(e:Event):void {
            addDanbo();
        }
        
        private function removeDanboHandler(e:Event):void {
            removeDanbo();
        }
        
        private function sliderZoomChange(e:Event):void {
             player.zoom = sliderZoom.value;
        }
        
        private function setupGUI():void 
        {
             //ボタン
            btnAdd = new PushButton(this, 80, 5, "AddDanbord", addDanboHandler);
         
            btnRemove = new PushButton(this, 80, 30, "RemoveDanbord", removeDanboHandler);
            labelTotal = new Label(this, 80, 55, "TotalDanbord = " + danboCount);
            
          //  btnCamera = new PushButton(this, 210, 5, "CameraChange", cameraModeChange);
          //  labelCamera = new Label(this, 210, 30, "CameraMode = " + cameraModeLabels[cameraMode]);
            
             cbStandardMaterial = new CheckBox(this, 210, 40, "StandardMat", cbStandardChanged);
             cbStandardMaterial.selected = true;
            sliderZoom = new Slider(Slider.HORIZONTAL, this, 210, 5, sliderZoomChange);
            sliderZoom.minimum = 0;
            sliderZoom.maximum = 1;
            sliderZoom.addEventListener(MouseEvent.MOUSE_DOWN, stopEventPropagate);
        
            labelZoom = new Label(this, 210, 15, "CameraZoom ");
            
            
        }
        
        private function cbStandardChanged(e:Event):void 
        {
        
            danboSet.material =cbStandardMaterial.selected ? danboStandardMaterial : danboTextureMaterial;
            //Danbord.MATERIAL = danboStandardMaterial;
            //player.danbo.= danboStandardMaterial;
        }
        
        private function stopEventPropagate(e:Event):void 
        {
            e.stopPropagation();
        }

        
        // -- CUSTOMISATION ------------
        
        private function doLoadingOfAssets():Boolean {
            
            handleModelLoaded();
            return false;
        }
        
        private function preUpdate():void 
        {
            
        // nothing here for now
        }
        
        private function preRender():void {
            
            var curTime:Number = getTimer();
            var timeElapsed:Number = _lastTime != 0 ? curTime-_lastTime : 30;
            timeElapsed *= MS;
            _lastTime = curTime;
            
            var myDanbord:Danbord;
            
            for (var danboClone:DanboClone = activeDanboList; danboClone != null; danboClone = danboClone.next) {
                myDanbord = danboClone.danbo;
                //ジャンプしていないときに、一定確率でモーションをランダムに変更
                
                if (Math.random() < 0.01 && myDanbord.ground) {
                    myDanbord.setMotionRandom();
                }
                //移動
                myDanbord.x += Math.sin(-myDanbord.bodyR) * myDanbord.sp;
                myDanbord.y += Math.cos( -myDanbord.bodyR) * myDanbord.sp;
                
                //画面端に移動したら反射
                if (myDanbord.x < -AREA_W * 0.5) {
                    myDanbord.x = -AREA_W * 0.5 - (AREA_W * 0.5 + myDanbord.x);
                    myDanbord.bodyR = Math.PI - myDanbord.bodyR + Math.PI;
                    myDanbord.danBody._rotationZ = myDanbord.bodyR;
                    myDanbord.danBody.transformChanged = true;
                }else if (myDanbord.x > AREA_W * 0.5) {
                    myDanbord.x = AREA_W * 0.5 + (AREA_W * 0.5 - myDanbord.x);
                    myDanbord.bodyR = Math.PI - myDanbord.bodyR + Math.PI;
                                    myDanbord.danBody._rotationZ = myDanbord.bodyR;
                    myDanbord.danBody.transformChanged = true;
                }
                if (myDanbord.y < -AREA_H * 0.5) {
                    myDanbord.y = -AREA_H * 0.5 - (AREA_H * 0.5 + myDanbord.y);
                                    myDanbord.danBody._rotationZ = myDanbord.bodyR;
                    myDanbord.danBody.transformChanged = true;
                }else if (myDanbord.y > AREA_H * 0.5) {
                    myDanbord.y = AREA_H * 0.5 + (AREA_H * 0.5 - myDanbord.y);
                    myDanbord.bodyR = Math.PI - myDanbord.bodyR;
                                    myDanbord.danBody._rotationZ = myDanbord.bodyR;
                    myDanbord.danBody.transformChanged = true;
                    
                }
                
                
                //ジャンプ時の挙動
                if (!myDanbord.ground) {
                    myDanbord.spZ -= 9.8 / FPS;
                    myDanbord._z += myDanbord.spZ;
                    if (myDanbord._z <= 0) {
                        myDanbord._z = 0;
                        myDanbord.spZ = 0;
                        myDanbord.ground = true;
                        myDanbord.transformChanged = true;
                        myDanbord.setMotionRandom(false);
                    }
                }
                
                //パーツのモーション
                myDanbord.enterFrameEvent2();
                
                //影を足元に移動
               // var myKage:Object3D = kages[i];
              //  myKage.x = myDanbord.x;
               // myKage.y = myDanbord.y;
               
            }

            
            player.update(timeElapsed);
            
            player.preRender();
        }
        
        private function setupStartingScene():void 
        {

            Danbord.precomputeAll();
            
            var bmpData:BitmapData;
            if (DANBO_TX != null) {
                throw new Error( BitmapEncoder.encodeBase64( new DANBO_TX().bitmapData) );
                bmpData = new DANBO_TX().bitmapData;
            }
            else bmpData = BitmapEncoder.decodeBase64( new Bmp_Tx().str);
            
        
            var diffuse:TextureResource = new BitmapTextureResource( bmpData);
            danboStandardMaterial = new StandardMaterial( diffuse, normalResource);
            danboStandardMaterial = new StandardMaterial( diffuse, normalResource);
            danboTextureMaterial = new TextureMaterial( diffuse);
            Danbord.MATERIAL = danboStandardMaterial;
            //VertexLightTextureMaterial
            danboAdam = new Danbord();
            danboAdam.setupBlueprintHierachy();
            ////danboAdam.scaleX = .05;
            //danboAdam.scaleY = .05;
            //danboAdam.scaleZ = .05;

            
            danboSet = new MeshSetClonesContainer(danboAdam, danboStandardMaterial, 40, DanboClone);
            
            _template.rootControl.addChild(danboSet);
            
            allocateDanboPool(60);
            //for (var i:int = 0; i < 44; i++) {
            //    addDanbo(danboCount * 30);
            //}
            
            //orbitCamController = new OrbitCameraController(_template.camera, new Object3D(), stage, stage, stage, true, true);
            
            
        //    _template.rootControl.addChild( danboAdam.clone() );
            var planeMat:FillMaterial = new FillMaterial(0x614F3E);
            var plane:Plane = new Plane(AREA_W + 80, AREA_H + 80, 1, 1, false, false, planeMat, planeMat);
            _floorPlane = plane;
         
            var cell:Box = new Box(AREA_W + 80, AREA_H + 80, AREA_W + 1000, 1, 1,1, true);
            var collisionObjects:Object3D= new Object3D();
           collisionObjects.addChild(plane);
            
            collisionObjects.addChild(cell);
            _template.rootControl.addChild(collisionObjects);
        
            
        
            var playerClone:DanboClone =  danboSet.createClone() as DanboClone;
            
           var playerDanbord:Danbord =playerClone.root as Danbord;
           playerDanbord.ai = false;
playerDanbord.z = 238;
           //_template.rootControl.addChild(playerDanbord);
           danboSet.addClone(playerClone);
           player = new DanboPlayer(playerDanbord, _template.camera, collisionObjects, stage);
        
           player.thirdPerson.rayCollisionScene =  _floorPlane;
           sliderZoom.value = player.getZoomRatio(); 
                
        }
        
        
        private var activeDanboList:DanboClone;
        private var pooledDanboList:DanboClone;
        private var danboStandardMaterial:StandardMaterial;
        private var danboTextureMaterial:TextureMaterial;
        
        private var danboCount:int = 1;
        private var _floorPlane:Plane;
        private var cbStandardMaterial:CheckBox;
        
        private function addDanbo( ):void {
            danboCount++;
            
            var me:DanboClone;
            if (pooledDanboList != null) {
                me = pooledDanboList;
                pooledDanboList = me.next;
            }
            else {
                me = danboSet.createClone() as DanboClone;
                me.danbo = me.root as Danbord;
            }
            me.next = activeDanboList;
            activeDanboList = me;
            
            
            me.root.transformChanged = true;
            
              var mySc:Number = Math.random() * 1 + 0.5;
            
            var myDanbord:Danbord = me.danbo;
        
            
        //    System.setClipboard( Danbord.STAND.getCSS(myDanbord) + Danbord.WALK.getCSS(myDanbord) + Danbord.DASH.getCSS(myDanbord)   );
            //myDanbord.
            myDanbord._x = Math.random() * AREA_W - AREA_W * 0.5;
            myDanbord._y = Math.random() * AREA_H - AREA_H * 0.5;
            myDanbord._scaleX = myDanbord._scaleY = myDanbord._scaleZ = mySc;
          
          //  if (ai) {
             //   danbords.push(myDanbord)
                myDanbord.bodyR = Math.random() * Math.PI * 2;
               myDanbord.danBody.rotationZ = myDanbord.bodyR;
          //  }
           // else {
                //
           // }
            
            /*
            var myKage:Kage = new Kage(textures);
            myKage.x = myDanbord.x
            myKage.y = myDanbord.y
            myKage.scaleX = myKage.scaleY = myKage.scaleZ = mySc;
            kageContainer.addChild(myKage);
            myKage.calculateBounds();
            */
           
         //   if (ai) kages.push(myKage);  // to take away this
        //    myDanbord.kage = myKage;

            labelTotal.text = "TotalDanbord = " + String(danboCount);
            
            danboSet.addClone( me);

        }
        
        private function allocateDanboPool(amount:int):void {
            if (amount == 0) return;
            var head:DanboClone;
            var tail:DanboClone = head =  danboSet.createClone() as DanboClone;
            tail.danbo = tail.root as Danbord;
            amount--;
            while (--amount > -1) {
                var me:DanboClone = danboSet.createClone() as DanboClone;
                me.danbo = me.root as Danbord;
                tail.next = me;
                tail = me;
            }
            tail.next = pooledDanboList;
            pooledDanboList = head;
        }
        
        private function removeDanbo():void {
            if (activeDanboList == null) return;
            var me:DanboClone = activeDanboList;
            activeDanboList = me.next;
            me.next = pooledDanboList;
            pooledDanboList = me;
            danboSet.removeClone(me);
            danboCount--;
            labelTotal.text = "TotalDanbord = " + String(danboCount);
        }
        
        
        private function handleModelLoaded():void {
    
            // Let's go!
            startGame();    
        }
        
        private function startGame():void {
              stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
            _template.preUpdate = preUpdate;
            _template.preRender = preRender;
        }
        
        //-------------

    
        
        private function applyMaterial(objects:Vector.<Object3D>, injectMaterial:Material):void {
            var sk:Mesh;
            var sLen:int = objects.length;
              for (var s:int = 0; s < sLen; s++) {
                  var child:Object3D = objects[s];
        

                sk = objects[s] as Mesh;

                if (sk == null) continue;
                 //   sk.geometry.calculateNormals();
                 //   sk.geometry.calculateTangents(0);
                    
                    sk.geometry.upload(_template.stage3D.context3D);
        
                for (var i:int = 0; i < sk.numSurfaces; i++){ //cycle through all surface
                    var surface:Surface = sk.getSurface(i); //get the current surface
                 
                    surface.material = injectMaterial; // new FillMaterial(0xFF0000, 1);
            
                }
     
            }
        }
        
        private function setMaterialToCont(mat:Material, cont:Object3D):void 
        {
            for (var c:Object3D = cont.childrenList; c != null; c = c.next) {
                var mesh:Mesh = c as Mesh;
                if (mesh != null) {
                    mesh.setMaterialToAllSurfaces(mat);
                }
            }
        }

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

        }

    
        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;
            
                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 initialize(event:Event):void {
            _template.removeEventListener(Template.VIEW_CREATE, initialize);
            
            
                
            //マテリアル用のリソースの用意
            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);
            _textureLoader = new TexturesLoader(_template.stage3D.context3D);
            
            setupStartingScene();
        
            if ( !doLoadingOfAssets() ) {
                 _template.initialize();
            };
         
        }
        

        
    
        
    }
}



/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.
 * You may add additional accurate notices of copyright ownership.
 *
 * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ 
 * */

//package alternativa.engine3d.objects {

    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.Debug;
    import alternativa.engine3d.core.DrawUnit;
    import alternativa.engine3d.core.Light3D;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.core.VertexAttributes;
    import alternativa.engine3d.core.VertexStream;
    import alternativa.engine3d.materials.Material;
    import alternativa.engine3d.materials.compiler.Linker;
    import alternativa.engine3d.materials.compiler.Procedure;
    import alternativa.engine3d.resources.Geometry;
    import alternativa.protocol.codec.primitive.UIntCodec;
    import flash.utils.ByteArray;
    import flash.utils.Endian;

    import flash.display3D.Context3DVertexBufferFormat;
    import flash.utils.Dictionary;

    use namespace alternativa3d;

    /**
     * A modified MeshSet class that allows you to create a dynamic hierachy of meshes (or just a single mesh) (all sharing the same material) which can than be cloned, and than dynamically added  and removed from this container to support batch rendering at varying numbers. Depending on your settings (meshes per surface) and how many meshes are found in your hierachy, you could batch render a certain number of mesh-hierachies at once, saving on your draw calls! The advantage of using this over MeshSet is that this container is supports dynamic cloning, allowing you to add/remove the clones as you wish, as well as reusing a much smaller geometry footprint per batch draw call instead of naively saving entirely static duplicate geometry like what MeshSet does. These 2 differences make it work great with particle systems and crowds. This container isn't recursive, though, and it should not exist under another MeshSet or MeshSetClonesContainer.
     */
    
     // TODO: SkinSetClonesContainer...yea!!
     
    //public 
    class MeshSetClonesContainer extends Mesh {
        private var root:Object3D;

        private static const ATTRIBUTE:uint = 20;

        private var surfaceMeshes:Vector.<Vector.<Mesh>> = new Vector.<Vector.<Mesh>>();
        private var surfaceMeshesTris:Vector.<Vector.<int>> = new Vector.<Vector.<int>>();

        public static const MESHES_PER_SURFACE:uint = 40;
        //private var surfaceTransformProcedures:Vector.<Procedure> = new Vector.<Procedure>();
        //private var surfaceDeltaTransformProcedures:Vector.<Procedure> = new Vector.<Procedure>();

        private static var _transformProcedures:Dictionary = new Dictionary();
        private static var _deltaTransformProcedures:Dictionary = new Dictionary();
        
        private var _material:Material;

        private var meshesPerSurface:uint;
        alternativa3d var clones:Vector.<MeshSetClone> = new Vector.<MeshSetClone>();
        alternativa3d var numClones:int = 0;
        
        public static var CLONE_CLASS:Class = MeshSetClone;
        alternativa3d var cloneClass:Class;
        
        private var orderArray:Vector.<int> = new Vector.<int>();
        private var _numMeshes:int = 0;
        private var _numVertices:int;
        private var outputSurface:Surface;
        private var outputMeshes:Vector.<Mesh> = new Vector.<Mesh>();
        private var numOutputMeshes:int;
        
        private var currentClone:MeshSetClone;
        private var curCloneMeshIndex:int = 0;
        private var _options:int;
        private var _perBatchClones:int;
        
        /**
         * Whether to attempt to pack all meshes tightly in the output buffer for ensuring the least amount of drawcalls possible (this could result in slightly larger geometry buffer size)
         */
        public static const FLAG_PACKWRAP:int = 1;
        /**
         * Whether to attempt to consider all surfaces (if number of meshes in hierachy exceed MESHES_PER_SURFACE) to detemrine maximum possible duplicate geometry to generate for batch-rendering surfaces. This could result in larger geometry buffer size for such a case, but will help a bit in optimizing drawcalls, otherwise, using this class is pointless.
         */
        public static const FLAG_CLONESURFACES:int = 2;
        
        /**
         * Constructor.
         * @param    root            An unchanging prototype hierachical Object3D reference to use for reference cloning
         * @param    material        THe material to apply to all meshes
         * @param    meshesPerSurface (Optional) The number of meshes (transform constants) allowed per surface. Defaulted to MESHES_PER_SURFACE.
         * @param    cloneClass            (Optional)  Custom MeshSetClone class to use if any. Defaulted to CLONE_CLASS.
         * @param     flags            (Optional)  THe clone packing option bitflags to optimize drawcalls (see FLAG_ constants)
         */
        public function MeshSetClonesContainer(root:Object3D, material:Material, meshesPerSurface:uint = 0, cloneClass:Class = null, flags:int = 1) {
            this.root = root;
            this.cloneClass = cloneClass != null ? cloneClass : CLONE_CLASS;
            this.meshesPerSurface = meshesPerSurface > 0 ? meshesPerSurface : MESHES_PER_SURFACE;
            this._options = flags;
            
            outputSurface = new Surface();
            outputSurface.object = this;
            outputSurface.material = material;
            
            _material = material;
            calculateGeometry();
            runHierachyOrder(this.root, attemptRegisterMesh);
            
            if ( int(meshesPerSurface/ _numMeshes ) == (meshesPerSurface / _numMeshes )) _options &= ~FLAG_PACKWRAP;
            if ( (_options & FLAG_PACKWRAP) ) {  // TODO: confirm if packwrap is really needed before jumping in!
                duplicateGeometry2();
            }
            else {
                duplicateGeometry();
            }
        }
        
        private function duplicateGeometry():void 
        {
            var totalAllowedFloored:int = (_options & FLAG_CLONESURFACES) ? getMaxPossibleDuplicatesForAllSurfaces() : meshesPerSurface / _numMeshes;
            if (totalAllowedFloored <= 0) totalAllowedFloored = 1;
            _minClonesPerBatch = totalAllowedFloored;
            setupDuplicateGeometry(totalAllowedFloored);
        }
        
        private function getMaxPossibleDuplicatesForAllSurfaces():int {
            var min:int = int.MAX_VALUE;
            for (var i:int = 0; i < surfaceMeshes.length; i++) {
                if (surfaceMeshes[i].length < min) min = surfaceMeshes[i].length;
            }
            return meshesPerSurface / min;
        }
        
        private function duplicateGeometry2():void {
            var numMeshes:int = _numMeshes; 
            var totalAllowed:Number = (_options & FLAG_CLONESURFACES) ? getMaxPossibleDuplicatesForAllSurfaces() : meshesPerSurface / numMeshes;
            _minClonesPerBatch = int(totalAllowed) <= 0 ? 1 : int(totalAllowed);
            if (totalAllowed != int(totalAllowed)) {
                totalAllowed += 2;
            }
            setupDuplicateGeometry( int(totalAllowed) );
        }
        
        private function setupDuplicateGeometry(total:int):void {
            if (total <= 1) return;
            
            var cap:int = total * _numMeshes;
            
            if (cap > meshesPerSurface) {
                cap = meshesPerSurface;
            }
            
            transformProcedure = calculateTransformProcedure(cap);
            deltaTransformProcedure = calculateDeltaTransformProcedure(cap);
            
            var bytes:ByteArray;
            //throw new Error(geometry.getAttributeValues(VertexAttributes.POSITION))
            // get samples
            var protoJointIndices:Vector.<Number> = geometry.getAttributeValues(ATTRIBUTE);
            var protoNumVertices:int = geometry.numVertices;
            _numVertices = protoNumVertices;
            var protoByteArrayStreams:Vector.<ByteArray> = new Vector.<ByteArray>();
            var len:int = geometry._vertexStreams.length;
    
            // copy all geometry bytearray data samples for all vertex streams
            for (var i:int = 0; i < len; i++) {
                protoByteArrayStreams[i] = bytes = new ByteArray();
                bytes.endian = Endian.LITTLE_ENDIAN;
                for (var u:int = 0; u < total; u++) {
                    bytes.writeBytes( geometry._vertexStreams[i].data );
                }
            }
            
            // paste geometry data for all the vertex streams
            for (i = 0; i < len; i++) {
                bytes =protoByteArrayStreams[i];
                for (u = 0; u < total; u++) {
                    var data:ByteArray = geometry._vertexStreams[i].data;
                    data.position = data.length;
                    data.writeBytes(bytes, data.length);
                }
            }
            
            // set number of vertices to match new vertex data size
            geometry._numVertices = protoNumVertices * total;
        
            var indices:Vector.<uint> = geometry.indices;
            // duplicate indices with offsets
            len = indices.length;
            for (i = 1; i < total; i++) {
                var indexOffset:int = i * protoNumVertices;
                for (u = 0; u < len; u++) {
                    indices.push(indexOffset+ indices[u]);
                }
            }
            geometry.indices = indices;
    
            
            // paste joint attribute values with offsets
            var jointIndices:Vector.<Number> = geometry.getAttributeValues(ATTRIBUTE);
            len = protoJointIndices.length;
            var duplicateMultiplier:Number = _numMeshes * 3;
            var totalLen:int = jointIndices.length;
            for (i = len; i < totalLen; i += len) {
                for (u = i; u < i+len; u++) {
                    jointIndices[u] += duplicateMultiplier;
                }
                duplicateMultiplier+= _numMeshes * 3;
            }
            geometry.setAttributeValues(ATTRIBUTE, jointIndices);
            
            
        }
        
        private function runHierachyOrder(root:Object3D, meshMethod:Function):void 
        {
            if ( root is Mesh) meshMethod(root);
            for (var child:Object3D = root.childrenList; child != null; child = child.next) {
                checkIsMeshRecursive(child, meshMethod);
            }
        }
        
        private function attemptRegisterMesh(mesh:Mesh):void {
            var loc:Vector.<int> = findLocationOfMeshInSurfaceMeshes(mesh);
            if (loc == null) throw new Error("Failed to find mesh!");
            orderArray[(_numMeshes<<1)] = loc[0];
            orderArray[(_numMeshes<<1)+1] = loc[1];
            _numMeshes++;
        }
    
        
        private function checkIsMeshRecursive(child:Object3D, meshMethod:Function):void 
        {
            if ( child is Mesh) meshMethod(child);
            child = child.childrenList;
            while( child!=null) {
                checkIsMeshRecursive(child, meshMethod);
                child = child.next;
            }
        }
        
        private function findLocationOfMeshInSurfaceMeshes(mesh:Object3D):Vector.<int> {
            var len:int = surfaceMeshes.length;
            for (var i:int = 0; i < len; i++) {
                var meshes:Vector.<Mesh> = surfaceMeshes[i];
                var uLen:int = meshes.length;
                for (var u:int = 0; u < uLen; u++) {
                    if (meshes[u] === mesh) return new <int>[i,u];
                }
            }
            return null;
        }
        

        public function createClone():MeshSetClone {
            var cloneItem:MeshSetClone = new cloneClass();
            cloneItem.root = root.clone();
            cloneItem.index = -1;
            
            cloneItem.surfaceMeshes = surfaceMeshes.concat();
            var len:int = cloneItem.surfaceMeshes.length;
            for (var i:int = 0; i < len; i++) {
                cloneItem.surfaceMeshes[i]= cloneItem.surfaceMeshes[i].concat();
            }
            
            currentClone = cloneItem;
            curCloneMeshIndex = 0;
            runHierachyOrder(cloneItem.root, assignMeshToClone);
            currentClone = null;
            return cloneItem;
        }
        
        private function assignMeshToClone(mesh:Mesh):void {

            currentClone.surfaceMeshes[orderArray[(curCloneMeshIndex <<1)]][orderArray[(curCloneMeshIndex <<1) + 1]] = mesh;
            curCloneMeshIndex++;
        }
        
        public function addClone(cloneItem:MeshSetClone):void {
            if (cloneItem.index >= 0) throw new Error("Clone item seems to already belong to a container or wasn't freshly created/removed!!");  
            
            cloneItem.index = numClones;
            clones[numClones++] = cloneItem;
        }
        
        public function removeClone(cloneItem:MeshSetClone):void {
            if (cloneItem.index < 0) throw new Error("Clone item seems to already be removed!");
             
            clones[cloneItem.index] = null;  // nulling reference isn't necessary if pooling clones
            if (cloneItem.index < --numClones) {  
                clones[numClones] = null;  // nulling reference isn't necessary if pooling clones
                clones[cloneItem.index] = clones[numClones];
            }
            cloneItem.index = -1;
            
        }
        alternativa3d function addCloneQuick(cloneItem:MeshSetClone):void {
            cloneItem.index = numClones;
            clones[numClones++] = cloneItem;
        }
        
        alternativa3d function removeCloneQuick(cloneItem:MeshSetClone):void {
            clones[cloneItem.index] = null;  // nulling reference isn't necessary if pooling clones
            if (cloneItem.index < --numClones) {
                clones[numClones] = null;  // nulling reference isn't necessary if pooling clones
                clones[cloneItem.index] = clones[numClones];
            }
            cloneItem.index = -1;
        }
        
        /**
         * @private
         */
        alternativa3d override function calculateVisibility(camera:Camera3D):void {
            super.alternativa3d::calculateVisibility(camera);
            var i:int = numClones;
            while (--i > -1) {
                var root:Object3D = clones[i].root;
                if (root.transformChanged) root.composeTransforms();
                root.localToGlobalTransform.copy(root.transform);
                calculateMeshesTransforms(root);
            }
        }
        
        /**
         * @private
         */
        alternativa3d override function setTransformConstants(drawUnit:DrawUnit, surface:Surface, vertexShader:Linker, camera:Camera3D):void {
            drawUnit.setVertexBufferAt(vertexShader.getVariableIndex("joint"), geometry.getVertexBuffer(ATTRIBUTE), geometry._attributesOffsets[ATTRIBUTE], Context3DVertexBufferFormat.FLOAT_1);
            
            var meshesLen:int = surfaceMeshes[_curSurfaceIndex].length;
            var limit:int = _curCloneIndex + _curBatchCount;
    
    
            var offsetNumMeshes:int = _offsetNumMeshes;
            drawUnit.setVertexConstantsFromNumbers( vertexShader.getVariableIndex("cVars"), offsetNumMeshes*3, 0, 0, 1);
            var count:int = 0;
            
                
                var triCount:int = 0;
            for (var i:int = _curCloneIndex; i < limit; i++) {
                var meshes:Vector.<Mesh> = clones[i].surfaceMeshes[_curSurfaceIndex];
                for (var m:int = offsetNumMeshes; m < meshesLen; m++) {
                    var mesh:Mesh = meshes[m];            
                    triCount += mesh.geometry.numTriangles;
                    drawUnit.setVertexConstantsFromTransform(count * 3, mesh.localToGlobalTransform);
                    count++;
                    offsetNumMeshes = 0;
                }
                
            }
        
            // handle pack-fill spill over if any

            if (i  >= numClones) return;
                meshes = clones[i].surfaceMeshes[_curSurfaceIndex];
                for (m = 0; m < _addNumMeshes; m++) {
                    mesh = meshes[m];            
                    triCount += mesh.geometry.numTriangles;
                    drawUnit.setVertexConstantsFromTransform(count * 3, mesh.localToGlobalTransform);
                    count++;
                }
                
            if (triCount != surface.numTriangles) {
                    surface.numTriangles = triCount;
                }
                    
        }

        private function calculateMeshesTransforms(root:Object3D):void {        
            for (var child:Object3D = root.childrenList; child != null; child = child.next) {
                if (child.transformChanged) child.composeTransforms();
                // Put skin transfer matrix to localToGlobalTransform
                child.localToGlobalTransform.combine(root.localToGlobalTransform, child.transform);
                calculateMeshesTransforms(child);
            }
        }
        
        private var _curCloneIndex:int;
        private var _curSurfaceIndex:int;
        private var _curBatchCount:int;
        private var _minClonesPerBatch:int;
        /**
         * 
         * @private
         */
        override alternativa3d function collectDraws(camera:Camera3D, lights:Vector.<Light3D>, lightsLength:int, useShadow:Boolean):void {
            var addNumTriangles:int;
            var addNumMeshes:int;
            if (geometry == null) return;
            
            var totalClones:int = numClones;
            if (totalClones == 0) return;
        
            
    
            var cloneSurfaces:Boolean = (_options & FLAG_CLONESURFACES) != 0;
            

            var minClonesPerBatch:int = _minClonesPerBatch;
            
            //var i:int = 0;
            for (var i:int = 0; i < _surfacesLength; i++) {
                _curSurfaceIndex = i;
                var surface:Surface = _surfaces[i];
                //transformProcedure = surfaceTransformProcedures[i];  // no longer being used
                //deltaTransformProcedure = surfaceDeltaTransformProcedures[i];
            
                var spillOverMeshes:int = 0;
                 addNumTriangles = 0 ;
                addNumMeshes = 0;
                 _addNumMeshes = 0;
                _offsetNumMeshes = 0;
                var lastNumAddTriangles:int = 0;
                
                var surfaceMeshesLen:int = surfaceMeshes[i].length;
                var packWrap:Boolean = (_options & FLAG_PACKWRAP)!=0 && (cloneSurfaces ? surfaceMeshesLen : _numMeshes) < meshesPerSurface;
                
            
                //var count:int = 0;  // for deubugging
                //var traceStr:String = "";
                for (var c:int = 0; c < totalClones; c += minClonesPerBatch) {
                    //count++;
                    
                    ///*  // Pack wrap branch...else should set default above!
                    if (packWrap) {
                        outputSurface.indexBegin =  lastNumAddTriangles * 3;
                        
                        var ratio:Number = (meshesPerSurface-spillOverMeshes) / surfaceMeshesLen;
                        minClonesPerBatch =  int(ratio);
                        //ratio = ratio < 1 ? 0 : ratio;   
                        var gotRemainder:Boolean = ratio != int(ratio);
                        addNumMeshes = gotRemainder ? meshesPerSurface - minClonesPerBatch*surfaceMeshesLen - spillOverMeshes  : 0;  //(ratio - int(ratio)) * numMeshes
                        
                        addNumTriangles =  gotRemainder ? getNumTriangles(i, addNumMeshes ) : 0;  
                        
                        _addNumMeshes =  addNumMeshes;
                        minClonesPerBatch+= spillOverMeshes ? 1 : 0;
                    
                    }
                //*/
                
                    _curCloneIndex = c;
                    _curBatchCount = totalClones - c;
                    _curBatchCount = _curBatchCount > minClonesPerBatch ? minClonesPerBatch : _curBatchCount;
                    
                    outputSurface.numTriangles = surface.numTriangles * _curBatchCount - lastNumAddTriangles + addNumTriangles;
                    
                    _material.collectDraws(camera, outputSurface, geometry, lights, lightsLength, useShadow);
                //    traceStr += "\n"+  (surfaceMeshesLen * _curBatchCount-_offsetNumMeshes) + "," + addNumMeshes +  " , " + _offsetNumMeshes + ": "+ _curCloneIndex + ", "+_curBatchCount + " | "+outputSurface.indexBegin + " + "+outputSurface.numTriangles + ", "+surface.numTriangles + " >> " +addNumTriangles;
        
                    lastNumAddTriangles = addNumTriangles;
                        spillOverMeshes = gotRemainder  ? surfaceMeshesLen - addNumMeshes : 0;
                    _offsetNumMeshes = addNumMeshes;

                    // Uncomment this if you relying on mouse events!
                    //    if (listening) camera.view.addSurfaceToMouseEvents(outputSurface, geometry, transformProcedure);
                };    
                
            }
            
            // Debug
            /*
            if (camera.debug) {
                var debug:int = camera.checkInDebug(this);
                if ((debug & Debug.BOUNDS) && boundBox != null) Debug.drawBoundBox(camera, boundBox, localToCameraTransform);
            }
            */
        }
        
        
        private var _addNumMeshes:int;
        private var _offsetNumMeshes:int = 0;
        
        private function getNumTriangles(surfaceIndex:int, numMeshes:int):int 
        {
            var count:int = 0;
    
            var counts:Vector.<int> = surfaceMeshesTris[surfaceIndex];
            //var meshes:Vector.<Mesh> = surfaceMeshes[surfaceIndex];
            for (var i:int = 0; i < numMeshes; i++) {
                //count += meshes[i].geometry.numTriangles;
                //if (counts[i] != meshes[i].geometry.numTriangles) throw new Error("MISMATCH!");
                count+=counts[i];
            }
            return count;
        }
        
        private function getOffsetNumTriangles(surfaceIndex:int, numMeshes:int):int 
        {
            var count:int = 0;
    
            var counts:Vector.<int> = surfaceMeshesTris[surfaceIndex];
            var len:int = counts.length;
            for (var i:int = numMeshes; i < len; i++) {
                
                count+=counts[i];
            }
            return count;
        }

        private function calculateGeometry():void {
            geometry = new Geometry(0);
            addSurface(_material, 0, 0);
            var numAttributes:int = 32;
            var attributesDict:Vector.<int> = new Vector.<int>(numAttributes, true);
            var attributesLengths:Vector.<int> = new Vector.<int>(numAttributes, true);
            var numMeshes:Number = collectAttributes(root, attributesDict, attributesLengths);

            var attributes:Array = [];
            var i:int;

            for (i = 0; i < numAttributes; i++) {
                if (attributesDict[i] > 0) {
                    attributesLengths[i] = attributesLengths[i]/attributesDict[i];
                }
            }
            for (i = 0; i < numAttributes; i++) {
                if (Number(attributesDict[i])/numMeshes == 1) {
                    for (var j:int = 0; j < attributesLengths[i]; j++) {
                        attributes.push(i);
                    }

                }
            }
            attributes.push(ATTRIBUTE);
            geometry.addVertexStream(attributes);
            if (root is Mesh) appendMesh(root as Mesh);
            collectMeshes(root);
            var surfaceIndex:uint = _surfaces.length - 1;
            var meshes:Vector.<Mesh> = surfaceMeshes[surfaceIndex];
            //surfaceTransformProcedures[surfaceIndex] = calculateTransformProcedure(meshes.length);
            //surfaceDeltaTransformProcedures[surfaceIndex] = calculateDeltaTransformProcedure(meshes.length);
            //throw new Error(numSurfaces);
        }

        private function collectAttributes(root:Object3D, attributesDict:Vector.<int>, attributesLengths:Vector.<int>):int {
            var geom:Geometry;
            var numMeshes:int = 0;
            if (root is Mesh) {
                geom = Mesh(root).geometry;

                for each (var stream:VertexStream in geom._vertexStreams) {
                    var prev:int = -1;
                    var attributes:Array = stream.attributes;
                    for each (var attr:int in attributes) {
                        attributesLengths[attr]++;
                        if (attr == prev) continue;
                        attributesDict[attr]++;
                        prev = attr;
                    }
                }
                numMeshes++;
            }

            for (var child:Object3D = root.childrenList; child != null; child = child.next) {
                numMeshes += collectAttributes(child, attributesDict, attributesLengths);
            }
            return numMeshes;
        }

        override public function addSurface(material:Material, indexBegin:uint, numTriangles:uint):Surface {
            surfaceMeshes.push(new Vector.<Mesh>());
            surfaceMeshesTris.push(new Vector.<int>());
            return super.addSurface(material, indexBegin, numTriangles);
        }

        private function collectMeshes(root:Object3D):void {
            for (var child:Object3D = root.childrenList; child != null; child = child.next) {
                if (child is Mesh) {
                    appendMesh(child as Mesh);
                }
                collectMeshes(child);
            }
        }
        
        

        private function appendGeometry(geom:Geometry, index:int):void {
            var stream:VertexStream;
            var i:int, j:int;
            var length:uint = geom._vertexStreams.length;
            var numVertices:int = geom._numVertices;
            for (i = 0; i < length; i++) {
                stream = geom._vertexStreams[i];
                var attributes:Array = geometry._vertexStreams[i].attributes;
                var attribtuesLength:int = attributes.length;
                var destStream:VertexStream = geometry._vertexStreams[i];
                var newOffset:int = destStream.data.length;
                destStream.data.position = newOffset;

                stream.data.position = 0;
                var stride:int = stream.attributes.length*4;
                var destStride:int = destStream.attributes.length*4;
                for (j = 0; j < numVertices; j++) {
                    var prev:int = -1;
                    for (var k:int = 0; k < attribtuesLength; k++) {
                        var attr:int = attributes[k];
                        if (attr == ATTRIBUTE) {
                            destStream.data.writeFloat(index*3);
                            continue;
                        }
                        if (attr != prev) {
                            stream.data.position = geom._attributesOffsets[attr]*4 + stride*j;
                            destStream.data.position = newOffset + geometry._attributesOffsets[attr]*4 + destStride*j;
                        }
                        destStream.data.writeFloat(stream.data.readFloat());
                        prev = attr;
                    }
                }

            }
            geometry._numVertices += geom._numVertices;

        }

        private function compareAttribtues(destStream:VertexStream, sourceStream:VertexStream):Boolean {
            if ((destStream.attributes.length - 1) != sourceStream.attributes.length) return false;
            var len:int = sourceStream.attributes.length;
            for (var i:int = 0; i < len; i++) {
                if (destStream.attributes[i] != sourceStream.attributes[i]) return false;
            }
            return true;
        }
        
        

        private function appendMesh(mesh:Mesh):void {
            var surfaceIndex:uint = _surfaces.length - 1;
            var destSurface:Surface = _surfaces[surfaceIndex];
            var meshes:Vector.<Mesh> = surfaceMeshes[surfaceIndex];
        
            var meshTriCounts:Vector.<int> = surfaceMeshesTris[surfaceIndex];
            if (meshes.length >= meshesPerSurface) {
                //surfaceTransformProcedures[surfaceIndex] = calculateTransformProcedure(meshes.length);
                //surfaceDeltaTransformProcedures[surfaceIndex] = calculateDeltaTransformProcedure(meshes.length);
                addSurface(_material, geometry._indices.length, 0);
                surfaceIndex++;
                destSurface = _surfaces[surfaceIndex];
                meshes = surfaceMeshes[surfaceIndex];
                meshTriCounts = surfaceMeshesTris[surfaceIndex];
            //    throw new Error(meshTriCounts + ", "+surfaceIndex);
            }
            meshes.push(mesh);
            var geom:Geometry = mesh.geometry;
            var vertexOffset:uint;
            var i:int, j:int;
            vertexOffset = geometry._numVertices;
            appendGeometry(geom, meshes.length - 1);
        //    trace(surfaceIndex);
            // Copy indexes
            var triCount:int = 0;
            for (i = 0; i < mesh._surfacesLength; i++) {
                var surface:Surface = mesh._surfaces[i];
                var indexEnd:uint = surface.numTriangles*3 + surface.indexBegin;
                triCount += surface.numTriangles;
                destSurface.numTriangles += surface.numTriangles;
                for (j = surface.indexBegin; j < indexEnd; j++) {
                    geometry._indices.push(geom._indices[j] + vertexOffset);
                }
            }
        
            meshTriCounts.push( triCount); 
            
        }
        

        private function calculateTransformProcedure(numMeshes:int):Procedure {
            var res:Procedure = _transformProcedures[numMeshes];
            if (res != null) return res;
            res = _transformProcedures[numMeshes] = new Procedure(null, "MeshSetTransformProcedure");
            res.compileFromArray(["#a0=joint", "#c1=cVars", "sub t0, a0.x, c1.x", "m34 o0.xyz, i0, c[t0.x]", "mov o0.w, i0.w"]);
            res.assignConstantsArray(numMeshes*3);
            return res;
        }

        private function calculateDeltaTransformProcedure(numMeshes:int):Procedure {
            var res:Procedure = _deltaTransformProcedures[numMeshes];
            if (res != null) return res;
            res = _deltaTransformProcedures[numMeshes] = new Procedure(null, "MeshSetDeltaTransformProcedure");
            res.compileFromArray(["#a0=joint", "#c1=cVars", "sub t0, a0.x, c1.x", "m33 o0.xyz, i0, c[t0.x]", "mov o0.w, i0.w"]);
            return res;
        }
        
        public function get material():Material 
        {
            return _material;
        }
        
        public function set material(value:Material):void 
        {
            _material = value;
            outputSurface.material = value;
        }
    }
//}

//package alternativa.engine3d.objects 
//{
    import alternativa.engine3d.alternativa3d;
    import alternativa.engine3d.core.Object3D;
    use namespace alternativa3d;
    /**
     * Base class for MeshSetClonesContainer to hold dynamic reference to a cloned hierachy of meshes. 
     * You can extend this class and assign it to a MeshSetClonesContainer to instantiate that instead.
     * @author Glenn Ko
     */
    //public
    class MeshSetClone 
    {
        public var root:Object3D;
        public var surfaceMeshes:Vector.<Vector.<Mesh>>;
        alternativa3d var index:int;

        
        public function MeshSetClone() 
        {
            
        }
        
    }

//}

import alternativa.engine3d.alternativa3d;
import alternativa.engine3d.collisions.EllipsoidCollider;
import alternativa.engine3d.core.BoundBox;
import alternativa.engine3d.materials.LightMapMaterial;
import alternativa.engine3d.primitives.GeoSphere;
import alternativa.engine3d.controllers.SimpleObjectController;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Resource;
import alternativa.engine3d.core.View;
import alternativa.engine3d.core.VertexAttributes;
import alternativa.engine3d.core.Transform3D;
import alternativa.engine3d.utils.Object3DUtils;
import flash.display.Bitmap;
import flash.display.BitmapData;

import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.objects.Joint;
import alternativa.engine3d.lights.AmbientLight;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.resources.Geometry;
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.utils.Dictionary;
import flash.geom.Vector3D;
import flash.utils.Dictionary;
import flash.geom.*;


class Template extends Sprite {
    public var rootControl: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 setup():void 
    {
          //View3D(表示エリア)の作成
        var view:View = new View(stage.stageWidth, stage.stageHeight);
        view.backgroundColor = 0xFFF9E5;
        view.antiAlias = 4
    
       
        
        //Scene(コンテナ)の作成
        scene = new Object3D();

        //Camera(カメラ)の作成
        camera = new Camera3D(1, 100000);
        camera.view = view;
        scene.addChild(camera);
        

        //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 = new Object3D();
        scene.addChild(rootControl);
        controlObject = new Object3D()
        rootControl.addChild(controlObject);
    }
    
    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;          
        
        setup();
        
        //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);
        
         addChild(camera.view);
        addChild(camera.diagram);
        
        dispatchEvent(new Event(VIEW_CREATE));
        
    
    }
    
    
    
    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;

}


/**
 * Primitive
 */

    
class Primitive extends Mesh {
    protected const RADIAN:Number = Math.PI / 180;
    
    public var inSide:Surface = null;
    public var outSide:Surface = null;        
    protected var indices:Vector.<uint> = new Vector.<uint>();
    protected var positions:Vector.<Number> = new Vector.<Number>();
    protected var texcoords:Vector.<Number> = new Vector.<Number>();        
    
    //表用と裏用のpositionsとtexcoordsとindicesとを追加
    protected var positionsInSide:Vector.<Number> = new Vector.<Number>();
    protected var positionsOutSide:Vector.<Number> = new Vector.<Number>();
    protected var texcoordsInSide:Vector.<Number> = new Vector.<Number>();
    protected var texcoordsOutSide:Vector.<Number> = new Vector.<Number>();
    protected var indicesInSide:Vector.<uint> = new Vector.<uint>();
    protected var indicesOutSide:Vector.<uint> = new Vector.<uint>();            
    
    public function Primitive() {
        geometry = new Geometry();
        var pos:int = VertexAttributes.POSITION
        var tex:int = VertexAttributes.TEXCOORDS[0]
        geometry.addVertexStream([pos,pos,pos,tex,tex]);
    }
    
    protected function setGeometry():void {
        positions = positionsOutSide.concat(positionsInSide);
        texcoords = texcoordsOutSide.concat(texcoordsInSide);
        indices = indicesOutSide.concat();
        var indexStart:int = positionsOutSide.length / 3;
        var count:uint = indicesInSide.length;
        for (var i:uint = 0; i < count; i++) {
            indices.push(indicesInSide[i] + indexStart);
        }
        geometry.numVertices = positions.length / 3;
        geometry.setAttributeValues(VertexAttributes.POSITION, positions);
        geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
        geometry.indices = indices;
        if (indicesInSide.length) {
            inSide = this.addSurface(null, indicesOutSide.length, indicesInSide.length / 3);
        }
        if (indicesOutSide.length) {
            outSide = this.addSurface(null, 0, indicesOutSide.length / 3);
        }
        MeshUtility.createNormal(this);
    }
}




class RoundMesh extends Primitive {
    
    public function RoundMesh(lineList:Vector.<Point>, radialSegments:uint = 3,lastSegments:uint = 0,star:Number = 0, twoSide:Boolean = false, reverse:Boolean = false) {
        super();
        
        var rInterval:Number = 360 /  radialSegments;
        var heightSegments:uint = lineList.length - 1;
        var height:Number = lineList[heightSegments].y - lineList[0].y;
        
        var radian:Number
        var segmentCount:int = (lastSegments > 0) ? lastSegments : radialSegments;
        for (var i:int = 0; i < segmentCount; i++) {
            for (var j:int = 0; j < heightSegments; j++) {
                var vertices:Vector.<Vector3D> = new Vector.<Vector3D>(4);
                var uvs:Vector.<Point> = new Vector.<Point>(4);
                
                //時計周りで四角を作成していく
                var tempRadiusA:Number = (i % 2 == 1 && star > 0) ? lineList[j].x * star : lineList[j].x
                var tempRadiusB:Number = (i % 2 == 1 && star > 0) ? lineList[j+1].x * star : lineList[j+1].x
                
                radian = (rInterval * i - 90) * RADIAN;
                vertices[0] = new Vector3D(Math.cos(radian) * tempRadiusA, Math.sin(radian) * tempRadiusA, lineList[j].y-(height/2));
                vertices[3] = new Vector3D(Math.cos(radian) * tempRadiusB, Math.sin(radian) * tempRadiusB, lineList[j+1].y-(height/2));
                
                radian = (rInterval * (i + 1) - 90) * RADIAN;
                vertices[1] = new Vector3D(Math.cos(radian) * tempRadiusA, Math.sin(radian) * tempRadiusA, lineList[j].y-(height/2));
                vertices[2] = new Vector3D(Math.cos(radian) * tempRadiusB, Math.sin(radian) * tempRadiusB, lineList[j+1].y-(height/2));

                uvs[0] = new Point(1 / radialSegments * -i, 1 / heightSegments * j);
                uvs[3] = new Point(1 / radialSegments * -i, 1 / heightSegments * (j+1));
                uvs[1] = new Point(1 / radialSegments * -(i+1), 1 / heightSegments * j);
                uvs[2] = new Point(1 / radialSegments * -(i+1), 1 / heightSegments * (j+1));
                
                //Cylinderに巻きつける場合、裏表が逆になるので注意(UVも逆)
                if (reverse == false || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesOutSide, positionsOutSide, texcoordsOutSide, true)
                }
                if (reverse == true || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesInSide, positionsInSide, texcoordsInSide, false)
                }
                
            }
        }
        setGeometry();
    }
}






class Cylinder extends Primitive {
    public function Cylinder(topRadius:Number = 0, bottomRadius:Number = 50, height:Number = 100, radialSegments:uint = 3, heightSegments:uint = 1, lastSegments:uint = 0,star:Number = 0, twoSide:Boolean = false, reverse:Boolean = false) {
        
        var rInterval:Number = 360 / radialSegments;
        var hInterval:Number = height / heightSegments;
        
        var radian:Number
        var segmentCount:int = (lastSegments > 0) ? lastSegments : radialSegments;
        for (var i:int = 0; i < segmentCount; i++) {
            for (var j:int = 0; j < heightSegments; j++) {
                var vertices:Vector.<Vector3D> = new Vector.<Vector3D>(4);
                var uvs:Vector.<Point> = new Vector.<Point>(4);
                var px:Number=0;
                var py:Number = 0;
                var tmpR:Number = 0;
                var starRatio:Number = 0;
                radian = (rInterval * i - 90) * RADIAN;
                starRatio = (i % 2 == 1 && star > 0) ? star : 1;
                px = Math.cos(radian)
                py = Math.sin(radian)
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * j, starRatio);
                vertices[0] = new Vector3D(px * tmpR, py * tmpR, hInterval * j - (height / 2));
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * (j + 1), starRatio);
                vertices[3] = new Vector3D(px * tmpR, py * tmpR, hInterval * (j + 1) - (height / 2));
                
                radian = (rInterval * (i + 1) - 90) * RADIAN;
                starRatio = (i % 2 == 0 && star > 0) ? star : 1;
                px = Math.cos(radian);
                py = Math.sin(radian);
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * j, starRatio);
                vertices[1] = new Vector3D(px * tmpR, py * tmpR, hInterval * j - (height / 2));
                tmpR = getRadius(topRadius, bottomRadius, height, hInterval * (j + 1), starRatio);
                vertices[2] = new Vector3D(px * tmpR, py * tmpR, hInterval * (j + 1) - (height / 2));
                
                var u:Number = 1 / radialSegments;
                var v:Number = 1 / heightSegments;
                uvs[0] = new Point(u * -i, v * j);
                uvs[3] = new Point(u * -i, v * (j + 1));
                uvs[1] = new Point(u * -(i + 1), v * j);
                uvs[2] = new Point(u * -(i + 1), v * (j + 1));
                
                
                //Cylinderに巻きつける場合、裏表が逆になるので注意(UVも逆)
                if (reverse == false || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesOutSide, positionsOutSide, texcoordsOutSide, true)
                }
                if (reverse == true || twoSide == true) {
                    MeshUtility.createSquare(vertices, uvs, indicesInSide, positionsInSide, texcoordsInSide, false)
                }
                
            }
        }
        setGeometry()

    }
    
    /**
     * 半径を高さの比率から割り出す
     * @return
     */
    private function getRadius(topRadius:Number, bottomRadius:Number, height:Number, length:Number, starRatio:Number):Number {
        var result:Number
        var difference:Number
        var ratio:Number
        
        if (topRadius > bottomRadius) {
            difference = topRadius - bottomRadius;
            ratio = (length) ? (height - length) / height : 1
            result = (bottomRadius + (difference * ratio));
        } else {
            difference = bottomRadius - topRadius;
            ratio = (length) ? length / height : 0
            result = (topRadius + (difference * ratio));
        }
        result *= starRatio
        return result;
    }

}








/**
 * 頂点に隣接する面法線を収集するクラス
 */

class ExtraVertex {
    public var vertex:Vector3D;
    public var normals:Dictionary;
    public var indices:Vector.<uint>;
    public function ExtraVertex(x:Number, y:Number, z:Number) {
        vertex = new Vector3D(x, y, z);
        normals = new Dictionary();
        indices = new Vector.<uint>();
    }
    
}



/**
 * MeshUtility
 */


use namespace alternativa3d;

class MeshUtility {
    
    public function MeshUtility() {
    
    }
    
    /**
     * 3つの頂点座標から、Faceを作成し、indices、positionsに各値を登録する
     */
    public static function createTriangle(vertices:Vector.<Vector3D>, uvs:Vector.<Point>,
                                          indices:Vector.<uint>, positions:Vector.<Number>, 
                                          texcoords:Vector.<Number>, reverse:Boolean = false):void {
        if (reverse == false) {
            //三角形用の頂点を登録
            positions.push(vertices[0].x, vertices[0].y, vertices[0].z,
                           vertices[2].x, vertices[2].y, vertices[2].z,
                           vertices[1].x, vertices[1].y, vertices[1].z);
            
            //三角形用のUVを登録
            texcoords.push(uvs[0].x, uvs[0].y,uvs[2].x, uvs[2].y,uvs[1].x, uvs[1].y);
            
        } else {
            //三角形用の頂点を登録
            positions.push(vertices[0].x, vertices[0].y, vertices[0].z,
                           vertices[1].x, vertices[1].y, vertices[1].z,
                           vertices[2].x, vertices[2].y, vertices[2].z);
            
            //三角形用のUVを登録
            texcoords.push(uvs[0].x, uvs[0].y,uvs[1].x, uvs[1].y,uvs[2].x, uvs[2].y);
            
        }
        //Face用indexを登録
        var startIndex:uint = indices.length
        indices.push(startIndex + 0, startIndex + 1, startIndex + 2);
    }
    
    /**
     * 4つの頂点座標から、四角形(2つのFace)を作成し、indices、positions、uvsに各値を登録する
     */
    public static function createSquare(vertices:Vector.<Vector3D>, uvs:Vector.<Point>,
                                        indices:Vector.<uint>, positions:Vector.<Number>, 
                                        texcoords:Vector.<Number>, reverse:Boolean = false):void {
        
        if (reverse == false) {
            positions.push(    vertices[0].x, vertices[0].y, vertices[0].z,
                            vertices[3].x, vertices[3].y, vertices[3].z,
                            vertices[1].x, vertices[1].y, vertices[1].z);
            positions.push(    vertices[1].x, vertices[1].y, vertices[1].z,
                            vertices[3].x, vertices[3].y, vertices[3].z,
                            vertices[2].x, vertices[2].y, vertices[2].z);    
            //三角形用のUVを登録
            texcoords.push(uvs[0].x, uvs[0].y,uvs[3].x, uvs[3].y,uvs[1].x, uvs[1].y);
            texcoords.push(uvs[1].x, uvs[1].y,uvs[3].x, uvs[3].y,uvs[2].x, uvs[2].y);
                            
        } else {
            positions.push(    vertices[0].x, vertices[0].y, vertices[0].z,
                            vertices[1].x, vertices[1].y, vertices[1].z,
                            vertices[3].x, vertices[3].y, vertices[3].z);
            positions.push(    vertices[1].x, vertices[1].y, vertices[1].z,
                            vertices[2].x, vertices[2].y, vertices[2].z,
                            vertices[3].x, vertices[3].y, vertices[3].z);    
            //三角形用のUVを登録
            texcoords.push(uvs[0].x, uvs[0].y,uvs[1].x, uvs[1].y,uvs[3].x, uvs[3].y);
            texcoords.push(uvs[1].x, uvs[1].y, uvs[2].x, uvs[2].y, uvs[3].x, uvs[3].y);
        }
        
        //Face用indexを登録
        var startIndex:uint = indices.length
        indices.push(startIndex + 0, startIndex + 1, startIndex + 2);
        indices.push(startIndex + 3, startIndex + 4, startIndex + 5);
    
    }
    
    /**
     * 指定Meshの法線を作成します
     * @param    mesh
     */
    public static function createNormal(mesh:Mesh):void {
        //法線の有無のチェック
        if (mesh.geometry.hasAttribute(VertexAttributes.NORMAL) == false) {
            var nml:int = VertexAttributes.NORMAL;    
            mesh.geometry.addVertexStream([nml,nml,nml]);
        }
        
        var indices:Vector.<uint> = mesh.geometry.indices;
        var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
        var vartices:Vector.<Vector3D> = new Vector.<Vector3D>(positions.length / 3);
        var vNormals:Vector.<Vector3D> = new Vector.<Vector3D>(positions.length / 3);
        
        var i:int;
        var count:uint = positions.length / 3;
        for (i = 0; i < count; i++) {
            vartices[i] = new Vector3D(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
        }
        
        //面法線を求め、頂点法線に代入する
        count = indices.length;
        for (i = 0; i < count; i += 3) {
            var normal:Vector3D = calcNormal(vartices[indices[i]], vartices[indices[i + 1]], vartices[indices[i + 2]]);
            vNormals[indices[i]] = normal;
            vNormals[indices[i + 1]] = normal;
            vNormals[indices[i + 2]] = normal;
        }
        
        var normals:Vector.<Number> = new Vector.<Number>();
        
        count = vNormals.length;
        for (i = 0; i < count; i++) {
            if (vNormals[i]) {
                normals.push(vNormals[i].x, vNormals[i].y, vNormals[i].z);
            }
        }
        
        mesh.geometry.setAttributeValues(VertexAttributes.NORMAL, normals);
    }
    
    /**
     * 面法線の計算
     * 三つの頂点座標からなる三角ポリゴンの法線を計算し返します
     */
    public static function calcNormal(a:Vector3D, b:Vector3D, c:Vector3D):Vector3D {
        var v1:Vector3D = b.subtract(a);
        var v2:Vector3D = c.subtract(a);
        var v3:Vector3D = v1.crossProduct(v2);
        //var v3:Vector3D = cross(v1,v2);
        v3.normalize();
        return (v3);
    }
    

    
    /**
     * 指定Meshの法線をSmoothShadingにします
     * @param    mesh
     */
    public static function smoothShading(mesh:Mesh,separateSurface:Boolean=false,threshold:Number=0.000001):void {
        
        var indices:Vector.<uint> = mesh.geometry.indices;
        var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
        var normals:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.NORMAL);
        
        var vartices:Vector.<Vector3D> = new Vector.<Vector3D>(positions.length / 3);
        var vNormals:Vector.<Vector3D> = new Vector.<Vector3D>(normals.length / 3);
        
        var vertexDictionary:Dictionary = new Dictionary()
        var exVertex:ExtraVertex;
        
        //サーフェースごとに判断する

        for (var s:uint = 0; s < mesh.numSurfaces; s++ ) {
            var side:String = (separateSurface) ? s.toString() : '';
            for (var n:uint = 0; n < mesh.getSurface(s).numTriangles * 3; n++) {
                var i:uint = indices[n+mesh.getSurface(s).indexBegin];
                vartices[i] = new Vector3D(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]);
                //誤差を丸める
                vartices[i].x = int(vartices[i].x / threshold) * threshold;
                vartices[i].y = int(vartices[i].y / threshold) * threshold;
                vartices[i].z = int(vartices[i].z / threshold) * threshold;                
                vNormals[i] = new Vector3D(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
                //誤差を丸める
                vNormals[i].x = int(vNormals[i].x / threshold) * threshold;
                vNormals[i].y = int(vNormals[i].y / threshold) * threshold;
                vNormals[i].z = int(vNormals[i].z / threshold) * threshold;
                
                //同じ頂点を集める
                //ただし、表裏がある場合があるので法線の方向もチェックする
                if (vertexDictionary[vartices[i].toString()+'_'+side]) {
                    exVertex = vertexDictionary[vartices[i].toString()+'_'+side]
                    if (exVertex.normals[vNormals[i].toString()+'_'+side] == null) {
                        exVertex.normals[vNormals[i].toString()+'_'+side] = vNormals[i];
                    }
                    exVertex.indices.push(i);

                } else {
                    exVertex = new ExtraVertex(vNormals[i].x, vNormals[i].y, vNormals[i].z);
                    exVertex.normals[vNormals[i].toString()+'_'+side] = vNormals[i];
                    exVertex.indices.push(i)
                    vertexDictionary[vartices[i].toString()+'_'+side] = exVertex
                }
            }                
            
        }

        //Normalの平均化
        var count:uint = 0;
        for each (exVertex in vertexDictionary) {
            var normalX:Number = 0;
            var normalY:Number = 0;
            var normalZ:Number = 0;
            count = 0
            for each (var normal:Vector3D in exVertex.normals) {
                normalX += normal.x;
                normalY += normal.y;
                normalZ += normal.z;
                count++
            }
            normal = new Vector3D(normalX / count, normalY / count, normalZ / count);
            normal.normalize();
            count = exVertex.indices.length;
            for (i = 0; i < count; i++) {
                vNormals[exVertex.indices[i]] = normal;
            }
        }
        count = vNormals.length;
        normals = new Vector.<Number>();
        for (i = 0; i < count; i++) {
            normals.push(vNormals[i].x, vNormals[i].y, vNormals[i].z);
        }
        
        mesh.geometry.setAttributeValues(VertexAttributes.NORMAL, normals);
    }
    
    
    /**
     * 指定MeshのUVをVertexのxyから仮に作成する
     * @param    mesh
     */
    public static function createUv(mesh:Mesh):void {
        if (mesh.geometry.hasAttribute(VertexAttributes.TEXCOORDS[0]) == false) {
            var tex:int = VertexAttributes.TEXCOORDS[0];    
            mesh.geometry.addVertexStream([tex,tex]);
        }
        mesh.calculateBoundBox()
        var width:Number = mesh.boundBox.maxX - mesh.boundBox.minX
        var length:Number = mesh.boundBox.maxZ - mesh.boundBox.minZ
        
        var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
        var texcoords:Vector.<Number> = new Vector.<Number>;
        var i:int;
        for (i = 0; i < positions.length; i += 3) {
            texcoords.push((positions[i] - mesh.boundBox.minX) / width, (positions[i + 2] - mesh.boundBox.minZ) / length);
        }

        mesh.geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
    }
    
    
    
    /**
     * 指定MeshのTangentを作成する
     * @param    mesh
     */
    static public function createTangent(mesh:Mesh):void {
        //接線有無のチェック


        
        
        var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
        var texcoords:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
        var normals:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.NORMAL);
        
        var indices:Vector.<uint> = mesh.geometry.indices;
        var vertices:Vector.<Vector3D> = new Vector.<Vector3D>;
        var uvs:Vector.<Point> = new Vector.<Point>;
        var vNormals:Vector.<Vector3D> = new Vector.<Vector3D>;
        
        var i:int;
        for (i = 0; i < positions.length; i += 3) {
            vertices.push(new Vector3D(positions[i], positions[i + 1], positions[i + 2]));
        }
        for (i = 0; i < texcoords.length; i += 2) {
            uvs.push(new Point(texcoords[i], texcoords[i + 1]));
        }
        for (i = 0; i < normals.length; i += 3) {
            vNormals.push(new Vector3D(normals[i], normals[i + 1], normals[i + 2]));
        }
        
        var tangents:Vector.<Number> = calcTangent(mesh.geometry.indices, vertices, uvs, vNormals);
        
        var geometry:Geometry = new Geometry();
        //if (mesh.geometry.hasAttribute(VertexAttributes.TANGENT4) == false) {
        var tan:int = VertexAttributes.TANGENT4;
        var pos:int = VertexAttributes.POSITION;
        var nor:int = VertexAttributes.NORMAL;
        var tex:int = VertexAttributes.TEXCOORDS[0];
        var attribute:Array = [
            pos, pos, pos,
            nor, nor, nor,
            tan, tan, tan, tan,
            tex, tex
        ]
        geometry.addVertexStream(attribute)
        //}                
        geometry.numVertices = mesh.geometry.numVertices
        geometry.indices = mesh.geometry.indices;
        geometry.setAttributeValues(VertexAttributes.POSITION, positions);
        geometry.setAttributeValues(VertexAttributes.NORMAL, normals);
        geometry.setAttributeValues(VertexAttributes.TANGENT4, tangents);
        geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);

        mesh.geometry = geometry;
    }
    
    
    /**
     * 複数のMeshを結合し、1つのMeshにします
     * @param    meshs
     * @return
     */
    public static function bindMeshs(meshs:Vector.<Mesh>):Mesh {
        var count:uint = meshs.length;
        
        var indices:Vector.<uint> = new Vector.<uint>();
        var positions:Vector.<Number> = new Vector.<Number>();
        var texcoords:Vector.<Number> = new Vector.<Number>();
        
        var nextIndex:uint = 0;
        var nextPosition:uint = 0;
        var mesh:Mesh = meshs[i];
        var i:int
        var j:int
        for (i = 0; i < count; i++) {
            mesh = meshs[i];
            var tempPositions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
            mesh.matrix.transformVectors(tempPositions, tempPositions);
            positions = positions.concat(tempPositions);
            texcoords = texcoords.concat(mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]));
            
            var tempIndices:Vector.<uint> = mesh.geometry.indices;            
            var indexCount:uint = tempIndices.length
            for (j = 0; j < indexCount; j++) {
                tempIndices[j] += nextIndex;
            }
            indices = indices.concat(tempIndices);
            nextIndex += tempPositions.length/3
        }

        var geometry:Geometry = new Geometry();
        
        var attributes:Array = [];
        attributes[0] = VertexAttributes.POSITION;
        attributes[1] = VertexAttributes.POSITION;
        attributes[2] = VertexAttributes.POSITION;
        attributes[3] = VertexAttributes.TEXCOORDS[0];
        attributes[4] = VertexAttributes.TEXCOORDS[0];
        
        geometry.addVertexStream(attributes);
        
        geometry.numVertices = positions.length/3;
        
        geometry.setAttributeValues(VertexAttributes.POSITION, positions);
        geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
        
        geometry.indices = indices;            
        
        var result:Mesh = new Mesh()
        result.geometry = geometry;
        
        //サーフェースのコピー
        var indexBegin:uint = 0
        for (i = 0; i < count; i++) {
            mesh = meshs[i];
            for (j = 0; j < mesh.numSurfaces; j++) {
                var surface:Surface = mesh.getSurface(j);
                result.addSurface(surface.material, surface.indexBegin+indexBegin, surface.numTriangles)
            }
            indexBegin = surface.indexBegin+indexBegin + surface.numTriangles * 3;
        }
        
        //normal再計算
        createNormal(result);
        return result;
    }
    
    
    /**
     * Cylinder、Cone、Dome、RoundMesh等を合成した、MeshのSurfaceを合成します
     * UVのV値のみ更新されます
     * 
     * 頂点情報の高さ(Z座標)で判断します
     * 
     */
    public static function repairRoundSurface(mesh:Mesh):Mesh {
        var positions:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.POSITION);
        var texcoords:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
        var count:int = positions.length / 3
        //全体の高さを割り出す
        var minY:Number=0
        var maxY:Number=0
        for (var i:int = 0; i < count; i++) {
            if (minY > positions[i * 3 + 2])
                minY = positions[i * 3 + 2];
            if (maxY < positions[i * 3 + 2])
                maxY = positions[i * 3 + 2];
        }
        
        var height:Number = maxY - minY;
        for (i = 0; i < count; i++) {
            texcoords[i * 2 + 1] = (positions[i * 3 + 2] - minY) / height;
        }
        mesh.geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], texcoords);
        var result:Mesh = new Mesh();
        result.geometry = mesh.geometry
        result.addSurface(null, 0, positions.length / 9);
        return result;
    }
    
    
    /**
     * サーフェースのコピー
     * @param    origin
     * @param    mesh
     */
    public static function copySurface(origin:Mesh, mesh:Mesh):void {
        for (var i:uint = 0; i < origin.numSurfaces; i++) {
            var surface:Surface = origin.getSurface(i);
            mesh.addSurface(surface.material, surface.indexBegin, surface.numTriangles)
        }
    }

    
    /**
     * TANGENT4を再計算
     * @param    indices
     * @param    vertex
     * @param    uvs
     * @param    normals
     * @return
     */
    static public function calcTangent(indices:Vector.<uint>, vertices:Vector.<Vector3D>, uvs:Vector.<Point>, normals:Vector.<Vector3D>):Vector.<Number> {
        var tangent:Vector.<Number> = new Vector.<Number>;
        var numTriangle:int = indices.length / 3;
        var numVertex:int = vertices.length;
        
        var tan1:Vector.<Vector3D> = new Vector.<Vector3D>;
        var tan2:Vector.<Vector3D> = new Vector.<Vector3D>;
        
        var i:int;
        for (i = 0; i < vertices.length; i++) {
            tan1.push(new Vector3D());
            tan2.push(new Vector3D());
        }
        
        var max:int = indices.length;
        for (i = 0; i < max; i += 3) {
            var i1:Number = indices[i];
            var i2:Number = indices[i + 1];
            var i3:Number = indices[i + 2];
            
            var v1:Vector3D = vertices[i1];
            var v2:Vector3D = vertices[i2];
            var v3:Vector3D = vertices[i3];
            
            var w1:Point = uvs[i1];
            var w2:Point = uvs[i2];
            var w3:Point = uvs[i3];
            
            var x1:Number = v2.x - v1.x;
            var x2:Number = v3.x - v1.x;
            var y1:Number = v2.y - v1.y;
            var y2:Number = v3.y - v1.y;
            var z1:Number = v2.z - v1.z;
            var z2:Number = v3.z - v1.z;
            
            var s1:Number = w2.x - w1.x;
            var s2:Number = w3.x - w1.x;
            var t1:Number = w2.y - w1.y;
            var t2:Number = w3.y - w1.y;
            
            var r:Number = 1 / (s1 * t2 - s2 * t1);
            var sdir:Vector3D = new Vector3D((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
            var tdir:Vector3D = new Vector3D((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
            
            tan1[i1].incrementBy(sdir);
            tan1[i2].incrementBy(sdir);
            tan1[i3].incrementBy(sdir);
            
            tan2[i1].incrementBy(tdir);
            tan2[i2].incrementBy(tdir);
            tan2[i3].incrementBy(tdir);
        }
        
        for (i = 0; i < numVertex; i++) {
            var n:Vector3D = normals[i];
            var t:Vector3D = tan1[i];
            var tgt:Vector3D = t.subtract(getScaled(n, dot(n, t)));
            tgt.normalize();
            var w:Number = dot(cross(n, t), tan2[i]) < 0 ? -1 : 1;
            tangent.push(tgt.x, tgt.y, tgt.z, w);
        }
        return tangent;
    }
    
    /**
     * 2つのベクトルの内積を返します。
     * (内積:2つのベクトルがどれだけ平行に近いかを示す数値)
     * ・ 1 に近いほど同じ向きで平行
     * ・ 0 に近いほど直角
     * ・-1 に近いほど逆向きで平行
     */
    static public function dot(a:Vector3D, b:Vector3D):Number {
        return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
    }
    
    /**
     * 2つのベクトルの外積を返します。
     * (外積:2つのベクトルで作られる面に垂直なベクトル(=法線)。)
     */
    static public function cross(a:Vector3D, b:Vector3D):Vector3D {
        return new Vector3D((a.y * b.z) - (a.z * b.y), (a.z * b.x) - (a.x * b.z), (a.x * b.y) - (a.y * b.x));
    }
    
    /**
     * スケーリングした新しいベクトルを取得
     * @param    v
     * @param    scale
     * @return
     */
    static public function getScaled(v:Vector3D, scale:Number):Vector3D {
        var sv:Vector3D = v.clone();
        sv.scaleBy(scale);
        return sv;
    }
    
    /**
     * Jointの位置を初期化
     * @param    joints
     */
    public static function JointBindPose(joints:Vector.<Joint>):void {
        var count:uint = joints.length;
        for (var i:uint = 0; i < count; i++) 
        {
            var joint:Joint = joints[i]
            var jointMatrix:Matrix3D = joint.concatenatedMatrix.clone();

            jointMatrix.transpose();
            var jointBindingTransform:Transform3D = new Transform3D();
            jointBindingTransform.initFromVector(jointMatrix.rawData);
            jointBindingTransform.invert();
            var matrixVector:Vector.<Number> = new Vector.<Number>();
            matrixVector.push(jointBindingTransform.a);
            matrixVector.push(jointBindingTransform.b);
            matrixVector.push(jointBindingTransform.c);
            matrixVector.push(jointBindingTransform.d);
            matrixVector.push(jointBindingTransform.e);
            matrixVector.push(jointBindingTransform.f);
            matrixVector.push(jointBindingTransform.g);
            matrixVector.push(jointBindingTransform.h);
            matrixVector.push(jointBindingTransform.i);
            matrixVector.push(jointBindingTransform.j);
            matrixVector.push(jointBindingTransform.k);
            matrixVector.push(jointBindingTransform.l);
            
            joint.setBindPoseMatrix(matrixVector);

        }
    }
    

}

    




    import alternativa.engine3d.controllers.SimpleObjectController;
    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.Object3D;
    import flash.events.IEventDispatcher;

    import flash.display.DisplayObjectContainer;
    import flash.display.InteractiveObject;
    import flash.display.Sprite;
    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.geom.Vector3D;
    import flash.ui.Keyboard;
    import flash.ui.Mouse;
    import flash.ui.MouseCursor;

    /**
     * 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 alternativa.engine3d.controller 
//{
  import alternativa.engine3d.core.Object3D;
  import alternativa.engine3d.core.RayIntersectionData;
  import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.materials.TextureMaterial;

    import flash.display.InteractiveObject;
    import flash.events.Event;
    import flash.events.IEventDispatcher;
    import flash.events.MouseEvent;
    import flash.geom.Vector3D;
    import flash.utils.Dictionary;
    import alternativa.engine3d.alternativa3d;
    use namespace alternativa3d;
    
    /**
     * Does adjustments to orbit controller for chase-cams/follow target in relation to entire environment
     * and various game-types.
     * 
     * @author Glidias
     */
    // public 
     class OrbitCameraMan 
    {

        private var _followTarget:Object3D;
        public var rot:Vector3D;
        public var controller:OrbitCameraController;
        
        protected var _preferedZoom:Number;
        public var preferedMinDistance:Number;
        public var maxFadeAlpha:Number = .8;
        public var minFadeAlpha:Number = .2;
        protected var _fadeDistance:Number = 200;
        public var useFadeDistance:Boolean = true;
        
        public var visBufferLength:Number = 0;
        public var preferedAlpha:Number = 1;
        
        public var followAzimuth:Boolean = false;
        public var followPitch:Boolean = false;
        protected static const ORIGIN:Vector3D = new Vector3D();
        protected var scene:Object3D;
        //x:  -45 * (Math.PI / 180)  
        
        public var cameraForward:Vector3D = new Vector3D();
        public var cameraReverse:Vector3D = new Vector3D();
        public var ignoreDict:Dictionary = new Dictionary();
        public var collideOffset:Number = 24;

        public var rayCollisionScene:Object3D = new Object3D();
        
        public var threshold:Number = 0.01;
        
        public var alphaSetter:*;

        
        public var instant:Boolean = false;
        public var mouseWheelSensitivity:Number = 30;
        
        public function OrbitCameraMan(camera:Camera3D,  cameraTarget:Object3D, stager:InteractiveObject, scene:Object3D, followTarget:Object3D=null, rot:Vector3D=null, useMouseWheel:Boolean=false) 
        {

            this.rot = rot;
            var controller:OrbitCameraController = new OrbitCameraController(camera, cameraTarget, stager, stager, stager, false, useMouseWheel, mouseWheelHandler);
            this._followTarget = followTarget || (controller._followTarget);
            alphaSetter = new DummyAlpha();
            this.scene = scene;
            this.controller = controller;
            _preferedZoom = controller.getDistance();
            ignoreDict[controller._followTarget] = true;
            controller.minPitch = Math.PI * .5;
            preferedMinDistance = 0;
            
            
        }
        
        /*
             _object.x += -Math.sin(_object.rotationZ) * 120;
            _object.y += Math.cos(_object.rotationZ) * 120;
        
            _object.x += forward.x * 120;
            _object.y += forward.y * 120;
            _object.z += forward.z * 120;
        */
        
        public function mouseWheelHandler(e:MouseEvent, delta:Number=0):void {
            var _lastLength:Number = controller.getDistance();
            _lastLength -= e!= null ? e.delta * mouseWheelSensitivity : delta;
            var minDist:Number = (preferedMinDistance > 0 ? preferedMinDistance : controller.minDistance);
            if (_lastLength < minDist )
            {
                _lastLength = minDist;
            }
            else if (_lastLength > controller.maxDistance)
            {
                _lastLength = controller.maxDistance
            }
            
            preferedZoom = _lastLength;
            
        }
        
        private var positionRay:Vector3D = new Vector3D();
        public function update():void {
            controller.update(); 

            var camera:Camera3D = controller._target;
        
            var camLookAtTarget:Object3D = controller._followTarget;
            if (followAzimuth) {
                _followTarget._rotationZ = rot.z = camera.rotationZ;
                _followTarget.transformChanged = true;
            }
            if (followPitch) {
                _followTarget._rotationX =  rot.x = camera.rotationX + Math.PI * .5;
                _followTarget.transformChanged = true;
            }
            
     
            
            camera.calculateRay(ORIGIN, cameraForward, camera.view._width*.5, camera.view._height*.5);            
        
            cameraReverse.x = -cameraForward.x;
            cameraReverse.y = -cameraForward.y;
            cameraReverse.z = -cameraForward.z;
            cameraReverse.w = _preferedZoom;
            
            positionRay.x = camLookAtTarget._x + cameraForward.x * -threshold;
            positionRay.y =camLookAtTarget._y + cameraForward.y * -threshold;
            positionRay.z = camLookAtTarget._z + cameraForward.z * -threshold;
            
            
            var data:RayIntersectionData =  getBackRayIntersectionData( positionRay );
            
                _followTarget.visible = true;
            var tarDist:Number = _preferedZoom;
            if (data != null) {
                
                //cameraReverse.normalize();
                data.point = data.object.localToGlobal(data.point);
                
                positionRay.x = camLookAtTarget._x;
                positionRay.y = camLookAtTarget._y;
                positionRay.z= camLookAtTarget._z;
                tarDist = data.point.subtract( positionRay ).length - collideOffset;
            
                if (tarDist > _preferedZoom) tarDist = _preferedZoom;
                if (tarDist < controller.minDistance) tarDist =  controller.minDistance;
                    
                var limit:Number = (controller.minDistance + visBufferLength);
                _followTarget.visible = tarDist >= limit;
                
                if (useFadeDistance) {
                    limit = (tarDist - limit) / (_preferedZoom - _fadeDistance);
                    if (limit < 1) {
                        limit  = limit < 0 ? 0 : limit;
                        limit = minFadeAlpha + limit * (maxFadeAlpha - minFadeAlpha);
                       alphaSetter.alpha = limit;
                    }
                    else alphaSetter.alpha = preferedAlpha;
                }
                controller.setDistance(tarDist, instant);
                controller.setObjectPosXYZ(camera._x = camLookAtTarget.x +  cameraReverse.x * tarDist,
                camera._y= camLookAtTarget._y +  cameraReverse.y * tarDist,
                camera._z = camLookAtTarget._z +  cameraReverse.z * tarDist);
            camera.transformChanged = true;
            }
            else  {
                
                controller.setDistance(tarDist, instant);
                if (useFadeDistance) alphaSetter.alpha = preferedAlpha;
            }
            
            
            //*/
            
        }
        
        public function get sceneCollidable():Object3D {
            return scene;
        }
        protected function getBackRayIntersectionData(objPosition:Vector3D):RayIntersectionData 
        {
            return rayCollisionScene.intersectRay( objPosition, cameraReverse);
        }
        
        public function get preferedZoom():Number 
        {
            return _preferedZoom;
        }
        
        public function set preferedZoom(value:Number):void 
        {
            _preferedZoom = value;
            controller.setDistance(value);
            
        }
        
        public function get fadeDistance():Number 
        {
            return _fadeDistance;
        }
        
        public function set fadeDistance(value:Number):void 
        {
            _fadeDistance = value;

        }
        
    }

//}

class DummyAlpha {
    
    public var alpha:Number;
    public function DummyAlpha() {
        alpha = 1;
    }
}


// Danbo package

class DangBoardFrame {

    public var danBody_z:Number;
          public var           danHead_rotationX :Number;
          public var           danArmL_rotationY:Number;
           public var          danArmR_rotationY:Number;
          public var           danSldL_rotationX :Number;
           public var          danSldR_rotationX:Number;
           public var          danLegL_rotationX:Number;
            public var        danLegR_rotationX:Number;
            public var         danKneL_rotationY :Number;
          public var           danKneR_rotationY:Number;

}

class DangBoardClip {
    public var motionR:Number;
    public var frameLen:int = 0;
    public var frames:Vector.<DangBoardFrame> = new Vector.<DangBoardFrame>();
}

//}
//Danbord.as
//ダンボーの生成とモーション
//package {
    //Alternativa3D
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.materials.TextureMaterial;
    import alternativa.engine3d.primitives.Box;
    import alternativa.engine3d.primitives.Plane;
    import alternativa.engine3d.alternativa3d;
    use namespace alternativa3d;
        
    //public 
    class Danbord extends Object3D {
        private static var AREA_W:uint = 4000;
        private static var AREA_H:uint = 4000;
        private static var FPS:uint = 60;
        //3D関連
        private var boxHead:Object3D;
      //  private var boxBody:Object3D;
        private var boxSldL:Object3D;
        private var boxSldR:Object3D;
        private var boxArmL:Object3D;
        private var boxArmR:Object3D;
        private var boxKneL:Object3D;
        private var boxKneR:Object3D;
        private var boxLegL:Object3D;
        private var boxLegR:Object3D;
        private var planeSktL:Object3D;
        private var planeSktR:Object3D;
        private var planeSktF:Object3D;
        private var planeSktB:Object3D;
        

        
    
        

       public static var boxBody_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboBodyFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxHead_textures:Array = ["txMatDanboBottom", "txMatDanboHeadTop", "txMatDanboBack", "txMatDanboFace", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxSldL_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxSldR_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxArmL_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxArmR_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxKneL_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxKneR_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxLegL_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       public static var boxLegR_textures:Array = ["txMatDanboBottom", "txMatDanboTop", "txMatDanboBack", "txMatDanboFront", "txMatDanboLeft", "txMatDanboRight"];
       
       public static var  planeSktL_textures:Array = ["txMatDanboLeft", "txMatDanboBottom"];
       public static var planeSktR_textures:Array = ["txMatDanboRight", "txMatDanboBottom"];
        public static var  planeSktF_textures:Array = ["txMatDanboBottom", "txMatDanboFront"];
          public static var planeSktB_textures:Array = ["txMatDanboBack", "txMatDanboBottom"];
       
        
        public static function getTextureCombiner():TextureCombiner {
            var me:TextureCombiner = new TextureCombiner();
            
            me.parseXML(<atlas width="128" height="128">
            <a id="txMatDanboBottom" x="0" y="0" width="32" height="32"></a>
            <a id="txMatDanboTop" x="32" y="0" width="32" height="32"></a>
            <a id="txMatDanboBack" x="64" y="0" width="32" height="32"></a>
            <a id="txMatDanboFront" x="96" y="0" width="32" height="32"></a>
            
            <a id="txMatDanboLeft" x="0" y="32" width="32" height="32"></a>
            <a id="txMatDanboRight" x="64" y="32" width="32" height="32"></a>
            <a id="txMatDanboBodyFront" x="96" y="32" width="32" height="32"></a>
            
            <a id="txMatDanboFace" x="0" y="96" width="50" height="32"></a>
            <a id="txMatDanboHeadTop" x="50" y="96" width="50" height="32"></a>
        </atlas>);
            return me;
        }
          public static var textureCombiner:TextureCombiner;
        
        
        public var danHead:Object3D;
        public var danBody:Mesh;
        private var danSldL:Object3D;
        private var danSldR:Object3D;
        private var danArmL:Object3D;
        private var danArmR:Object3D;
        private var danKneL:Object3D;
        private var danKneR:Object3D;
        private var danLegL:Object3D;
        private var danLegR:Object3D;
        /*
        private var danSktL:Object3D;
        private var danSktR:Object3D;
        private var danSktF:Object3D;
        private var danSktB:Object3D;
        */
       // private var danUpper:Object3D;

        private var motionR:Number = 0;
        private var motionType:String = "stand";
        private var motionTypes:Vector.<String>;
        private var bodyGeometry:Geometry;

        
        public var bodyR:Number = 0;
        public var sp:Number = 0;
        public var spZ:Number = 0;
        public var ground:Boolean = true;
        
        public static var MATERIAL:Material = new TextureMaterial();


        
        //ダンボー
        public function Danbord():void {

            //if (textureCombiner == null) textureCombiner = getTextureCombiner();
            //
            //パーツを埋め込むコンテナの作成(回転軸を中心以外の場所にしたいためコンテナに埋め込んでます)
            
            // 10 dans   // skirts and box merged to body for blueprint
            danHead = new Object3D();
            danBody = new Mesh();
            danSldL = new Object3D();
            danSldR = new Object3D() ;
            danArmL = new Object3D();
            danArmR = new Object3D();
            danKneL = new Object3D();
            danKneR = new Object3D();
            danLegL = new Object3D();
            danLegR = new Object3D() ;
            

            
         //   danUpper = new Object3D();
            
            // danBody.z = 0;
            danHead.z = 188;
            danSldL.x = -48;
            danSldL.z = 178;
            danSldR.x = 48;
            danSldR.z = 178;
            
            danKneL.x = -17;
            danKneL.z = 76;
            danKneR.x = 17;
            danKneR.z = 76;

            
            danArmL.rotationY = -1.3;
            danArmR.rotationY = 1.3;
            
        
            
            
              //
            motionTypes = new Vector.<String>();
            motionTypes[0] = "stand";
            motionTypes[1] = "walk";
            motionTypes[2] = "dash";
         //   motionTypes[3] = "jump";

            
        }
        
        public function setRandomAI():void {
              bodyR = Math.random() * Math.PI * 2;
            danBody.rotationZ = bodyR;
            setMotionRandom();
        }
        
        public function cloneParts(src:Danbord):void {
    
            danBody.geometry = bodyGeometry = src.bodyGeometry;
            danBody.addSurface(MATERIAL, 0, bodyGeometry.numTriangles);
            boxHead = src.boxHead.clone();
            boxSldL = src.boxSldL.clone();
            boxSldR = src.boxSldR.clone();
            boxArmL = src.boxArmL.clone();
            boxArmR = src.boxArmR.clone();
            boxKneL = src.boxKneL.clone();
            boxKneR = src.boxKneR.clone();
            boxLegL = src.boxLegL.clone();
            boxLegR = src.boxLegR.clone();
            
            planeSktL = src.planeSktL.clone();
            planeSktR = src.planeSktR.clone();
            planeSktF = src.planeSktF.clone();
            planeSktB = src.planeSktB.clone();

        }
        
        override public function clone():Object3D {
            var mirror:Danbord = new Danbord();
            mirror.setupStandardHierachy();
            
            mirror._scaleX = _scaleX;
            mirror._scaleY = _scaleY;
            mirror._scaleZ = _scaleZ;
            
            mirror.cloneParts(this);
            mirror.setupPartHierachy();
            
            return mirror;
        }
        
        private function setupStandardHierachy():void {
            addChild(danBody);
            danBody.addChild(danHead);
            danBody.addChild(danSldL);
            danBody.addChild(danSldR);
            danSldL.addChild(danArmL);
            danSldR.addChild(danArmR);
            danBody.addChild(danKneL);
            danBody.addChild(danKneR);
            danKneL.addChild(danLegL);
            danKneR.addChild(danLegR);
        }
        
    
        
        public function setupBlueprintHierachy():void 
        {
            
    
            setupStandardHierachy();
            
            if (textureCombiner == null) textureCombiner = getTextureCombiner();
             /*
            danBody:体
            -danHead:頭
            -danSldL:左肩
            --danArmL:左腕
            -danSldR:右肩
            --danArmR:右腕
            -boxKneL:左膝
            --boxLegL:左足
            -boxKneR:右膝
            --boxLegR:右足
            -danSktL:スカート右
            -danSktR:スカート左
            -danSktF:スカート前
            -danSktB:スカート後
            */
            
            // 10 box
            //パーツを作成
            ///*
            var boxBody:Box;
           textureCombiner.setupAtlasUVCoordinates( boxBody = new Box(76, 60, 102, 1, 1, 1, false, MATERIAL), boxBody_textures );
            textureCombiner.setupAtlasUVCoordinates( boxHead = new Box(160, 102, 102, 1, 1, 1, false, MATERIAL), boxHead_textures );
            textureCombiner.setupAtlasUVCoordinates( boxSldL = new Box(20, 20, 20, 1, 1, 1, false, MATERIAL),  boxSldL_textures);
           textureCombiner.setupAtlasUVCoordinates(  boxSldR = new Box(20, 20, 20, 1, 1, 1, false, MATERIAL), boxSldR_textures );
            textureCombiner.setupAtlasUVCoordinates( boxArmL = new Box(106, 26, 26, 1, 1, 1, false, MATERIAL), boxArmL_textures );
           textureCombiner.setupAtlasUVCoordinates(  boxArmR = new Box(106, 26, 26, 1, 1, 1, false, MATERIAL), boxArmR_textures );
           textureCombiner.setupAtlasUVCoordinates(  boxKneL = new Box(20, 20, 20, 1, 1, 1, false, MATERIAL), boxKneL_textures );
           textureCombiner.setupAtlasUVCoordinates(  boxKneR = new Box(20, 20, 20, 1, 1, 1, false, MATERIAL), boxKneR_textures );
           textureCombiner.setupAtlasUVCoordinates(  boxLegL = new Box(30, 56, 66, 1, 1, 1, false, MATERIAL), boxLegL_textures );
          textureCombiner.setupAtlasUVCoordinates(  boxLegR = new Box(30, 56, 66, 1, 1, 1, false, MATERIAL),  boxLegR_textures);
            //*/
    
            // 4 plane
        //    /*
           textureCombiner.setupAtlasUVCoordinates(  planeSktL = new Plane(60, 30, 1, 1, true, false,MATERIAL), planeSktL_textures );
           textureCombiner.setupAtlasUVCoordinates(  planeSktR = new Plane(60, 30, 1, 1, true, false, MATERIAL), planeSktR_textures );
           textureCombiner.setupAtlasUVCoordinates(  planeSktF = new Plane(76, 30, 1, 1, true, false,  MATERIAL), planeSktF_textures );
            textureCombiner.setupAtlasUVCoordinates( planeSktB = new Plane(76, 30, 1, 1, true, false, MATERIAL), planeSktB_textures );
   // */
       
            setupPartHierachy();
            
            danBody.addChild(boxBody);
        
            //座標・角度設定
            boxBody.z = 137
            boxHead.z = 51;
            boxArmL.x = -63;
            boxArmR.x = 63;
            boxLegL.z = -38;
            boxLegR.z = -38;

            // setup skirts
            planeSktL.rotationX = Math.PI * 0.5;
            planeSktL.rotationZ = Math.PI * 0.5;
            planeSktL.z = -15;
            planeSktR.rotationX = Math.PI * 0.5;
            planeSktR.rotationZ = Math.PI * -0.5;
            planeSktR.z = -15
            planeSktF.rotationX = Math.PI * 0.5;
            planeSktF.rotationZ = Math.PI * 0;
            planeSktF.z = -15
            planeSktB.rotationX = Math.PI * 0.5;
            planeSktB.rotationZ = Math.PI * 1;
            planeSktB.z = -15;

            var danSktL:Object3D = new Object3D();
            var danSktR:Object3D = new Object3D();
            var danSktF:Object3D = new Object3D();
            var danSktB:Object3D = new Object3D();      
            danSktL.x = -38;
            danSktL.z = 86;
            danSktL.rotationY = 0.5;
            danSktR.x = 38;
            danSktR.z = 86;
            danSktR.rotationY = -0.5;
            danSktF.y = 30;
            danSktF.z = 86;
            danSktF.rotationX = 0.5;
            danSktB.y = -30;
            danSktB.z = 86;
            danSktB.rotationX = -0.5;
            danBody.addChild(danSktL);
            danBody.addChild(danSktR);
            danBody.addChild(danSktF);
            danBody.addChild(danSktB);
            
                        
            danSktL.addChild(planeSktL);
            danSktR.addChild(planeSktR);
            danSktF.addChild(planeSktF);
            danSktB.addChild(planeSktB);
            
            
            
            var combinedMeshList:Vector.<Mesh> = new Vector.<Mesh>();
            combinedMeshList.push(boxBody);
            combinedMeshList.push(planeSktL);
            combinedMeshList.push(planeSktR);
            combinedMeshList.push(planeSktF);
            combinedMeshList.push(planeSktB);
            var combinedMesh:Mesh = MeshUtils.combine(combinedMeshList, MATERIAL);
            combinedMesh.geometry.calculateNormals();
            combinedMesh.geometry.calculateTangents(0);
            danBody.geometry = bodyGeometry = combinedMesh.geometry;
            danBody.addSurface(MATERIAL, 0, bodyGeometry.numTriangles);
            danBody.removeChild(boxBody);
            danBody.removeChild(danSktL);
            danBody.removeChild(danSktR);
            danBody.removeChild(danSktF);
            danBody.removeChild(danSktB);
        
            setupPartHierachy();
        
            // consider: instead of setupPart hierachy, bake plane geometry into parent coordinate space and let parents be meshes
        }
        
        private function setupPartHierachy():void {
            
            
            danHead.addChild(boxHead);
            danSldL.addChild(boxSldL);
            danSldR.addChild(boxSldR);
            danArmL.addChild(boxArmL);
            danArmR.addChild(boxArmR);
            danKneL.addChild(boxKneL);
            danKneR.addChild(boxKneR);
            danLegL.addChild(boxLegL);
            danLegR.addChild(boxLegR);
   
        
        }
        

        
        public var motionI:int = 0;
        public var ai:Boolean = true;
        
        //モーションを設定
        public function setMotion(_motion:String):void {
            if(motionType != _motion){
                motionType = _motion;
                motionR = 0;
                motionI = 0;
                danBody._rotationX = 0;
                danBody._rotationY = 0;
                danBody.transformChanged = true;
                switch(_motion) {
                    case "stand":
                        sp = 0;
                        break;
                    case "walk":
                        bodyR = Math.random() * Math.PI * 2;
                        if (ai) danBody.rotationZ = bodyR
                        sp = (2 + Math.random() * 2) * scaleX;
                        break;
                    case "dash":
                        bodyR = Math.random() * Math.PI * 2;
                      if (ai)  danBody.rotationZ = bodyR
                        sp = (6 + Math.random() * 4) * scaleX;
                        break;
                    case "jump":
                        spZ = (8 + Math.random() * 8) * scaleX;
                        ground = false;
                        break;
                    default :
                        break;
                }
            }
        }
        //ランダムにモーションを設定(ジャンプの有無)
        public function setMotionRandom(_jump:Boolean = true):void {
            if (_jump) {
                setMotion(motionTypes[Math.floor(Math.random() * motionTypes.length)])
            }else {
                setMotion(motionTypes[Math.floor(Math.random() * (motionTypes.length - 1))])
            }
        }
        
        public static const MOTION_TIMES:Object = {
            stand: 0.03,
            walk: 0.1,
            dash: 0.23
            //jump: 0.23
        }
        
        public static var STAND:DangBoardClip;
        public static var WALK:DangBoardClip;
        public static var DASH:DangBoardClip;
        
        public static function precomputeAll():void {
            STAND = precompute("stand");
            WALK = precompute("walk");
            DASH = precompute("dash");
        }
        
        private static function precompute(motionType:String):DangBoardClip {
            var clip:DangBoardClip = new DangBoardClip();

            var amt:Number = MOTION_TIMES[motionType];
            var units:int = Math.round( 2 * Math.PI / amt );
            clip.motionR = amt = 2 * Math.PI / units;
            
            for (var i:int = 0 ; i < units; i++) {
                var frame:DangBoardFrame =  new DangBoardFrame();
                clip.frames[clip.frameLen++] = frame;
                var motionR:Number = i * amt;
                  switch(motionType) {
                    case "stand":
                        frame.danBody_z = 0;
                        frame.danHead_rotationX = Math.sin(-motionR) * 0.04;
                        frame.danArmL_rotationY = Math.sin(-motionR) * 0.1 + -1.3;
                        frame.danArmR_rotationY = Math.sin(motionR) * 0.1 + 1.3;
                        frame.danSldL_rotationX = 0;
                        frame.danSldR_rotationX = 0;
                        frame.danLegL_rotationX = 0;
                        frame.danLegR_rotationX = 0;
                        frame.danKneL_rotationY = 0;
                        frame.danKneR_rotationY = 0;
                        break;
                    case "walk":
                        frame.danBody_z = Math.sin(motionR * 2) * 3 + 3;
                        frame.danHead_rotationX = Math.cos(motionR*2) * 0.03;
                        frame.danArmL_rotationY = -1.3;
                        frame.danArmR_rotationY = 1.3;
                        frame.danSldL_rotationX = Math.sin(-motionR) * 0.3;
                        frame.danSldR_rotationX = Math.sin(motionR) * 0.3;
                        frame.danLegL_rotationX = Math.sin(motionR) * 0.4;
                        frame.danLegR_rotationX = Math.sin( -motionR) * 0.4;
                        frame.danKneL_rotationY = 0;
                        frame.danKneR_rotationY = 0;
                        break;
                    case "dash":
                        frame.danBody_z = Math.sin(motionR * 2) * 8 + 8;
                        frame.danHead_rotationX = Math.cos(motionR*2) * 0.05;
                        frame.danArmL_rotationY = -1.3;
                        frame.danArmR_rotationY = 1.3;
                        frame.danSldL_rotationX = Math.sin(-motionR) * 0.6;
                        frame.danSldR_rotationX = Math.sin(motionR) * 0.6;
                        frame.danLegL_rotationX = Math.sin(motionR) * 0.6;
                        frame.danLegR_rotationX = Math.sin( -motionR) * 0.6;
                        frame.danKneL_rotationY = 0;
                        frame.danKneR_rotationY = 0;
                        break;
                    /*
                    case "jump":
                        frame.danBody_z = 0;
                        frame.danHead_rotationX = Math.max(Math.min(spZ * 0.1, 0.8), -0.5);
                        frame.danArmL_rotationY = -Math.max(Math.min(spZ * 0.2, 1.4), -0.2);
                        frame.danArmR_rotationY = Math.max(Math.min(spZ * 0.2, 1.4), -0.2);
                        frame.danSldL_rotationX = 0;
                        frame.danSldR_rotationX = 0;
                        frame.danKneL_rotationY = -Math.max(Math.min(spZ * 0.2, 0), -0.2);
                        frame.danKneR_rotationY = Math.max(Math.min(spZ * 0.2, 0), -0.2);
                        frame.danLegL_rotationX = 0;
                        frame.danLegR_rotationX = 0;
                        break;
                    */
                    default :
                        break;
                }
            }
            

            return clip;
        }
        
        
         public function enterFrameEvent2() :void {
        
            //danUpper.rotationZ += .1;
        //    danHead.rotationZ += .1;
         //   danBody.rotationZ = bodyR;
            var clip:DangBoardClip;
    
            switch(motionType) {
                case "stand": 
                    clip = STAND;
                    danBody.y = 0;
                break;
                case "walk":
                    clip = WALK;
                break;
                case "dash":
                    clip = DASH;
                 
                break;
                    
                case "jump":
                    motionR += 0.23;
                    danBody.z = 0;
                    danHead.rotationX = Math.max(Math.min(spZ * 0.1, 0.8), -0.5);
                    danArmL.rotationY = -Math.max(Math.min(spZ * 0.2, 1.4), -0.2);
                    danArmR.rotationY = Math.max(Math.min(spZ * 0.2, 1.4), -0.2);
                    danSldL.rotationX = 0;
                    danSldR.rotationX = 0;
                    danKneL.rotationY = -Math.max(Math.min(spZ * 0.2, 0), -0.2);
                    danKneR.rotationY = Math.max(Math.min(spZ * 0.2, 0), -0.2);
                    danLegL.rotationX = 0;
                    danLegR.rotationX = 0;
                    
                    return;
                default :
                    return;
                    
            }
            
            
            if (motionI >= clip.frameLen) motionI = 0;
            var frame:DangBoardFrame = clip.frames[motionI];
                    
                    danBody._z = frame.danBody_z;
                    danHead._rotationX = frame.danHead_rotationX;
                    danArmL._rotationY = frame.danArmL_rotationY
                    danArmR._rotationY = frame.danArmR_rotationY
                    danSldL._rotationX = frame.danSldL_rotationX
                    danSldR._rotationX = frame.danSldR_rotationX
                    danKneL._rotationY = frame.danKneL_rotationY
                    danKneR._rotationY = frame.danKneR_rotationY
                    danLegL._rotationX = frame.danLegL_rotationX
                    danLegR._rotationX = frame.danLegR_rotationX
                    
                    danBody.transformChanged = true;
                    danHead.transformChanged = true;
                     danArmL.transformChanged = true;
                    danArmR.transformChanged = true;
                    danSldL.transformChanged = true;
                    danSldR.transformChanged = true;
                    danKneL.transformChanged = true;
                    danKneR.transformChanged = true;
                    danLegL.transformChanged = true;
                    danLegR.transformChanged = true;
                    
                    
                    
            motionI++;
             
             
         }
        
      
    
    }
    
class DanboClone extends MeshSetClone {

          
          public var danbo:Danbord;
          
          public var next:DanboClone;
          
          
    public function DanboClone() {        
        
    }
    
    
    
    
}



//Texture.as
//テクスチャを一括読み込み
//package {

    import alternativa.engine3d.core.Camera3D;
    import alternativa.engine3d.core.Debug;
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.materials.FillMaterial;
    import alternativa.engine3d.materials.Material;
    import alternativa.engine3d.objects.Mesh;
    import alternativa.engine3d.primitives.Box;
    import flash.display.FrameLabel;
    import flash.display.Shape;
    import flash.display.Stage;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Matrix;
    import flash.geom.Vector3D;
    import flash.system.LoaderContext;
    import flash.ui.Keyboard;
    import flash.utils.Dictionary;
    

    //
    //Alternativa3D
    import alternativa.engine3d.core.Object3D;
    import alternativa.engine3d.materials.TextureMaterial;
        
    
class TextureCombiner {
    private var atlasRect:Rectangle;
    private var atlasArray:Array;
    
    public var divisor:int = 4;
    private var idDict:Dictionary;
    
    public function parseXML(xml:XML):void {
                atlasRect = new Rectangle();
            atlasArray = [];
            idDict = new Dictionary();
        
            atlasRect.width = int(xml.@width);
            atlasRect.height = int(xml.@height);
            var sheetList:XMLList =  xml.*;
            var len:int = sheetList.length();
            for (var i:int = 0; i < len; i++) {
                var node:XML = sheetList[i];
                // NOTE: order not validated!
                if (node.@id != undefined) idDict[node.@id.toString()] = i;
                atlasArray.push(  new Rectangle(int(node.@x), int(node.@y), int(node.@width), int(node.@height)) );
            }
            
            
    }


public function setupAtlasUVCoordinates(obj:Object3D, stringLookup:Array=null):void 
        {
        var mesh:Mesh = obj as Mesh;
        if ( mesh == null) throw new Error("Obj is null or cast to Mesh failed:" + obj);
            var len:int;
            var indices:Vector.<uint> = mesh.geometry._indices;
            var uvCoords:Vector.<Number> = mesh.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
            var vertexVisited:Vector.<int> = new Vector.<int>(mesh.geometry.numVertices,true);
            len = indices.length;
            
            var i_divisor:Number = 1 / divisor;
        
            
            for (var i:int = 0; i < len; i += 3) {

                var a:int = indices[i];
                
                if (indices[i + 1] > a)  a = indices[i + 1];
                if (indices[i + 2] > a)  a = indices[i + 2];
                a *= i_divisor;
            
                var rect:Rectangle = stringLookup!= null ? atlasArray[ idDict[ stringLookup[a] ] ]  : atlasArray[a];
                var wRatio:Number = rect.width / atlasRect.width;
                var hRatio:Number = rect.height / atlasRect.height;
                
                a = indices[i];
                if (vertexVisited[a] == 0) {
                    uvCoords[(a<<1)] = rect.x / atlasRect.width +  uvCoords[(a<<1)] * wRatio;
                    uvCoords[(a<<1)+1] = rect.y / atlasRect.height +  uvCoords[(a<<1)+1] * hRatio;
                    vertexVisited[a] = 1;
                }
                a = indices[i+1];
                if (vertexVisited[a] == 0) {
                uvCoords[(a<<1)] = rect.x / atlasRect.width +  uvCoords[(a<<1)] * wRatio;
                    uvCoords[(a<<1)+1] = rect.y / atlasRect.height +  uvCoords[(a<<1)+1] * hRatio;
                    vertexVisited[a] = 1;
                }
                a = indices[i+2];
                if (vertexVisited[a] == 0) {
                uvCoords[(a<<1)] = rect.x / atlasRect.width +  uvCoords[(a<<1)] * wRatio;
                    uvCoords[(a<<1)+1] = rect.y / atlasRect.height +  uvCoords[(a<<1)+1] * hRatio;
                    vertexVisited[a] = 1;
                }
                
            }
            
            mesh.geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], uvCoords);
            
            /*
            for (var i:int = 0; i < len ; i += 2) {
                //uvCoords[i] = rect.
                //uvCoords[i+1]
            }
            */
            //var dict:Dictionary = new Dictionary();
            /*
            for (var f:Face = mesh.faceList; f != null; f = f.next) {
                var rect:Rectangle = atlasArray[f.id.materialIndex];
                var wRatio:Number = rect.width / atlasRect.width;
                var hRatio:Number = rect.height / atlasRect.height;
                for (var w:Wrapper = f.wrapper; w != null; w = w.next) {
                    var v:Vertex = w.vertex;
                    if (v.drawId < 0) continue;
                    //if (dict[v]) throw new Error("ALREADY PROCESSED!");
                    v.drawId = -1;
                    v.u = rect.x / atlasRect.width +  v.u * wRatio;
                    v.v = rect.y / atlasRect.height +  v.v * hRatio;
                    
                    //dict[v] = true;
                }
            }
            */
        }
        
}

class MeshUtils
    {

        public static function combine(meshes:Vector.<Mesh>, material:Material = null):Mesh
        {
            var res:Mesh;

            var indices:Vector.<uint> = new Vector.<uint>();
            var vert:Vector.<Number> = new Vector.<Number>();
            var norm:Vector.<Number> = new Vector.<Number>();
            var tex:Vector.<Number> = new Vector.<Number>();

            var i:int, il:uint, nil:uint, vec:Vector3D,vecn:Vector3D, transf:Matrix3D,vt:Vector.<Number>, v:Vector.<Number>,vn:Vector.<Number>,tempind:Vector.<uint>;

            for each (res in meshes)
            {        
                v = res.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
                il = v.length;
                for (i = 0; i < il; i += 2)
                {
                    tex.push(v[i], v[i + 1]);
                }

                v = res.geometry.getAttributeValues(VertexAttributes.POSITION);
                vn = res.geometry.getAttributeValues(VertexAttributes.NORMAL);
                il = v.length;
                for (i = 0; i < il; i += 3)
                {
                    vec = new Vector3D(v[i], v[i + 1], v[i + 2]);
                    vecn = new Vector3D(vn[i], vn[i + 1], vn[i + 2]);
                    vecn.incrementBy(vec);

                    vec = res.localToGlobal(vec);
                    vecn = res.localToGlobal(vecn);

                    vert.push(vec.x, vec.y, vec.z);

                    vecn.decrementBy(vec);
                    vecn.normalize();

                    norm.push(vecn.x, vecn.y, vecn.z);
                }

                tempind = res.geometry.indices;
                il = tempind.length;
                for (i = 0; i < il; i++)
                {
                    indices.push(nil + tempind[i])
                }
                nil += v.length / 3;

            }

            res = new Mesh();

            var geometry:Geometry = new Geometry(vert.length / 3);
            geometry._indices = indices;
            var attributes:Array = [];
            attributes[0] = VertexAttributes.POSITION;
            attributes[1] = VertexAttributes.POSITION;
            attributes[2] = VertexAttributes.POSITION;
            attributes[3] = VertexAttributes.TEXCOORDS[0];
            attributes[4] = VertexAttributes.TEXCOORDS[0];
            attributes[5] = VertexAttributes.NORMAL;
            attributes[6] = VertexAttributes.NORMAL;
            attributes[7] = VertexAttributes.NORMAL;
            attributes[8] = VertexAttributes.TANGENT4;
            attributes[9] = VertexAttributes.TANGENT4;
            attributes[10] = VertexAttributes.TANGENT4;
            attributes[11] = VertexAttributes.TANGENT4;

            geometry.addVertexStream(attributes);
            geometry.setAttributeValues(VertexAttributes.POSITION, vert);
            geometry.setAttributeValues(VertexAttributes.NORMAL, norm);
            geometry.setAttributeValues(VertexAttributes.TEXCOORDS[0], tex);
            geometry.calculateTangents(0);

            res.geometry = geometry;
            res.addSurface(material, 0, indices.length / 3);
            res.calculateBoundBox();

            return res;
        }
    }

class CrossHair extends Sprite {
    public function CrossHair() {
        graphics.lineStyle(1, 0xDDEE44, 1);
        graphics.moveTo(0, -2);
        graphics.lineTo(0, -6);
        
        graphics.moveTo(0, 2);
        graphics.lineTo(0, 6);
        
        graphics.moveTo(2, 0);
        graphics.lineTo(6, 0);
        
        graphics.moveTo(-2, 0);
        graphics.lineTo(-6, 0);
    }
}


// -- Other utilities/classes



import flash.utils.Dictionary;
import flash.events.KeyboardEvent;
import flash.display.Stage;

    class KeyBinder {
        protected var _keys : flash.utils.Dictionary;
        public function KeyBinder(stage : flash.display.Stage = null) : void {
            this._keys = new flash.utils.Dictionary();
            if(stage != null) this.setupStageListeners(stage);
        }
        
        public function setupStageListeners(stage : flash.display.Stage) : void {
            stage.addEventListener(flash.events.KeyboardEvent.KEY_UP,this.onKeyUp);
            stage.addEventListener(flash.events.KeyboardEvent.KEY_DOWN,this.onKeyDown);
        }
        
        public function removeStageListeners(stage : flash.display.Stage) : void {
            stage.removeEventListener(flash.events.KeyboardEvent.KEY_UP,this.onKeyUp);
            stage.removeEventListener(flash.events.KeyboardEvent.KEY_DOWN,this.onKeyDown);
        }
        
        protected function onKeyDown(e : flash.events.KeyboardEvent) : void {
            if(this._keys[e.keyCode]) this._keys[e.keyCode](true);
        }
        
        protected function onKeyUp(e : flash.events.KeyboardEvent) : void {
            if(this._keys[e.keyCode]) this._keys[e.keyCode](false);
        }
        
        public function bindKey(key : int,method : *) : void {
            this._keys[key] = method;
        }
        
        public function hasBinding(key : int) : Boolean {
            return this._keys[key] != null;
        }
        
        public function clear() : void {
            this._keys = new flash.utils.Dictionary();
        }
        
    }

// Danbo Player package

 class DanboPlayer {
        
        private var danbo:Danbord;
        private var scene:Object3D;
        
        public var thirdPerson:OrbitCameraMan;
        
        public var surfaceMovement:SurfaceMovement;
        
        public var jump:Jump;
        
        public var velocity:Vector3D = new Vector3D();
        public var position:Vector3D = new Vector3D();
        public var rotation:Vector3D = new Vector3D();
        public var radius:Vector3D = new Vector3D(16,16,32);
    
        private var /*static const*/ playerMover:PlayerMover = new PlayerMover();
        private var /*static const*/ qPhysics:QPhysics  = new QPhysics(QPhysics.FLAG_GRAVITY);
        
        private var lookAtObject:Object3D = new Object3D();
        public var excludedObjects:Dictionary = new Dictionary();
        private var lookAtOffset:Vector3D = new Vector3D();
        
        private var keyBinder:KeyBinder = new KeyBinder();
        
        private var colliderPosition:Vector3D = new Vector3D();
        
        private var walkSpeed:Number;
        private var runSpeed:Number;
        
        public function calculateMovementSpeeds():void {
            walkSpeed = 1 + (10 * radius.z/400);
            runSpeed = 1 + (30 * radius.z/400);
        }

        
        public function DanboPlayer(danbo:Danbord, camera:Camera3D, scene:Object3D, stage:Stage) {
            this.scene = scene;
            

            // -- Player-Specific stuff for Client
            thirdPerson = new OrbitCameraMan(camera, lookAtObject, stage, scene, danbo, rotation, false);
            
            //thirdPerson.controller.easingSeparator  = 12;
            thirdPerson.preferedZoom = 1000;
            thirdPerson.controller.minDistance = 100;
            thirdPerson.controller.maxDistance = 1800;
            //thirdPerson.controller.minAngleLatitude = 5;
            thirdPerson.controller.minAngleLatitude = -85;
            thirdPerson.controller.maxAngleLatidude =75;
            thirdPerson.followAzimuth = true;
            thirdPerson.useFadeDistance = true;
            thirdPerson.maxFadeAlpha = 1;
            
            keyBinder.clear();
            keyBinder.setupStageListeners(stage);
            keyBinder.bindKey(Keyboard.W, forward);
            keyBinder.bindKey(Keyboard.S, back);
            keyBinder.bindKey(Keyboard.A, left);
            keyBinder.bindKey(Keyboard.D, right);
            keyBinder.bindKey(Keyboard.SPACE, space);
            keyBinder.bindKey(Keyboard.SHIFT, speedChange);
            
           // stage.addEventListener(MouseEvent.MOUSE_WHEEL, thirdPerson.mouseWheelHandler);
            
            
            // -- The below also applies to NPCs or "other players" besides Client
            
            this.danbo = danbo;
            updateRadius();
            
    
            lookAtOffset.y = 0;
            lookAtOffset.z = 200;
            
            surfaceMovement = new SurfaceMovement();
            jump = new Jump(1,2135);
            
            
            excludedObjects[danbo] = true;
           // excludedObjects[danbo.kage] = true;
            
            //danbo.z += 100;
            setupPosition();
            
            // view collision sphere
            ///*
            var sphere:Object3D = new GeoSphere(radius.z, 2, false, new FillMaterial(0xFF0000, .3) );
            sphere.x = position.x;
            sphere.y = position.y;
            sphere.z = position.z;
            
           // scene.addChild( sphere);
           
            //*/
            

            calculateMovementSpeeds();
        }
        

        
        // Client controls
                
        
        public function getZoomRatio():Number {
            return  (thirdPerson.preferedZoom- thirdPerson.controller.minDistance) /  (thirdPerson.controller.maxDistance -  thirdPerson.controller.minDistance);
        }
        
        public function set zoom(ratio:Number):void {
            ratio = 1 - ratio;
            thirdPerson.preferedZoom =   thirdPerson.controller.minDistance + (thirdPerson.controller.maxDistance -  thirdPerson.controller.minDistance) * ratio;
        }
        
        
        
        
        
        private function speedChange(boo:Boolean):void 
        {
            _runMode = !boo;
        }
        
        private function space(boo:Boolean):void 
        {
            if (boo == _space) return;
            _space = boo;
        }
        
        private function right(boo:Boolean):void 
        {
            if (boo == _right) return;
            surfaceMovement.strafe_state += boo ? 1 : -1;
            _right = boo;
        }
        
        private function left(boo:Boolean):void 
        {
            if (boo == _left) return;
            surfaceMovement.strafe_state += boo ? -1 : 1;
            _left = boo;
            
        }
        
        private function back(boo:Boolean):void 
        {
            if (boo == _back) return;
            surfaceMovement.walk_state += boo ? -1 : 1;
            _back = boo;
        }
        
        private function forward(boo:Boolean):void 
        {
            if (boo == _forward) return;
            surfaceMovement.walk_state += boo ? 1 : -1;
            _forward = boo;
        }
        

        
        // Methods
        private function updateRadius():void 
        {
            var boundBox:BoundBox = Object3DUtils.calculateHierarchyBoundBox(danbo, danbo);
            radius.x = (boundBox.maxX - boundBox.minX + 12)*danbo._scaleX * .5;
            radius.y = (boundBox.maxY - boundBox.minY + 12)*danbo._scaleY * .5;
            radius.z = (boundBox.maxZ - boundBox.minZ + 35)*danbo._scaleZ * .5;
        }
        
        private function setupPosition():void 
        {
            position.x = danbo.x;
            position.y = danbo.y;
            position.z = danbo.z + radius.z;
            
        }
        
        private function preRenderPosition():void {
            danbo.x = position.x;
            danbo.y = position.y;
            danbo.z = position.z - radius.z;
            
           // danbo.kage.x = danbo.x;
          //  danbo.kage.y = danbo.y;
         
        }
        
        private var _back:Boolean= false;
        private var _forward:Boolean= false;
        private var _left:Boolean = false;
        private var _right:Boolean = false;
    
        private var _space:Boolean = false;
        private var _runMode:Boolean = true;
        private var maxGroundNormal:Vector3D;
        

        public function update(t:Number):void {
        
            // Update global environs
            qPhysics.update(t, velocity);
            
            rotation.x = danbo._rotationX;
            rotation.y = danbo._rotationY;
            rotation.z = danbo._rotationZ;
            
            // Jump
            jump.update(t);
            var gotJump:Boolean = false;
            if (maxGroundNormal != null && _space) {
                gotJump = jump.do_jump(velocity, t);
            }
            
            // Movement along surface
            var lastPosition:Vector3D = position.clone();
    
            maxGroundNormal = playerMover.queryMove(radius, position, velocity, scene);        
            var baseSpeed:Number = _runMode ? runSpeed : walkSpeed;
            surfaceMovement.WALK_SPEED = baseSpeed;
            surfaceMovement.WALKBACK_SPEED = baseSpeed * 1;
            surfaceMovement.STRAFE_SPEED = baseSpeed * 1;
            
            surfaceMovement.update(t, position, rotation, velocity, maxGroundNormal);
            
            if (maxGroundNormal == null) {
                danbo.spZ -= 9.8 / 60;
            //    myDanbord.z += myDanbord.spZ;
            }
            
            // Animations
            danbo.setMotion(  maxGroundNormal == null ? "jump" :
            
            maxGroundNormal != null &&  (surfaceMovement.strafe_state != 0 || surfaceMovement.walk_state != 0) ? 
                    _runMode ?  "dash" : "walk" 
                    
                    
                    :
                    "stand"
            );
            danbo.enterFrameEvent2();
            
            // Update animations for Client
            /*
            var useLat:Number = thirdPerson.controller.angleLatitude;
            if (useLat > 45 ) useLat = 45;
            if (useLat < 0 ) useLat = 0;
            danbo.danHead.rotationX = -useLat * DEG_TO_RAD * .7;
            */
            
        }
        
        private static const DEG_TO_RAD:Number = Math.PI / 180;
        
        
        private function preRenderCallback():void {
            
            //  Adjust look at position for Client Camera
            // This is basically a hardcoded way to translate position of lookAtObject from local coordinate space to global coordinate space. 
            //  If not, one would need to uncomment "//   testLook = _followTarget.localToGlobal(testLook);"
            // from OrbitCameraController class.
            lookAtObject._x = lookAtOffset.x +  danbo.danHead._x + danbo.danBody._x;
            lookAtObject._y = lookAtOffset.y +  danbo.danHead._y + danbo.danBody._y;
            lookAtObject._z = lookAtOffset.z + danbo.danHead._z + danbo.danBody._z;
            
            if (danbo.transformChanged) danbo.composeTransforms();
            if (lookAtObject.transformChanged) lookAtObject.composeTransforms();
            lookAtObject.localToGlobalTransform.combine(danbo.transform, lookAtObject.transform);
            
            lookAtObject.x = lookAtObject.localToGlobalTransform.d;
            lookAtObject.y = lookAtObject.localToGlobalTransform.h;
            lookAtObject.z = lookAtObject.localToGlobalTransform.l;
        }
        
        private function get pitchRatio():Number {
            return (thirdPerson.controller.angleLatitude - thirdPerson.controller.minAngleLatitude) / (thirdPerson.controller.maxAngleLatidude - thirdPerson.controller.minAngleLatitude);
        }
        
        private function get arcRatio():Number {
            return thirdPerson.controller.angleLatitude < 0 ? -thirdPerson.controller.angleLatitude/thirdPerson.controller.minAngleLatitude : thirdPerson.controller.angleLatitude/thirdPerson.controller.maxAngleLatidude;
        }
        
        public function preRender():void {
            
            preRenderPosition();
            lookAtOffset.y = 90 * arcRatio;  // adjust so that got crosshair to view at center

            
            // Client view
            // This is stupid, have to call this multiple times to ensure Danbo's rotation transform of lookAtOffset vector appears more accruately without jittering.
            preRenderCallback();
            thirdPerson.update();return;
                
            preRenderCallback();
            thirdPerson.update();
            
            preRenderCallback();
            thirdPerson.update();
        }

        
    }
    

class PlayerMover {
    
    public var collisionPoint:Vector3D = new Vector3D();
    public var collisionNormal:Vector3D = new Vector3D();
    private static const GRAVITY_DIR:Vector3D = new Vector3D(0, 0, -1);
    public static var COLLIDER:EllipsoidCollider = new EllipsoidCollider(2, 2, 2);
    
        public function queryMove(radius:Vector3D, position:Vector3D, displacement:Vector3D, scene:Object3D):Vector3D {
            const collider:EllipsoidCollider = COLLIDER;
            collider.radiusX = radius.x;
            collider.radiusY = radius.y;
            collider.radiusZ = radius.z;
            
            const collisionPoint:Vector3D = collisionPoint;
            const collisionNormal:Vector3D = collisionNormal;
            
                var dest:Vector3D = collider.calculateDestination(position, displacement, scene);
                var maxGroundNormal:Vector3D
                if (collider.getCollision(position, displacement, collisionPoint, collisionNormal, scene) ) {
                    if (collisionNormal.z >= .8) {
                        maxGroundNormal = collisionNormal; // <- shoudl still count this?
                    }
                    else {
                        GRAVITY_DIR.z = displacement.z;
                        if ( collider.getCollision(dest, GRAVITY_DIR, collisionPoint, collisionNormal, scene) ) {
                        
                            if (collisionNormal.z >= .8)  {
                                maxGroundNormal =  collisionNormal;
                            }
                    //        collisionEvent.next = groundCollision;
                        }
                        
                    }
                //    p.move.collisions = collisionEvent;
                //    return maxGroundNormal;
                }
                else {
                //    p.move.clearCollisions();
                
                }
                
                position.x = dest.x;
                position.y = dest.y;
                position.z = dest.z;
                
                return maxGroundNormal;
    
        }
        
    
}


    
class Jump 
{
    // State Settings
    public var JUMP_COOLDOWN:Number;
    
    // State for thing
    private var jump_timer:Number;
    private var jump_speed:Number;
    public var enabled:Boolean;  // use this as a master lock to enable/disable jump depending on situation
    
    public function Jump(timeCooldown:Number, jumpSpeed:Number) 
    {
        JUMP_COOLDOWN = timeCooldown;
        jump_speed = jumpSpeed;
        jump_timer = 0;
        enabled = true;
    }
    
    public function update(time:Number ):void {
        jump_timer = jump_timer-time < 0 ? 0 : jump_timer - time;
    }
     
    
    public function do_jump(velocity:Vector3D, time:Number):Boolean {
        if (enabled && this.jump_timer == 0)
        {
            velocity.z += jump_speed * time;
            jump_timer = JUMP_COOLDOWN;         
            return true;
        }
        return false;
    }

}

class SurfaceMovement 
{
    // State settings
    public var WALK_SPEED:Number;
    public var WALKBACK_SPEED:Number;
    public var STRAFE_SPEED:Number;

    // State for thing
    // delta walk/strafe state (-1 for backwards/left, 0 for neither direction, 1 for forwards/right)
    public var walk_state:int; 
    public var strafe_state:int;
    
    public static const WALK_FORWARD:int = 1;
    public static const WALK_STOP:int = 0;
    public static const WALK_BACK:int = -1;

    public static const STRAFE_LEFT:int = -1;
    public static const STRAFE_STOP:int = 0;
    public static const STRAFE_RIGHT:int = 1;
    
    // Normalized forward direction along surface
    public var forwardVec:Vector3D;
    public var rightVec:Vector3D;
    public var friction:Number;

    
    

    public function SurfaceMovement() 
    {
        walk_state = 0;
        strafe_state = 0;
        forwardVec = new Vector3D();
        rightVec = new Vector3D();
        friction = .25;
        
        setWalkSpeeds(16);
        setStrafeSpeed(10);
    }
    
    public function setWalkSpeeds(forwardSpeed:Number, backspeed:Number = -1):void {
        WALK_SPEED = forwardSpeed;
        WALKBACK_SPEED = (backspeed != -1) ? backspeed : forwardSpeed; 
    }
    public function setStrafeSpeed(val:Number):void {
        STRAFE_SPEED = val;
    }
    public function setAllSpeeds(val:Number):void {
        WALK_SPEED = val;
        WALKBACK_SPEED  = val;
        STRAFE_SPEED = val;
    }
        
    public function respond_move_forward():void {
        walk_state = 1;
    }
    public function respond_move_back():void {
        walk_state = -1;
    }
    public function respond_move_stop():void {
        walk_state = 0;
    }
    
    public function respond_strafe_left():void {
        strafe_state = -1;
    }
    public function respond_strafe_right():void {
        strafe_state = 1;
    }
    public function respond_strafe_stop():void {
        strafe_state = 0;
    }

    
    public function update(time:Number, position:Vector3D, rotation:Vector3D, velocity:Vector3D, ground_normal:Vector3D = null):void {
        
        var multiplier:Number;
        
        if (ground_normal != null) { // can walk on ground
            velocity.x *= friction;
            velocity.y *= friction;
            velocity.z *= friction;
            
            /*
             * Math.cos(this.thingBase.azimuth) * Math.cos(this.thingBase.elevation), Math.sin(this.thingBase.azimuth) * Math.cos(this.thingBase.elevation)
             */
            forwardVec.x = -Math.sin(rotation.z); //Math.cos(rotation.z) * Math.cos(rotation.y);  // frm rotation.z azimith
            forwardVec.y = Math.cos(rotation.z);//Math.sin(rotation.z) * Math.cos(rotation.y); // frm rotation.x pitch.  //* Math.cos(rotation.x)
            forwardVec.z = 0;
            if (forwardVec.dotProduct(ground_normal) > 0) {
                multiplier = ground_normal.x * forwardVec.x + ground_normal.y * forwardVec.y + ground_normal.z * forwardVec.z;
                forwardVec.x -= ground_normal.x * multiplier;
                forwardVec.y -= ground_normal.y * multiplier;
                forwardVec.z -= ground_normal.z * multiplier;
            }
            forwardVec.normalize();
            
            if (walk_state != 0 ) {
                multiplier = (walk_state != WALK_BACK) ? WALK_SPEED : -WALKBACK_SPEED;
                velocity.x += forwardVec.x * multiplier;
                velocity.y += forwardVec.y * multiplier;
                velocity.z += forwardVec.z * multiplier;
    
            }
            
            rightVec = forwardVec.crossProduct(ground_normal);  // TODO: inline
            rightVec.normalize();
            if (strafe_state != 0) {
                multiplier = strafe_state != STRAFE_LEFT ? STRAFE_SPEED : -STRAFE_SPEED;
                velocity.x += rightVec.x * multiplier;
                velocity.y += rightVec.y * multiplier;
                velocity.z += rightVec.z * multiplier;
            }
        }
        
    }

}


/**
 * Quake-like physics static controller or stateful component.
 * @author Glidias
 */
class QPhysics 
{
    public static const FLAG_STICKY:int = 8;
    public static const FLAG_DAMPING:int = 4;
    public static const FLAG_BOUNCE:int = 2;
    public static const FLAG_GRAVITY:int = 1;
    
    // State settings
    public var FLAGS:int;
    public var T_BOUNCE:Number;
    public var TAU_DAMP:Number;
    public var N_BOUNCE:Number;

    
    public static var GRAVITY:Number = 40;
    
    public function QPhysics(flags:int=0) {    
    
        FLAGS = flags;
        N_BOUNCE = 0.5;
        T_BOUNCE = 0.9;
        TAU_DAMP = .9;
    }

    public function applyBounce(collisions:CollisionEvent, velocity:Vector3D):void {
        var coll:CollisionEvent = collisions;
        applyBounceWith(velocity, coll.normal, T_BOUNCE, N_BOUNCE);
        coll = coll.next;
        while (coll != null) {
            applyBounceWith(velocity, coll.normal, T_BOUNCE, N_BOUNCE);
            coll = coll.next;
        }
    }
    public static function applyBounceWith(velocity:Vector3D, normal:Vector3D, T_BOUNCE:Number, N_BOUNCE:Number):void {
        var pushBack:Number = normal.dotProduct( velocity);
        var addVel:Vector3D = normal.clone();
        addVel.scaleBy(-pushBack);
        velocity.add(addVel);
        velocity.scaleBy(T_BOUNCE);
        addVel.scaleBy(N_BOUNCE);
        velocity.add(addVel);
    }
    

    
    public function update(time:Number,  velocity:Vector3D, rotation:Vector3D = null, ang_velocity:Vector3D=null):void {
        if (rotation != null) {
            rotation.x += ang_velocity.x * time; // roll
            rotation.y += ang_velocity.y * time; // elevation
            rotation.z += ang_velocity.z * time; // azimuth 
        }
        if ( (FLAGS & FLAG_DAMPING) != 0)
        {
            velocity.scaleBy(Math.exp((-time) / TAU_DAMP)); // need to check on this.
        }
        if ( (FLAGS & FLAG_GRAVITY) != 0)
        {
            velocity.z -= GRAVITY * time;
        }
        
    }

}


class CollisionEvent {
    
    public var collision:Vector3D = new Vector3D();
    public var normal:Vector3D = new Vector3D();
    public var offset:Number;
    public var t:Number;
    
    public static var COLLECTOR:CollisionEvent = new CollisionEvent();
    public var next:CollisionEvent;
    
    public function CollisionEvent() 
    {
    
    }
    
        
    // Pooling, linked list and disposal
    public static function Get(collision:Vector3D, normal:Vector3D, offset:Number, t:Number, geomtype:int):CollisionEvent {
        var c:CollisionEvent = COLLECTOR || (COLLECTOR = new CollisionEvent());
        COLLECTOR = COLLECTOR.next;

        c.next = null;
        
        c.collision.x = collision.x;
        c.collision.y = collision.y;
        c.collision.z = collision.z;
        c.normal.x = normal.x;
        c.normal.y = normal.y;
        c.normal.z = normal.z;
        c.offset = offset;
        c.t = t;
       // c.geomtype = geomtype;
        
        return c;
    }
    

    public function get(collision:Vector3D, normal:Vector3D, offset:Number, t:Number, geomtype:int):CollisionEvent {
        //return Get(collision, normal, offset, t, geomtype);
        var c:CollisionEvent = COLLECTOR || (COLLECTOR = new CollisionEvent());
        COLLECTOR = COLLECTOR.next;

        c.next = null;
        
        c.collision.x = collision.x;
        c.collision.y = collision.y;
        c.collision.z = collision.z;
        c.normal.x = normal.x;
        c.normal.y = normal.y;
        c.normal.z = normal.z;
        c.offset = offset;
        c.t = t;
       // c.geomtype = geomtype;
        
        return c;
    }
    
    
    public function dispose():void {    
        next = COLLECTOR;
        COLLECTOR = this;
    }
    

}

//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_Tx {
        public var str:String = "eNrt3M1LVFEch/EJCnqhRUEFFQUiUURBVIhFkm+FWhON0RRIFFEEUuHC3khQzAizNzMG0iJfmJrI0gkUQwOtbCG0ad+/8svvpStkK4e4Z2bus3jgcPSec8ePyJ2Yk9VFC81loz1VTrtyZrvTBh6UOk0GZ45vsQuntwWa9vT9x/tqbCYdCzTt6fs3nNtht+p3BZr29P1TnRU28qo60LSn7y+Pq+d3Bpr29P3l8Wu6LtC0p+8vj7brRYGmPX1/eXxP1waa9sQff/zxxx9//PHHH3/88ccff/zxxx9//PHHH3/88ccff/zxxx9//PHHH3/88ccff/zxxx9//PHHH3/88ccff/zxxz+7/Tn/Ge7zn5z/Du/57+bLu50W9N+9+T1t2ue0gY4yp8mgtWGPPbxdHGjaM1v8Ey0HrP9hWaBpT98/9aQ8+L//s3v6/vLobj8YaNozW/zlkX5ZHWja0/d39vyHP/74448//vjnlf+3j2dtcqzLpqamvDTWHP7h8B8bfmTpkR82ODzjpbHm8A+HfzL5weKnL9mJ+EUvjTWHfzj8n3cPWPercet7/dVLY83hHw7/l4kbdre9zzoTw14aaw7/cPiPJuPW2V5vjY3XvDTWHP4hef4fitlE6piN9B/x0lhz+PP+H3/88ccff/zxxx9//PHHH3/88ccff/zxxx9//PHHH3/88ccff/zxzz9/zn+6Pf/p2p/z327Pf7v0d/3zD3v4448//vjjjz/++OOPP/74448//vjnv7/L//8PA/f+LsOAiIiIiIiIiIiIiIiIiIiIiIiIiCjHPn/6oDQvGu2pyotc+Kc6KwL//Pl/+xz77L37/uN9NTaTjuVkundX/i7On/zXcyx//PVz/DVdl5Pp3vHHH3/88ccff/zxxx9//PHHP9v8u++XWqyqwE4eKbTXzyoz3l/Xag2tpTXxz37/rjsHbMXyxRaJRLzWrllmQy8Wfu+6Rtf662hNrY1/dvvXlG+eM/O7Xr9rwXvrmvnraG38s9v/VLTwH7fWxqIF761r5q+jtfHPbv93zw/b1sJVc2bFu9fZ57fHFry3rtG1/jpaU2tn4v++q9ImU1H7OR7PyXTveg258vw38SZq924WW0fTPvsyeDzj/XWt1tBaWjPT57+2hr32tGW/9T4uz8l073oNvP/LzL+nrcQGE4e836FcTPeu14A/7//wxx9//PHHP5f9vw3FrLa6wEtj/MPj/yl51EqK1s+9t9dYc/jnv3/v4zLbtGHlP/+2pzl9Df/89R/pr7Gy/RttzeqltnLFkr/SnL6m78Gf5z/88ccff/zxxx9//Dn/yflPzn9z/juwIs2R5kW/Afx7FeQ=";
    }