package org.papervision3d.core.controller
{
	import org.papervision3d.core.geom.TriangleMesh3D;
	import org.papervision3d.core.geom.renderables.Vertex3D;
	import org.papervision3d.core.log.PaperLogger;
	
	/**
	 * The MorphController class controls a mesh's vertices by applying a morph.
	 * 
	 * Each possible mesh that can be blended (a morph target) must be specified.
	 * Each morph target is assigned a blend weight. The result is obtained via two methods :
	 * 
	 * <ol>
	 * <li>NORMALIZED  (Target1, Target2, ...)*(w1, w2, ...) = (1-w1-w2-...)*BaseMesh + w1*Target1 + w2*Target2 + ...</li>
	 * <li>RELATIVE    (Target1, Target2, ...) + (w1, w2, ...) = BaseMesh + w1*Target1 + w2*Target2 + ...</li>
	 * </ol>
	 * 
	 * @author Tim Knip / floorplanner.com
	 */
	public class MorphController implements IObjectController
	{
		/** */
		public var active :Boolean;
		
		/** */
		public var target :TriangleMesh3D;
		
		/** */
		public var targets :Array;
		
		/** */
		public var weights :Array;
		
		/** */
		public var normalized :Boolean;
		
		/** */
		private var cached :Array;
		
		/**
		 * Constructor.
		 */ 
		public function MorphController(target:TriangleMesh3D, normalized:Boolean=true)
		{
			this.target = target;
			this.active = true;
			this.targets = new Array();
			this.normalized = normalized;
			this.weights = new Array();
			this.cached = new Array(target.geometry.vertices.length);
			
			var v :Vertex3D;
			for(var i:int = 0; i < cached.length; i++)
			{
				v = target.geometry.vertices[i];
				cached[i] = v.clone();
			}
		}

		/**
		 * 
		 */
		public function addMorphTarget(mesh:TriangleMesh3D, weight:Number):void
		{
			if(mesh.geometry.vertices.length != this.target.geometry.vertices.length)
			{
				PaperLogger.warning("Invalid morph target! " +
					"Number of specified vertices (" + mesh.geometry.vertices.length + ")" +
					" not equal to number of base vertices (" +
					+this.target.geometry.vertices.length + ").");
				return;
			}
			
			this.targets.push(mesh);
			this.weights.push(weight);
		}
		
		/**
		 * 
		 */ 
		public function update():void
		{
			var orig :Array = this.target.geometry.vertices;
			var cached :Array = this.cached;
			var mesh :TriangleMesh3D;
			var c :Vertex3D, v :Vertex3D, t :Vertex3D;
			var num :int = orig.length;
			var totalWeight :Number = 0;
			var restWeight :Number, weight :Number;
			var i :int, j :int;
			
			if(!this.active)
			{
				return;
			}
			
			if(normalized)
			{
				for(i = 0; i < weights.length; i++)
				{
					totalWeight += weights[i];
				}
				restWeight = 1 - totalWeight;
			}
			
			for(i = 0; i < num; i++)
			{
				v = orig[i];
				c = cached[i];
				
				v.x = normalized ? restWeight * c.x : c.x;
				v.y = normalized ? restWeight * c.y : c.y;
				v.z = normalized ? restWeight * c.z : c.z;
				
				for(j = 0; j < targets.length; j++)
				{
					mesh = targets[j];
					t = mesh.geometry.vertices[i];
					weight = weights[j];
					
					v.x += weight * t.x;
					v.y += weight * t.y;
					v.z += weight * t.z;
				}
			}
		}
	}
}
