﻿package classes.away3d.core.base
{
	import classes.away3d.arcane;
	import classes.away3d.core.geom.*;
	import classes.away3d.core.vos.*;
	import classes.away3d.materials.*;
	
	import flash.events.*;
	import flash.geom.*;
    
    use namespace arcane;
    
	 /**
	 * Dispatched when the uv mapping of the face changes.
	 * 
	 * @eventType away3d.events.FaceEvent
	 */
	[Event(name="mappingChanged",type="away3d.events.FaceEvent")]
    
	 /**
	 * Dispatched when the material of the face changes.
	 * 
	 * @eventType away3d.events.FaceEvent
	 */
	[Event(name="materialChanged",type="away3d.events.FaceEvent")]
	
    /**
    * A triangle element used in the mesh object
    * 
    * @see away3d.core.base.Mesh
    */
    public class Face extends Element
    {
		/** @private */
        arcane var _v0:Vertex;
		/** @private */
        arcane var _v1:Vertex;
		/** @private */
        arcane var _v2:Vertex;
		/** @private */
        arcane var _uv0:UV;
		/** @private */
        arcane var _uv1:UV;
		/** @private */
        arcane var _uv2:UV;
		/** @private */
        arcane var _material:Material;
		/** @private */
        arcane var _back:Material;
        
        private var _normal:Vector3D = new Vector3D();
        private var _normalDirty:Boolean;
        private var _area:Number;
        private var _areaDirty:Boolean;
		private var _a:Number;
		private var _b:Number;
		private var _c:Number;
		private var _s:Number;
		
		// used for bsp
		arcane var _plane : Plane3D;
		//arcane var _isSplitter : Boolean;
		//prefab
		public var isBack:Boolean;
		
        private function onUVChange(event:Event):void
        {
            notifyMappingChange();
        }
		
		private function updateVertex():void
		{
			vertexDirty = false;
			_normalDirty = true;
			_areaDirty = true;
		}
		
		private function updateNormal():void
		{
	    	_normalDirty = false;
	    	
	        var d1x:Number = _v1.x - _v0.x;
	        var d1y:Number = _v1.y - _v0.y;
	        var d1z:Number = _v1.z - _v0.z;
	    
	        var d2x:Number = _v2.x - _v0.x;
	        var d2y:Number = _v2.y - _v0.y;
	        var d2z:Number = _v2.z - _v0.z;
	    
	        var pa:Number = d1y*d2z - d1z*d2y;
	        var pb:Number = d1z*d2x - d1x*d2z;
	        var pc:Number = d1x*d2y - d1y*d2x;
	
	        var pdd:Number = Math.sqrt(pa*pa + pb*pb + pc*pc);
	
	        _normal.x = pa / pdd;
	        _normal.y = pb / pdd;
	        _normal.z = pc / pdd;
  		}
  		
  		private function updateArea():void
  		{
  			// not quick enough
            _a = v0.position.subtract(v1.position).length;
            _b = v1.position.subtract(v2.position).length;
            _c = v2.position.subtract(v0.position).length;
            _s = (_a + _b + _c) / 2;
            _area = Math.sqrt(_s*(_s - _a)*(_s - _b)*(_s - _c));
  		}
  		
  		private function addVertexAt(index:uint, vertex:Vertex, command:String):void
  		{
  			if (_vertices.length <= index)
  				_vertices.length = index + 1;
  			
  			if(_vertices[index] && _vertices[index] == vertex)
  				return;
  			
  			if (_vertices[index]) {
	  			_index = _vertices[index].parents.indexOf(this);
	  				if(_index != -1)
	  					_vertices[index].parents.splice(_index, 1);
	  		}
	  		
			_commands[index] = faceVO.commands[index] = command;
  			_vertices[index] = faceVO.vertices[index] = vertex;
  			
  			if(index == 0)
  				_v0 = vertex;
  			else if(index == 1)
  				_v1 = vertex;
  			else if(index == 2)
  				_v2 = vertex;
  			//else
  			
  			/*
  			if(index == 2)
	  		{
	  			if(-0.5*(_v0.x*(_v2.y - _v1.y) + _v1.x*(_v0.y - _v2.y) + _v2.x*(_v1.y - _v0.y)) < 0)
	  				faceVO.reverseArea = true;
	  		}
	  		*/
	  		
	  		vertex._index = index;
  			
  			vertex.parents.push(this);
  			
  			if (parent)
				vertex.geometry = parent;
			
  			vertexDirty = true;
  		}
  		
		public var bitmapRect:Rectangle;
		
  		public function moveTo(x:Number, y:Number, z:Number):void
  		{
  			var newVertex:Vertex = new Vertex(x, y, z);
  			addVertexAt(_vertices.length, newVertex, PathCommand.MOVE);
  			
  			_pathCommands.push(new PathCommand(PathCommand.MOVE, _lastAddedVertex.position, null, newVertex.position));
  			_lastAddedVertex = newVertex;
  		}
  		
  		public function lineTo(x:Number, y:Number, z:Number):void
  		{
  			var newVertex:Vertex = new Vertex(x, y, z);
  			addVertexAt(_vertices.length, newVertex, PathCommand.LINE);
  			
  			_pathCommands.push(new PathCommand(PathCommand.LINE, _lastAddedVertex.position, null, newVertex.position));
  			_lastAddedVertex = newVertex;
  		}
  		
  		public function curveTo(cx:Number, cy:Number, cz:Number, ex:Number, ey:Number, ez:Number):void
  		{
  			var newControlVertex:Vertex = new Vertex(cx, cy, cz);
  			var newEndVertex:Vertex = new Vertex(ex, ey, ez);
  			addVertexAt(_vertices.length, newControlVertex, PathCommand.CURVE);
  			addVertexAt(_vertices.length, newEndVertex, "P");
  			
  			_pathCommands.push(new PathCommand(PathCommand.CURVE, _lastAddedVertex.position, newControlVertex.position, newEndVertex.position));
  			_lastAddedVertex = newEndVertex;
  		}
  		
		public var faceVO:FaceVO = new FaceVO();
		
		/**
		 * Forces the recalculation of the face normal
		 * @param bool      Boolean, forces the refresh of the normal calculation
		 */
        public  function set normalDirty(val:Boolean):void
        {
           vertexDirty = val;
        }
        
		/**
		 * Returns an array of uv objects that are used by the face.
		 */
		public function get uvs():Vector.<UV>
        {
            return _uvs;
        }
		
		/**
		 * Defines the v0 vertex of the face.
		 */
        public function get v0():Vertex
        {
            return _v0;
        }

        public function set v0(value:Vertex):void
        {
            addVertexAt(0, value, "M");
        }
		
		/**
		 * Defines the v1 vertex of the face.
		 */
        public function get v1():Vertex
        {
            return _v1;
        }

        public function set v1(value:Vertex):void
        {
        	 addVertexAt(1, value, "L");
        }
		
		/**
		 * Defines the v2 vertex of the face.
		 */
        public function get v2():Vertex
        {
            return _v2;
        }

        public function set v2(value:Vertex):void
        {
             addVertexAt(2, value, "L");
        }
		
		/**
		 * Defines the material of the face.
		 */
        public function get material():Material
        {
            return _material;
        }

        public function set material(value:Material):void
        {
            if (value == _material)
                return;
			
			if (_material != null && parent)
				parent.removeMaterial(this, _material);
            
            _material = faceVO.material = value;
            
			if (_material != null && parent)
					parent.addMaterial(this, _material);
            
            notifyMappingChange();
        }
		
		/**
		 * Defines the optional back material of the face.
		 * Displays when the face is pointing away from the camera.
		 */
        public function get back():Material
        {
            return _back;
        }
		
        public function set back(value:Material):void
        {
            if (value == _back)
                return;
			
			if (_back != null)
				parent.removeMaterial(this, _back);
            
            _back = faceVO.back = value;
            
			if (_back != null)
				parent.addMaterial(this, _back);
			
			notifyMappingChange();
        }
		
		/**
		 * Defines the uv0 coordinate of the face.
		 */
        public function get uv0():UV
        {

            return _uv0;
        }

        public function set uv0(value:UV):void
        {
            if (value == _uv0)
                return;

            if (_uv0 != null)
                if ((_uv0 != _uv1) && (_uv0 != _uv2))
                    _uv0.removeOnChange(onUVChange);

            _uv0 = _uvs[uint(0)] = faceVO.uvs[uint(0)] = value;

            if (_uv0 != null)
                if ((_uv0 != _uv1) && (_uv0 != _uv2))
                    _uv0.addOnChange(onUVChange);
			
            notifyMappingChange();
        }
		
		/**
		 * Defines the uv1 coordinate of the face.
		 */
        public function get uv1():UV
        {
            return _uv1;
        }

        public function set uv1(value:UV):void
        {
            if (value == _uv1)
                return;

            if (_uv1 != null)
                if ((_uv1 != _uv0) && (_uv1 != _uv2))
                    _uv1.removeOnChange(onUVChange);

            _uv1 = _uvs[uint(1)] = faceVO.uvs[uint(1)] = value;

            if (_uv1 != null)
                if ((_uv1 != _uv0) && (_uv1 != _uv2))
                    _uv1.addOnChange(onUVChange);
			
            notifyMappingChange();
        }
		
		/**
		 * Defines the uv2 coordinate of the face.
		 */
        public function get uv2():UV
        {
            return _uv2;
        }

        public function set uv2(value:UV):void
        {
            if (value == _uv2)
                return;

            if (_uv2 != null)
                if ((_uv2 != _uv1) && (_uv2 != _uv0))
                    _uv2.removeOnChange(onUVChange);

            _uv2 = _uvs[uint(2)] = faceVO.uvs[uint(2)] = value;

            if (_uv2 != null)
                if ((_uv2 != _uv1) && (_uv2 != _uv0))
                    _uv2.addOnChange(onUVChange);
			
            notifyMappingChange();
        }
		
		/**
		 * Returns the calculated 2 dimensional area of the face.
		 */
        public function get area():Number
        {
        	if (vertexDirty)
            	updateVertex();
            
            if (_areaDirty)
            	updateArea();
            
            return _area;
        }
		
		/**
		 * Returns the normal vector of the face.
		 */
        public function get normal():Vector3D
        {
            if (vertexDirty)
            	updateVertex();
            
            if (_normalDirty)
            	updateNormal();
            
            return _normal;
        }
		
		/**
		 * Returns the squared bounding radius of the face.
		 */
        public override function get radius2():Number
        {
            var rv0:Number = _v0._x*_v0._x + _v0._y*_v0._y + _v0._z*_v0._z;
            var rv1:Number = _v1._x*_v1._x + _v1._y*_v1._y + _v1._z*_v1._z;
            var rv2:Number = _v2._x*_v2._x + _v2._y*_v2._y + _v2._z*_v2._z;

            if (rv0 > rv1)
            {
                if (rv0 > rv2)
                    return rv0;
                else
                    return rv2;
            }
            else
            {
                if (rv1 > rv2)
                    return rv1;
                else        
                    return rv2;
            }
        }
		
    	/**
    	 * Returns the maximum x value of the face
    	 * 
    	 * @see		away3d.core.base.Vertex#x
    	 */
        public override function get maxX():Number
        {
            if (_v0._x > _v1._x)
            {
                if (_v0._x > _v2._x)
                    return _v0._x;
                else
                    return _v2._x;
            }
            else
            {
                if (_v1._x > _v2._x)
                    return _v1._x;
                else
                    return _v2._x;
            }
        }
        
    	/**
    	 * Returns the minimum x value of the face
    	 * 
    	 * @see		away3d.core.base.Vertex#x
    	 */
        public override function get minX():Number
        {
            if (_v0._x < _v1._x)
            {
                if (_v0._x < _v2._x)
                    return _v0._x;
                else
                    return _v2._x;
            }
            else
            {
                if (_v1._x < _v2._x)
                    return _v1._x;
                else
                    return _v2._x;
            }
        }
        
    	/**
    	 * Returns the maximum y value of the face
    	 * 
    	 * @see		away3d.core.base.Vertex#y
    	 */
        public override function get maxY():Number
        {
            if (_v0._y > _v1._y)
            {
                if (_v0._y > _v2._y)
                    return _v0._y;
                else
                    return _v2._y;
            }
            else
            {
                if (_v1._y > _v2._y)
                    return _v1._y;
                else
                    return _v2._y;
            }
        }
        
    	/**
    	 * Returns the minimum y value of the face
    	 * 
    	 * @see		away3d.core.base.Vertex#y
    	 */
        public override function get minY():Number
        {
            if (_v0._y < _v1._y)
            {
                if (_v0._y < _v2._y)
                    return _v0._y;
                else
                    return _v2._y;
            }
            else
            {
                if (_v1._y < _v2._y)
                    return _v1._y;
                else
                    return _v2._y;
            }
        }
        
    	/**
    	 * Returns the maximum zx value of the face
    	 * 
    	 * @see		away3d.core.base.Vertex#z
    	 */
        public override function get maxZ():Number
        {
            if (_v0._z > _v1._z)
            {
                if (_v0._z > _v2._z)
                    return _v0._z;
                else
                    return _v2._z;
            }
            else
            {
                if (_v1._z > _v2._z)
                    return _v1._z;
                else
                    return _v2._z;
            }
        }
        
    	/**
    	 * Returns the minimum z value of the face
    	 * 
    	 * @see		away3d.core.base.Vertex#z
    	 */
        public override function get minZ():Number
        {
            if (_v0._z < _v1._z)
            {
                if (_v0._z < _v2._z)
                    return _v0._z;
                else
                    return _v2._z;
            }
            else
            {
                if (_v1._z < _v2._z)
                    return _v1._z;
                else
                    return _v2._z;
            }
        }
		
		/**
		 * Creates a new <code>Face</code> object.
		 *
		 * @param	v0						The first vertex object of the triangle
		 * @param	v1						The second vertex object of the triangle
		 * @param	v2						The third vertex object of the triangle
		 * @param	material	[optional]	The material used by the triangle to render
		 * @param	uv0			[optional]	The first uv object of the triangle
		 * @param	uv1			[optional]	The second uv object of the triangle
		 * @param	uv2			[optional]	The third uv object of the triangle
		 * 
		 * @see	away3d.core.base.Vertex
		 * @see	away3d.materials.Material
		 * @see	away3d.core.base.UV
		 */
        public function Face(v0:Vertex = null, v1:Vertex = null, v2:Vertex = null, material:Material = null, uv0:UV = null, uv1:UV = null, uv2:UV = null)
        {
        	if(v0)
            	this.v0 = v0;
            if(v1)
            	this.v1 = v1;
            if(v2)
            	this.v2 = v2;
            
            this.material = material;
            this.uv0 = uv0 || new UV();
            this.uv1 = uv1 || new UV();
            this.uv2 = uv2 || new UV();
            
            faceVO.face = this;
            
            vertexDirty = true;
        }
		
		/**
		 * Inverts the geometry of the face object by swapping the <code>v1</code>, <code>v2</code> and <code>uv1</code>, <code>uv2</code> points.
		 */
        public function invert():void
        {
             _vertices[uint(2)] = _v1;
             _vertices[uint(1)] = _v2;
            faceVO.uvs[uint(2)] = _uv1;
            faceVO.uvs[uint(1)] = _uv2;
			
            _v1 = _vertices[uint(1)];
            _v2 = _vertices[uint(2)];
            _uv1 = _uvs[uint(1)] = faceVO.uvs[1];
            _uv2 = _uvs[uint(2)] = faceVO.uvs[2];
			
			vertexDirty = true;
			
            notifyMappingChange();
        }
        
        /**
         * Produces a clone of the face.
         * NOTE: Supports only irregular faces for now. 
         * @return [Face] The cloned face.
         * 
         */        
        public function clone():Face
        {
        	var clonedFace:Face = new Face();
        	for(var i:uint; i<_pathCommands.length; i++)
			{
				var command:PathCommand = _pathCommands[i];
				switch(command.type)
				{
					case PathCommand.MOVE:
						clonedFace.moveTo(command.pEnd.x, command.pEnd.y, command.pEnd.z);
						break;
					case PathCommand.LINE:
						clonedFace.lineTo(command.pEnd.x, command.pEnd.y, command.pEnd.z);
						break;
					case PathCommand.CURVE:
						clonedFace.curveTo(command.pControl.x, command.pControl.y, command.pControl.z, command.pEnd.x, command.pEnd.y, command.pEnd.z);
						break;
				}
			}
        	return clonedFace;
        }
        
        /**
         * Scales the face by a given factor about its unweighed center.
         * NOTE: Supports only irregular faces for now. 
         * @param scale [Number] The amount factor to scale the face.
         * 
         */    
        public function scaleAboutCenter(scale:Number):void
        {
        	var minX:Number = Number.MAX_VALUE;
        	var maxX:Number = -Number.MAX_VALUE;
        	var minY:Number = Number.MAX_VALUE;
        	var maxY:Number = -Number.MAX_VALUE;
        	var minZ:Number = Number.MAX_VALUE;
        	var maxZ:Number = -Number.MAX_VALUE;
        	var i:uint;
        	var command:PathCommand;
			
			for(i = 0; i<_pathCommands.length; i++)
			{
				command = _pathCommands[i];
				if(command.pControl)
				{
					if(command.pControl.x < minX)
						minX = command.pControl.x;
					if(command.pControl.x > maxX)
						maxX = command.pControl.x;
					if(command.pControl.y < minY)
						minY = command.pControl.y;
					if(command.pControl.y > maxY)
						maxY = command.pControl.y;
					if(command.pControl.z < minZ)
						minZ = command.pControl.z;
					if(command.pControl.z > maxZ)
						maxZ = command.pControl.z;
				}
				if(command.pEnd)
				{
					if(command.pEnd.x < minX)
						minX = command.pEnd.x;
					if(command.pEnd.x > maxX)
						maxX = command.pEnd.x;
					if(command.pEnd.y < minY)
						minY = command.pEnd.y;
					if(command.pEnd.y > maxY)
						maxY = command.pEnd.y;
					if(command.pEnd.z < minZ)
						minZ = command.pEnd.z;
					if(command.pEnd.z > maxZ)
						maxZ = command.pEnd.z;
				}
			}
			
			var unweighedCenter:Vector3D = new Vector3D((maxX + minX)/2, (maxY + minY)/2, (maxZ + minZ)/2);
			for(i = 0; i<_pathCommands.length; i++)
			{
				command = _pathCommands[i];
				if(command.pControl)
				{
					var pControlCenterVec:Vector3D = new Vector3D(command.pControl.x, command.pControl.y, command.pControl.z);
					pControlCenterVec = pControlCenterVec.subtract(unweighedCenter);
					pControlCenterVec.scaleBy(scale);
					command.pControl.x = unweighedCenter.x + pControlCenterVec.x;
					command.pControl.y = unweighedCenter.y + pControlCenterVec.y;
					command.pControl.z = unweighedCenter.z + pControlCenterVec.z;
				}
				if(command.pEnd)
				{
					var pEndCenterVec:Vector3D = new Vector3D(command.pEnd.x, command.pEnd.y, command.pEnd.z);
					pEndCenterVec = pEndCenterVec.subtract(unweighedCenter);
					pEndCenterVec.scaleBy(scale);
					command.pEnd.x = unweighedCenter.x + pEndCenterVec.x;
					command.pEnd.y = unweighedCenter.y + pEndCenterVec.y;
					command.pEnd.z = unweighedCenter.z + pEndCenterVec.z;
				}
			}
        }
		
		/**
		 * Tests if an edge of the face lies on a given plane
		 * @param plane The plane to test against
		 * @return Whether or not an edge of this Face lies on the plane 
		 */
		public function hasEdgeOnPlane(plane : Plane3D, epsilon : Number = 0.001) : Boolean
		{
			var v0 : Vertex = _v0;
			var v1 : Vertex = _v1;
			var v2 : Vertex = _v2;
			var numEdge : int;
			var d0 : Number, d1 : Number, d2 : Number;
			var align : int = plane._alignment;
			var a : Number = plane.a,
				b : Number = plane.b,
				c : Number = plane.c,
				d : Number = plane.d;

			if (align == Plane3D.X_AXIS) {
				d0 = a*v0._x + d;
				d1 = a*v1._x + d;
				d2 = a*v2._x + d;
			}
			else if (align == Plane3D.Y_AXIS) {
				d0 = b*v0._y + d;
				d1 = b*v1._y + d;
				d2 = b*v2._y + d;
			}
			else if (align == Plane3D.Z_AXIS) {
				d0 = c*v0._z + d;
				d1 = c*v1._z + d;
				d2 = c*v2._z + d;
			}
			else {
				d0 = a*v0._x + b*v0._y + c*v0._z + d;
				d1 = a*v1._x + b*v1._y + c*v1._z + d;
				d2 = a*v2._x + b*v2._y + c*v2._z + d;
			}
			if (d0 <= epsilon && d0 >= -epsilon) ++numEdge;
			if (d1 <= epsilon && d1 >= -epsilon) ++numEdge;
			if (d2 <= epsilon && d2 >= -epsilon) ++numEdge;

			return numEdge > 1;
		}
		
		/**
  		 * used in BSP
  		 */
  		public function get plane() : Plane3D
  		{
  			var x : Number, y : Number, z : Number;
  			
  			if (vertexDirty) updateVertex();
  			
  			if (!_plane || _normalDirty) {
  				if (!_plane) _plane = new Plane3D(); 
  				// normal must point towards positive side of triangle
  				x = normal.x;
  				y = normal.y;
  				z = normal.z;
  				_plane.a = -x;
  				_plane.b = -y;
  				_plane.c = -z;
				_plane.d = _v0._x*x + _v0._y*y + _v0._z*z;
  				
  				if (x == 1 || x == -1) _plane._alignment = Plane3D.X_AXIS;
  				else if (y == 1 || y == -1) _plane._alignment = Plane3D.Y_AXIS;
  				else if (z == 1 || z == -1) _plane._alignment = Plane3D.Z_AXIS;
  				else _plane._alignment = Plane3D.ANY;

				_plane.normalize();
  			}
  			return _plane;
  		}
		
    }
}
