package classes.away3d.modifiers
{
	import classes.away3d.arcane;
	import classes.away3d.core.base.*;
	import classes.away3d.materials.utils.*;
	import classes.away3d.modifiers.data.*;
	
   	import flash.display.*;
	import flash.utils.*;
	
	use namespace arcane;
	  
	public class HeightMapModifier
	{
		private var _mesh:Mesh;
		private var _heightMap:BitmapData;
		private var _channel:uint;
		private var _maxLevel:uint;
		private var _vertexDictionary:Dictionary;
		private var _vertexData:VertexData;
		private var _vertexOffsetDirty:Boolean;
		private var _width:int;
		private var _height:int;
		
		private function updateVertexOffsets():void
		{
			if (_heightMap == null)
				return;
			
			for each (_vertexData in _vertexDictionary) {
				
				var uv:UV;
				var uvLength:int = _vertexData.uvs.length;
				var color:uint = 0x0;
				for each (uv in _vertexData.uvs)
					color += ( _channel == HeightMapDataChannel.ALPHA) ? _heightMap.getPixel32(uv.u*_width, (1-uv.v)*_height)/uvLength : _heightMap.getPixel(uv.u*_width, (1-uv.v)*_height)/uvLength;
				
				var offset:Number;
				switch(channel){
					case HeightMapDataChannel.ALPHA:
						offset = color >> 24 & 0xFF;
						break;
					case HeightMapDataChannel.RED:
						offset = color >> 16 & 0xFF;
						break;
					case HeightMapDataChannel.GREEN:
						offset = color >> 8 & 0xFF;
						break;
					case HeightMapDataChannel.BLUE:
						offset = color & 0xFF;
						break;
					case HeightMapDataChannel.AVERAGE:
						offset = ((color >> 16 & 0xFF)*0.212671) + ((color >> 8 & 0xFF)*0.715160) + ((color >> 8 & 0xFF)*0.072169);
				}
				
				if(offset < _maxLevel)
					_vertexData.offset = offset;
				else
					_vertexData.offset = _maxLevel;
			}
		}
		
		/**
		 * The <code>Mesh</code> object to be modified.
		 */
		public function get mesh():Mesh
		{
			return _mesh;
		}
		
		public function set mesh(val:Mesh):void
		{
			_vertexDictionary = new Dictionary(true);
			
			_mesh = val;
			
			if (_mesh == null)
				return;
			
			var face:Face;
			var vertex:Vertex;
			for each (face in _mesh.faces) {
				var i:int = face.vertices.length;
				while (i--) {
					vertex = face.vertices[i];
					if (!(_vertexData = _vertexDictionary[vertex])) {
						_vertexData = _vertexDictionary[vertex] = new VertexData();
						_vertexData.vertex = vertex;
						_vertexData.origin = vertex.position.clone();
						_vertexData.position = vertex.position.clone();
						_vertexData.normal = _mesh.geometry.getVertexNormal(vertex);
					}
					_vertexData.uvs.push(face.uvs[i]);
				}
			}
			
			_vertexOffsetDirty = true;
		}
				
		/**
		 * The scale multiplier applied to the height values of the height map. Defaults to 1.
		 */
		public var scale:Number;
		
		/**
		 * The offset addded to the height values of the height map. Defaults to 0.
		 */
		public var offset:Number;
		
		/**
		 * The channel used to extract the height values from the height map. Takes the static values available in the <code>HeightMapDataChannel</code> class. Defaults to RED.
		 * 
		 * @see away3d.materials.utils.HeightMapDataChannel
		 */
		public function get channel():uint
		{
			return _channel;
		}
		
		public function set channel(val:uint):void
		{
			if (!val)
				return;
			
			_channel = val;
		}
		
		/**
		* The <code>BitmapData</code> object to be used as the height map of the modifier.
		*/
		public function get heightMap():BitmapData
		{
			return _heightMap;
		}
		
		public function set heightMap(val:BitmapData):void
		{
			_heightMap = val;
			
			if (_heightMap == null)
				return;
			
			_width = _heightMap.width-1;
			_height = _heightMap.height-1;
			
			_vertexOffsetDirty = true;
		}
		
		/**
    	 * Defines a maximum level of influence. Values required are 0 to 1. If above or equal that level the influence is not applyed.
    	 */
		public function set maxLevel(val:uint):void
		{
			if (_maxLevel == val)
				return;
			
			_maxLevel = val;
			
			_vertexOffsetDirty = true;
		}
		public function get maxLevel():uint
		{
			return _maxLevel;
		}
		
		/**
		* Creates a new <code>HeightMapModifier</code> object. Modifies the position of vertices in a mesh along their normal vectors, using the height map imformation stored in a bitmap texture.
		* 
		* @param	mesh			[optional]	The <code>Mesh</code> object to be modified.
		* @param	heightMap		[optional]	The <code>BitmapData</code> object to be used as the height map of the modifier.
		*/
		public function HeightMapModifier(mesh:Mesh = null, heightMap:BitmapData = null, channel:uint = 1, maxLevel:uint = 255, scale:Number = 1, offset:Number = 0)
        {
				this.mesh = mesh as Mesh;
				this.heightMap = heightMap;
				this.channel = channel || HeightMapDataChannel.RED;
				this.maxLevel = maxLevel;
				this.scale = scale;
				this.offset = offset;
		}
		
		/**
		* Updates the position of vertices in the mesh with the color value found at the uv's coordinates multiplied by a scale factor along the normal vector.
		*
		* @see	#scale
		* @see	#channel
		*/
		public function execute():void
		{
			if (_vertexOffsetDirty)
				updateVertexOffsets();
			
			var vertexOffset:Number;
			for each (_vertexData in _vertexDictionary) {
				vertexOffset = _vertexData.offset * scale + offset;
				_vertexData.vertex.setValue(_vertexData.position.x + _vertexData.normal.x * vertexOffset, _vertexData.position.y + _vertexData.normal.y * vertexOffset, _vertexData.position.z + _vertexData.normal.z * vertexOffset);
			}
		}
		
		/**
		* Resets the verticies to their original values
		*/
		public function reset():void
		{
			for each (_vertexData in _vertexDictionary)
				_vertexData.position = _vertexData.vertex.position.clone();
			
			refreshPositions();
			
			refreshNormals();
			
			_vertexOffsetDirty = true;
		}
		
		/**
		* Apply the actual displacement and sets it as new base for further displacements.
		* @param	refreshnormal	s			[optional] Recalculates the normals of the Mesh. Default = false;
		*/
		public function refreshPositions():void
		{
			for each (_vertexData in _vertexDictionary)
				_vertexData.position = _vertexData.vertex.position.clone();
			
			_vertexOffsetDirty = true;
		}
		
		public function refreshNormals():void
		{
			for each (_vertexData in _vertexDictionary)
				_vertexData.normal = _mesh.geometry.getVertexNormal(_vertexData.vertex);
			
			_vertexOffsetDirty = true;
		}
	}
}