/*------------------------------------------------------------------------ 
 * Copyright 2007-2008 (c) Dmitri Sviridov, cast3d.com.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *------------------------------------------------------------------------ */
 
	/**
	 *
	 * @author		Dmitri Sviridov - sds
	 * @version		.90
	 * @date 		April, 23 2008
	 */
 
package {
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.*;
	import flash.utils.getTimer;
    import flash.utils.Timer;
    import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFieldType;
	import flash.display.MovieClip;
    import flash.net.URLRequest;
 
    // Cast3D dependencies
	import cast3d.core.Cast3d;
	import cast3d.core.events.LoadEvent;
	import cast3d.loader.Xc3Loader;
	import cast3d.utils.controls.*;
	import cast3d.utils.manipulators.Manipulator;
	import cast3d.utils.manipulators.ppv2.TrackBall;
	import cast3d.utils.controllers.NavigationController;
	import cast3d.nodes.Node3d;
	import cast3d.tracks.Track3d;
	import cast3d.geom.Part3d;
	import cast3d.geom.Skin3d;
	import cast3d.frames.KeyFrame3d;
 
    // Papervision3d dependencies
	import org.papervision3d.scenes.Scene3D;
 
	// Import Papervision3D
	import org.papervision3d.cameras.*;
	import org.papervision3d.scenes.*;
 
	import org.papervision3d.cameras.*;
	import org.papervision3d.scenes.*;
	import org.papervision3d.lights.*;
	import org.papervision3d.render.*;
	import org.papervision3d.view.*;
	import org.papervision3d.materials.*;
	import org.papervision3d.core.proto.MaterialObject3D;	
 
 
	[SWF(backgroundColor="#335566", frameRate="30")]
 
	public class Sample extends Sprite
	{
		private var manipulator:TrackBall;
		private var animator:Cast3d;
		private var cp:ControlPanel;
 
		private var scene:Scene3D;
	    private var camera:Camera3D; 		    	
		private var viewport:Viewport3D;
		private var renderer:BasicRenderEngine;
 
		private var loader:Xc3Loader; 
		private var loaded:Boolean; 
 
		private var statusText:TextField; 
		private var statusTimer:Timer;
 
	    private var _navigations:Array =  new Array;
	    private var _current_nav:int = 0;
 
		public function Sample()
		{
 			setup3DScene();
			setupStage();			      
		}
 
		/**
		 * Configures the Stage object
		 */
		private function setupStage(): void
		{
			this.stage.scaleMode = StageScaleMode.NO_SCALE;
			this.stage.align = StageAlign.TOP_LEFT;
		}
 
		private function setup3DScene(): void
		{
			this.setupPpv3D();
			this.setupCast3D();
			this.setupControls();
			this.loadData();
 
			this.addEventListener(Event.ENTER_FRAME, this.handleEnterFrame);
		}
 
  		/**
		 * initial setup for Papervision3D.
		 */		
		public function setupPpv3D(): void
		{		
			this.viewport = new Viewport3D(300, 400, true, false,false,false);
	        addChild( viewport );
 
			this.scene = new Scene3D();
			this.camera = new Camera3D();			
			this.renderer = new BasicRenderEngine();			
		}
 
  		/**
		 * initial setup for Cast3D.
		 */		
		public function setupCast3D(): void
		{		
            this.loaded = false;
			this.animator = new Cast3d(this.scene, this.camera);
			this.animator.animationType = Cast3d.ANIMATION_TYPE_BYFRAME; //  ANIMATION_TYPE_REAL; //
			this.animator.animationStyle = Cast3d.ANIMATION_STYLE_FORWARD;
			Cast3d.fps = 22;
			this.animator.autoRewind = true;						
		}
 
  		/**
		 *  Function setups visual animation control panel.
		 */		
		public function setupControls(): void
		{		
			cp = new ControlPanel(animator);
			this.stage.addChild(cp);
//			cp.visible = false;	
 
			statusText = new TextField();
			statusText.textColor = 0x0000ff;
            statusText.autoSize = TextFieldAutoSize.LEFT; 
            statusText.type = TextFieldType.DYNAMIC;
            statusText.y = cp.height;
            this.stage.addChild(statusText);
		}
 
  		/**
		 *  Function performs 3D data  load from a X3c file.
		 */				
		private function loadData(): void
		{
			var modelpath:String = loaderInfo.parameters.model;
			var rpath:String = loaderInfo.parameters.rpath;
 
			this.loader = new Xc3Loader( modelpath ? modelpath:  "");
			this.loader.resourcePath = rpath ? rpath : "";
 
            if (!modelpath || modelpath.length == 0)
            {            
				this.loader = new Xc3Loader("../../cast3dImport/models/walk/fig.xc3");
				this.loader.resourcePath = "../../cast3dImport/models/walk";
            }
 
			statusText.text = "loading file: " + loader.sourceURL; 
			statusTimer = new Timer(1000, 0.2);
			// designates listeners for the interval and completion events
			statusTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);				 
			statusTimer.start();			
 
			this.loader.preCalcMotion = false;
			this.loader.addEventListener(LoadEvent.LOAD_COMPLETE, this.cast3dLoadComplete);
			this.loader.addEventListener(LoadEvent.LOAD_ERROR, this.cast3dLoadError);
			this.loader.addEventListener(LoadEvent.LOAD_PROGRESS, this.cast3dLoadProgress);
//			this.loader.portLoader.onCreateColorMaterial = onColorMaterial;
//			this.loader.portLoader.onCreateAssetMaterial = onAssetMaterial;
 
			/** Set Call back functions to modify, augumnet or subsitute
			 *  material or/and geometry data. */
 
			this.loader.load(this.animator.source);	
 
		}
 
 		private function onColorMaterial (name:String, material:MaterialObject3D):MaterialObject3D
		{
		    material = new WireframeMaterial(0x777700,1);		
		    material.doubleSided = true;
			return material;
       }
 
		private function onAssetMaterial ( name:String, material:BitmapMaterial):MaterialObject3D
		{					
            return new WireframeMaterial(0x00FF00,1);
       }
		/**
		 * Timer handler
		 */		
       private function onTimerComplete(event:TimerEvent):void
       {
            trace("Time's Up!");
			statusText.textColor = 0xff0000;
            statusText.text = "Loading time exceeded 20 nimutes!"; 
            removeTimer();       	        	            
       }
 
		/**
		 * At the end of load, removes timer
		 */		
       private function removeTimer():void 
        {
           	statusTimer.stop();
 	   		statusTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,onTimerComplete);
    	}
 
		/**
		 * Handles the ENTER_FRAME event and updates the 3D scene.
		 */
		private function handleEnterFrame(event: Event): void
		{					
            if (!this.loaded) return;
 
			var time:Number = getTimer();	
 
			// Update cast3D first		
			this.animator.render();
 
			// then render scene 	
			this.renderer.renderScene(scene, camera, viewport);
 
			// Update stat data					
			if (this.animator.source && cp)
			{
				var frame:int = this.animator.source.currentFrame; 
				var kframe:int = this.animator.source.currentKeyFrame; 
				cp.setCurrentFrame(kframe,frame);
				cp.setCurrentTime(animator.currentTime);
				cp.currentFps = 1000.0/(getTimer() - time);
				if (this.manipulator) manipulator.update();
			}			
		}
 
		private function daeLoadComplete(e:Event):void {
			trace("loaded");
//			view.singleRender();
			this.manipulator = new TrackBall(this.animator,this.stage,
									 this.viewport.viewportWidth, this.viewport.viewportHeight,
									 Manipulator.Y_UP,
									 Manipulator.X_S,Manipulator.Z_S,Manipulator.NY_S
									 );
            return;
		}
 
		/**
		 * Handles the load complete event
		 */		
		private function cast3dLoadComplete(event: LoadEvent): void
		{
            makeClone();
			setupNavigator();
 
			trace("cast3dLoadComplete "); 
			this.manipulator = new TrackBall(this.animator,this.stage,
									 this.viewport.viewportWidth, this.viewport.viewportHeight,
									 Manipulator.Y_UP,
									 Manipulator.X_S,Manipulator.Z_S,Manipulator.NY_S
									 );
 
//			manipulator.showCamera = true;
//			manipulator.showCOR = true;
			cp.manipulator = this.manipulator;
 
            this.loaded = true;
            removeTimer();       	        	            
            if (loader.loaderror.length)
            {
	 			statusText.textColor = 0xff0000;       	        	            
				statusText.text = loader.loaderror;            
            }
            else
            {       	        	            
		   		statusText.visible = false;
            }
			this.animator.play();
		}
 
		private function cast3dLoadProgress(event: LoadEvent): void
		{
            var n:Number = event.scenesTotal != 0.0 ? event.scenesLoaded/event.scenesTotal : 0;
            var percent:int = n*100;
 			statusText.text = "Loading " + event.file + " ....... "+ percent.toString() + "%";
		}		
 
		/**
		 * Handles the load Error event
		 */		
		private function cast3dLoadError(event: LoadEvent): void
		{
			trace("cast3dLoadError ", event.message );
            removeTimer();
   			statusText.textColor = 0xff0000;       	        	            
			statusText.text = event.message;
		}				
 
 		/**
		 * This functin make a clone of Model node and sets Navigaion Contraller
		 */		
      private function makeClone():void
       {
       	    // First lets find the root node of a character
       	    // we know in advance it's Id is 'Cube'
       	    var nodename:String = "Cube";
	        var model_node:Node3d = this.animator.source.find(nodename) as Node3d;
	        if (!model_node)
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to make clone. Not found node: " + nodename;
	            return;
	        }
 
       	    // next step is to make a clone instance of a character node
             var cloned_model_node:Node3d = model_node.clone();            
 
       	    // A cloned instance of a node if not attached is handing in the air
       	    // To make it visible we need to add it to a scene, whic is KeyFrame
       	    // There is only one KeyFrame ( with infinite duration) and we know Id 
       	    // in advance by looking at the file.
       	    var kfname:String = "Scene_kf";
 	        var kf:KeyFrame3d = this.animator.source.find(kfname) as KeyFrame3d;
	        if (!kf)
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to make clone. Not found keyframe: " + kfname;
	            return;
	        }
	        // adding cloned node to Keyframe
            kf.addNode(cloned_model_node);
            // This step is also importan to understand.
            // Although we added node to a scene, the actual rendering is taking place
            // in Rendering engine, in this case papervision3D. So Cast3d is just manupulating 
            // with transforms and geometry. This step populates rendering engine with newly created 
            // node's data. Xc3 file loader does it for you, after it's done, you need to do that explicitly.
            cloned_model_node.register(this.animator, kf);
 
            // lets create navigation conrloller for new node.
	        var nc:NavigationController = new NavigationController(cloned_model_node,"navigator");
	        _navigations.push(nc); 
 
           // We know that "cube" node actually represents Skined geometry, which means
           // the motion is controlled by another skeleton node(s), in this case "lowerBack" node is
           // root skeleton node( see source file)
	        nodename = "lowerBack";
	        var skeleton_node:Node3d = this.animator.source.find(nodename) as Node3d;
 
	        // lets make a clone of that too. Otherwise both original chatecter skin and cloned one  
	        // will be controlled by same skeleton node(s). 
	        var cloned_skeleton_node:Node3d = skeleton_node.clone();
	        if (!cloned_skeleton_node)
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to set Cloning for node: " + nodename;
	            return;
	        }
	        // add to same scene 
            kf.addNode(cloned_skeleton_node);
	        // populate rendering engine 
            cloned_skeleton_node.register(this.animator, kf);
 
 			// This step required only for Skin 			
            var skin:Skin3d = cloned_model_node.part as Skin3d;
 	        if (skin)
	        {
	        	// The cloned version of skinned node holds the references to original skeleton bones
	        	// Now we need to reassign to the bones of new ( cloned) skeleton nodes
	        	// binding works in the way that it tries to find matching id of old bone in provided skeleton bone branhes
	        	// once it finds it does the replacement. If fails to find any single bone, the whole process fails.
		        if (!skin.bindBones(cloned_skeleton_node))
		        {
			   		statusText.visible = true;
		        	statusText.text = "Failed to make clone. Clould not bind skin: " + skin.id + " to node: " + cloned_skeleton_node.id;
		            return;	        
		        }
	        }
 
	        var tarck_id:String;
	        var motionAlias:String;
 
	        // now we add a 'walking' motion which is represented by MotionGroup class instance with id == "lowerBack_motion"
	        // again, we know that by looking at source file.
	        tarck_id = "lowerBack_motion";
	        motionAlias = "walk";	        
	        if (!nc.addMotion(cloned_skeleton_node, tarck_id, motionAlias))
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id;
	            return;
	        }
 
	        // another motion is a 'jump' motion  with id == "lowerBack_motionjump"
	        motionAlias = "jump";
	        tarck_id = "lowerBack_motionjump";
	        if (!nc.addMotion(cloned_skeleton_node, tarck_id, motionAlias))
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id;
	            return;
	        }
 
	        // lets move the newly created model away from intial position so it does not interlap with original
	        nc.position.x += 5.0; 
	        nc.rotation.x = 0; nc.rotation.y = 0; nc.rotation.z = 1; nc.rotation.w = 60 * Math.PI/180.0 ; 
		    this.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownHandler);
      }
 
       private function setupNavigator():void
       {
        	// First lets find the root node of a character
       	    // we know by looking at source file,  its Id is 'Cube'
      	    var nodename:String = "Cube";
	        var node:Node3d = this.animator.source.find(nodename) as Node3d;
	        if (!node)
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to set Navigation control for node: " + nodename;
	            return;
	        }
 
            // create navigation conrloller fo this node.
	        var nc:NavigationController = new NavigationController(node,"navigator");
	        _navigations.push(nc); 
 
           // We know that "Cube" node actually represents Skinned geometry, which means
           // the motion is controlled by another skeleton node(s), in this case "lowerBack" node is
           // root skeleton node( see source file)
	        nodename = "lowerBack";
	        node = this.animator.source.find(nodename) as Node3d;
	        if (!node)
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to set Navigation control for node: " + nodename;
	            return;
	        }
 
	        var tarck_id:String;
	        var motionAlias:String;
 
	        // now we add a 'walking' motion which is represented by MotionGroup class instance with id == "lowerBack_motion"
	        // again, we know that by looking at source file.
	        // Notice that 'motion' is produced by different node that we created nvigation controlled, which in that case "cube"
	        tarck_id = "lowerBack_motion";
	        motionAlias = "walk";	        
	        if (!nc.addMotion(node, tarck_id, motionAlias))
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id;
	            return;
	        }
 
	        // another motion
	        motionAlias = "jump";
	        tarck_id = "lowerBack_motionjump";
	        if (!nc.addMotion(node, tarck_id, motionAlias))
	        {
		   		statusText.visible = true;
	        	statusText.text = "Failed to add Motion " + motionAlias + " for track: " + tarck_id;
	            return;
	        }
	        nc.position.x -= 5.0; 
	        nc.rotation.x = 0; nc.rotation.y = 0; nc.rotation.z = 1; nc.rotation.w = 30 * Math.PI/180.0 ; 
 
		    this.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.keyDownHandler);
       }
 
		public function keyDownHandler( event :KeyboardEvent ):void
		{
	       var nc:NavigationController = _navigations[_current_nav];
	       if (!nc) return;
 
           trace(event.target + "(" + event.currentTarget + "): " + event.keyCode + "/" + event.charCode);
			switch( event.keyCode )
			{
				case 9: // TAB	
					_current_nav++;		    
	        		if (_current_nav >= _navigations.length) _current_nav = 0;
					break;	
 
				case 37: // left	
 
				   // start walking motion ant rotate  model over 1/4 of motion cycle ( which is one step)
				   // in local coordinates by 30 degree rotation about Z
	        		nc.run("walk",0.25, null, { x:0, y:0, z:1, w: 30.0*Math.PI/180.0 });
					break;	
 
				case 38: // up
 
				   // start walking motion by moving model over 1/4 of motion cycle ( which is one step)
				   // and propogation node forward in local coordinates by Y = -0.33
	        		nc.run("walk",.25,{x:0, y:-.33, z:0});
					break;	
 
				case 39: // right
 
				   // start walking motion ant rotate  model over 1/4 of motion cycle ( which is one step)
				   // in local coordinates by 30 degree rotation about -Z
	        		nc.run("walk",.25, null, {x:0, y:0, z:-1, w:30.0*Math.PI/180.0 });
					break;	
 
				case 40: // down
 
				   // start walking motion by moving model over 1/4 of motion cycle ( which is one step)
				   // and propogation node backwards in local coordinates by Y = 0.33
				   // also we reverse timing (last argument) for that motion so characted walks backwards.
	        		nc.run("walk",.25,{x:0, y:.33, z:0}, null, true);
					break;	
 
				case 32: // space
 
	        		nc.run("jump",1.0);
					break;	
			}
		}
 
	}
}