package classes.away3d.materials
{
    import classes.away3d.arcane;
    import classes.away3d.cameras.lenses.*;
    import classes.away3d.containers.*;
    import classes.away3d.core.base.*;
	import classes.away3d.core.render.*;
    import classes.away3d.core.utils.*;
    import classes.away3d.core.vos.*;
    
    import flash.display.*;
    import flash.geom.*;

	use namespace arcane;
	
    /** Basic bitmap texture material */
    public class TransformBitmapMaterial extends BitmapMaterial
    {
        /** @private */
        arcane var _transform:Matrix = new Matrix();
        /** @private */
		arcane override function updateMaterial(source:Object3D, view:View3D):void
        {
        	source; view;
        	
        	_graphics = null;
        	
        	if (_colorTransformDirty)
        		updateColorTransform();
        	
        	if (_bitmapDirty)
        		updateRenderBitmap();
        	
        	if (_projectionDirty || _transformDirty)
        		invalidateFaces();
        	
        	if (_transformDirty)
        		updateTransform();
        	
        	if (_materialDirty || _blendModeDirty)
        		updateFaces();
        	
        	_projectionDirty = false;
        	_blendModeDirty = false;
        }
        /** @private */
		arcane override function renderTriangle(priIndex:uint, viewSourceObject:ViewSourceObject, renderer:Renderer):void
        {
        	if (_projectionVector && !throughProjection) {
        		_faceVO = renderer.primitiveElements[priIndex] as FaceVO;
        		_source = viewSourceObject.source;
        		if (globalProjection) {
        			normalR = _source.sceneTransform.deltaTransformVector(_faceVO.face.normal);
        			if (normalR.dotProduct(_projectionVector) < 0)
        				return;
        		} else if (_faceVO.face.normal.dotProduct(_projectionVector) < 0)
        			return;
        	}
        	
			super.renderTriangle(priIndex, viewSourceObject, renderer);
        }
        /** @private */
		arcane override function renderBitmapLayer(priIndex:uint, viewSourceObject:ViewSourceObject, renderer:Renderer, containerRect:Rectangle, parentFaceMaterialVO:FaceMaterialVO):FaceMaterialVO
		{	
			//retrieve the transform
			if (_transform)
				_mapping = _transform.clone();
			else
				_mapping = new Matrix();
			
			_faceVO = renderer.primitiveElements[priIndex] as FaceVO;
			_source = viewSourceObject.source;
			
			//if not projected, draw the source bitmap once
			if (!_projectionVector)
				renderSource(_source, containerRect, _mapping);
			
			//get the correct faceMaterialVO
			_faceMaterialVO = getFaceMaterialVO(_faceVO.face.faceVO);
			
			//pass on resize value
			if (parentFaceMaterialVO.resized) {
				parentFaceMaterialVO.resized = false;
				_faceMaterialVO.resized = true;
			}
			
			//pass on invtexturemapping value
			_faceMaterialVO.invtexturemapping = parentFaceMaterialVO.invtexturemapping;
			
			//check to see if rendering can be skipped
			if (parentFaceMaterialVO.updated || _faceMaterialVO.invalidated || _faceMaterialVO.updated) {
				parentFaceMaterialVO.updated = false;
				
				//retrieve the bitmapRect
				_bitmapRect = _faceVO.face.bitmapRect;
				
				//reset booleans
				if (_faceMaterialVO.invalidated)
					_faceMaterialVO.invalidated = false;
				else
					_faceMaterialVO.updated = true;
				
				//store a clone
				_faceMaterialVO.bitmap = parentFaceMaterialVO.bitmap.clone();
				
				//update the transform based on scaling or projection vector
				if (_projectionVector) {
					
					//calulate mapping
					_invtexturemapping = _faceMaterialVO.invtexturemapping;
					_mapping.concat(projectMapping());
					_mapping.concat(_invtexturemapping);
					
					if (_globalProjection)
						normalR = _source.sceneTransform.deltaTransformVector(_faceVO.face.normal);
					
					//check to see if the bitmap (non repeating) lies inside the drawtriangle area
					if ((throughProjection || normalR.dotProduct(_projectionVector) >= 0) && (repeat || !findSeparatingAxis(getFacePoints(_invtexturemapping), getMappingPoints(_mapping)))) {
						
						//store a clone
						if (_faceMaterialVO.cleared)
							_faceMaterialVO.bitmap = parentFaceMaterialVO.bitmap.clone();
						
						_faceMaterialVO.cleared = false;
						_faceMaterialVO.updated = true;
						
						//draw into faceBitmap
						_graphics = _s.graphics;
						_graphics.clear();
						_graphics.beginBitmapFill(_bitmap, _mapping, repeat, smooth);
						_graphics.drawRect(0, 0, _bitmapRect.width, _bitmapRect.height);
			            _graphics.endFill();
						_faceMaterialVO.bitmap.draw(_s, null, _colorTransform, _blendMode, _faceMaterialVO.bitmap.rect);
					}
				} else {
					
					//check to see if the bitmap (non repeating) lies inside the containerRect area
					if (repeat || !findSeparatingAxis(getContainerPoints(containerRect), getMappingPoints(_mapping))) {
						_faceMaterialVO.cleared = false;
						_faceMaterialVO.updated = true;
						
						//draw into faceBitmap
						_faceMaterialVO.bitmap.copyPixels(_sourceVO.bitmap, _bitmapRect, _zeroPoint, null, null, true);
					}
				}
			}
			
			return _faceMaterialVO;
		}
		
        private var _uvt:Vector.<Number> = new Vector.<Number>(9, true);
        private var _scaleX:Number = 1;
        private var _scaleY:Number = 1;
        private var _offsetX:Number = 0;
        private var _offsetY:Number = 0;
        private var _rotation:Number = 0;
        private var _projectionVector:Vector3D;
        private var _projectionDirty:Boolean;
        private var _N:Vector3D = new Vector3D();
        private var _M:Vector3D = new Vector3D();
        private var DOWN:Vector3D = new Vector3D(0, -1, 0);
        private var RIGHT:Vector3D = new Vector3D(1, 0, 0);
        private var _transformDirty:Boolean;
        private var _throughProjection:Boolean;
        private var _globalProjection:Boolean;
        private var x:Number;
		private var y:Number;
		private var px:Number;
		private var py:Number;
        private var normalR:Vector3D = new Vector3D();
        private var _u0:Number;
        private var _u1:Number;
        private var _u2:Number;
        private var _v0:Number;
        private var _v1:Number;
        private var _v2:Number;
        private var _cos:Number;
        private var _sin:Number;
        private var v0x:Number;
        private var v0y:Number;
        private var v0z:Number;
        private var v1x:Number;
        private var v1y:Number;
        private var v1z:Number;
        private var v2x:Number;
        private var v2y:Number;
        private var v2z:Number;
        private var v0:Vector3D = new Vector3D();
        private var v1:Vector3D = new Vector3D();
        private var v2:Vector3D = new Vector3D();
        private var t:Matrix;
		private var _invtexturemapping:Matrix;
		private var fPoint1:Point = new Point();
        private var fPoint2:Point = new Point();
        private var fPoint3:Point = new Point();
        private var mapa:Number;
        private var mapb:Number;
        private var mapc:Number;
        private var mapd:Number;
        private var maptx:Number;
        private var mapty:Number;
        private var mPoint1:Point = new Point();
        private var mPoint2:Point = new Point();
        private var mPoint3:Point = new Point();
        private var mPoint4:Point = new Point();
        private var dot:Number;
		private var line:Point = new Point();
        private var zero:Number;
        private var sign:Number;
		private var point1:Point;
		private var point2:Point;
		private var point3:Point;
		private var flag:Boolean;
		
        private function updateTransform():void
        {
	        _transformDirty = false;
	        
        	//check to see if no transformation exists
        	if (_scaleX == 1 && _scaleY == 1 && _offsetX == 0 && _offsetY == 0 && _rotation == 0) {
        		_transform = null;
        	} else {
	        	_transform = new Matrix();
	        	_transform.scale(_scaleX, _scaleY);
	        	_transform.rotate(_rotation);
	        	_transform.translate(_offsetX, _offsetY);
	        }
	        
	        _materialDirty = true;
        }
        
		private function projectUV():Vector.<Number>
        {
        	if (globalProjection) {
	    		v0 = _source.sceneTransform.transformVector(_faceVO.vertices[0].position);
	    		v1 = _source.sceneTransform.transformVector(_faceVO.vertices[1].position);
	    		v2 = _source.sceneTransform.transformVector(_faceVO.vertices[2].position);
        	} else {
	    		v0 = _faceVO.vertices[0].position;
	    		v1 = _faceVO.vertices[1].position;
	    		v2 = _faceVO.vertices[2].position;
        	}
        	
        	v0x = v0.x;
        	v0y = v0.y;
        	v0z = v0.z;
        	v1x = v1.x;
        	v1y = v1.y;
        	v1z = v1.z;
        	v2x = v2.x;
        	v2y = v2.y;
        	v2z = v2.z;
    		
    		_uvt[0] = v0x*_N.x + v0y*_N.y + v0z*_N.z;
    		_uvt[1] = v0x*_M.x + v0y*_M.y + v0z*_M.z;
    		_uvt[3] = v1x*_N.x + v1y*_N.y + v1z*_N.z;
    		_uvt[4] = v1x*_M.x + v1y*_M.y + v1z*_M.z;
    		_uvt[6] = v2x*_N.x + v2y*_N.y + v2z*_N.z;
    		_uvt[7] = v2x*_M.x + v2y*_M.y + v2z*_M.z;
    		
            return _uvt;
        }
        
		private function projectMapping():Matrix
        {
        	if (globalProjection) {
	    		v0 = _source.sceneTransform.transformVector(_faceVO.vertices[0].position);
	    		v1 = _source.sceneTransform.transformVector(_faceVO.vertices[1].position);
	    		v2 = _source.sceneTransform.transformVector(_faceVO.vertices[2].position);
        	} else {
	    		v0 = _faceVO.vertices[0].position;
	    		v1 = _faceVO.vertices[1].position;
	    		v2 = _faceVO.vertices[2].position;
        	}
        	
        	v0x = v0.x;
        	v0y = v0.y;
        	v0z = v0.z;
        	v1x = v1.x;
        	v1y = v1.y;
        	v1z = v1.z;
        	v2x = v2.x;
        	v2y = v2.y;
        	v2z = v2.z;
    		
    		_u0 = v0x*_N.x + v0y*_N.y + v0z*_N.z;
    		_u1 = v1x*_N.x + v1y*_N.y + v1z*_N.z;
    		_u2 = v2x*_N.x + v2y*_N.y + v2z*_N.z;
    		_v0 = v0x*_M.x + v0y*_M.y + v0z*_M.z;
    		_v1 = v1x*_M.x + v1y*_M.y + v1z*_M.z;
    		_v2 = v2x*_M.x + v2y*_M.y + v2z*_M.z;
      
            // Fix perpendicular projections
            if ((_u0 == _u1 && _v0 == _v1) || (_u0 == _u2 && _v0 == _v2))
            {
            	if (_u0 > 0.05)
                	_u0 -= 0.05;
                else
                	_u0 += 0.05;
                	
                if (_v0 > 0.07)           
                	_v0 -= 0.07;
                else
                	_v0 += 0.07;
            }
    
            if (_u2 == _u1 && _v2 == _v1)
            {
            	if (_u2 > 0.04)
                	_u2 -= 0.04;
                else
                	_u2 += 0.04;
                	
                if (_v2 > 0.06)           
                	_v2 -= 0.06;
                else
                	_v2 += 0.06;
            }
            
            t = new Matrix(_u1 - _u0, _v1 - _v0, _u2 - _u0, _v2 - _v0, _u0, _v0);
            t.invert();
            return t;
        }
        
		private function getContainerPoints(rect:Rectangle):Array
		{
			return [rect.topLeft, new Point(rect.top, rect.right), rect.bottomRight, new Point(rect.bottom, rect.left)];
		}
		
		private function getFacePoints(map:Matrix):Array
		{
			fPoint1.x = _u0 = map.tx;
			fPoint2.x = map.a + _u0;
			fPoint3.x = map.c + _u0;
			fPoint1.y = _v0 = map.ty;
			fPoint2.y = map.b + _v0;
			fPoint3.y = map.d + _v0;
			return [fPoint1, fPoint2, fPoint3];
		}
		
        private function getMappingPoints(map:Matrix):Array
        {
        	mapa = map.a*width;
        	mapb = map.b*width;
        	mapc = map.c*height;
        	mapd = map.d*height;
        	maptx = map.tx;
        	mapty = map.ty;
        	mPoint1.x = maptx;
        	mPoint1.y = mapty;
        	mPoint2.x = maptx + mapc;
        	mPoint2.y = mapty + mapd;
        	mPoint3.x = maptx + mapa + mapc;
        	mPoint3.y = mapty + mapb + mapd;
        	mPoint4.x = maptx + mapa;
        	mPoint4.y = mapty + mapb;
        	return [mPoint1, mPoint2, mPoint3, mPoint4]; 
        }
        
		private function findSeparatingAxis(points1:Array, points2:Array):Boolean
		{
			if (checkEdge(points1, points2))
				return true;
			if (checkEdge(points2, points1))
				return true;
			return false;
		}
		
		private function checkEdge(points1:Array, points2:Array):Boolean
		{
            var _length:int = points1.length;
            var i:String;
            for (i in points1) {
            	//get point 1
            	point2 = points1[i];
            	
            	//get point 2
            	if (int(i) == 0) {
            		point1 = points1[_length-1];
            		point3 = points1[_length-2];
            	} else {
            		point1 = points1[int(i)-1];
            		if (int(i) == 1)
            			point3 = points1[_length-1];
            		else
            			point3 = points1[int(i)-2];
            	}
            	
            	//calulate perpendicular line
            	line.x = point2.y - point1.y;
            	line.y = point1.x - point2.x;
            	zero = point1.x*line.x + point1.y*line.y;
            	sign = zero - point3.x*line.x - point3.y*line.y;
            	
            	//calculate each projected value for points2
				flag = true;
				var point:Point;
            	for each (point in points2) {
            		dot = point.x*line.x + point.y*line.y;
            		//return if zero is greater than dot
            		if (zero*sign > dot*sign) {
            			flag = false;
            			break;
            		}
            	}
            	if (flag)
            		return true;
            }
			return false;
		}
		
		protected override function getUVData(priIndex:uint, viewSourceObject:ViewSourceObject, renderer:Renderer):Vector.<Number>
		{
			priIndex; viewSourceObject; renderer;
			
			if (_view.camera.lens is ZoomFocusLens)
        		_focus = _view.camera.focus;
        	else
        		_focus = 0;
			
			if (_generated) {
				_uvt[uint(2)] = _screenUVTs[uint(_screenIndices[_startIndex]*3 + 2)];
				_uvt[uint(5)] = _screenUVTs[uint(_screenIndices[uint(_startIndex + 1)]*3 + 2)];
				_uvt[uint(8)] = _screenUVTs[uint(_screenIndices[uint(_startIndex + 2)]*3 + 2)];
				
				if (projectionVector) {
		    		_uvt = projectUV();
		        	_u0 = (_uvt[uint(0)] - _offsetX)/width;
		        	_u1 = (_uvt[uint(3)] - _offsetX)/width;
		        	_u2 = (_uvt[uint(6)] - _offsetX)/width;
		        	_v0 = (_uvt[uint(1)] - _offsetY)/height;
		        	_v1 = (_uvt[uint(4)] - _offsetY)/height;
		        	_v2 = (_uvt[uint(7)] - _offsetY)/height;
		   		} else {
		   			_u0 = _uvs[0].u - _offsetX/width;
		        	_u1 = _uvs[1].u - _offsetX/width;
		        	_u2 = _uvs[2].u - _offsetX/width;
		        	_v0 = 1 - _uvs[0].v - _offsetY/height;
		        	_v1 = 1 - _uvs[1].v - _offsetY/height;
		        	_v2 = 1 - _uvs[2].v - _offsetY/height;
		   		}
	        	
	        	if (_rotation) {
	        		_uvt[uint(0)] = (_u0*_cos - _v0*_sin)/_scaleX;
	        		_uvt[uint(1)] = (_u0*_sin + _v0*_cos)/_scaleY;
	        		_uvt[uint(3)] = (_u1*_cos - _v1*_sin)/_scaleX;
	        		_uvt[uint(4)] = (_u1*_sin + _v1*_cos)/_scaleY;
	        		_uvt[uint(6)] = (_u2*_cos - _v2*_sin)/_scaleX;
	        		_uvt[uint(7)] = (_u2*_sin + _v2*_cos)/_scaleY;
	        	} else {
	        		_uvt[uint(0)] = _u0/_scaleX;
	        		_uvt[uint(1)] = _v0/_scaleY;
	        		_uvt[uint(3)] = _u1/_scaleX;
	        		_uvt[uint(4)] = _v1/_scaleY;
	        		_uvt[uint(6)] = _u2/_scaleX;
	        		_uvt[uint(7)] = _v2/_scaleY;
	        	}
	        	
	    		return _uvt;
			}
			
			_faceMaterialVO = getFaceMaterialVO(_faceVO, _source, _view);
			
			_faceMaterialVO.uvtData[uint(2)] = _screenUVTs[uint(_screenIndices[_startIndex]*3 + 2)];
			_faceMaterialVO.uvtData[uint(5)] = _screenUVTs[uint(_screenIndices[uint(_startIndex + 1)]*3 + 2)];
			_faceMaterialVO.uvtData[uint(8)] = _screenUVTs[uint(_screenIndices[uint(_startIndex + 2)]*3 + 2)];
			
			if (!_faceMaterialVO.invalidated)
				return _faceMaterialVO.uvtData;
			
			_faceMaterialVO.invalidated = false;
        	
        	if (projectionVector) {
	    		_uvt = projectUV();
	        	_u0 = (_uvt[uint(0)] - _offsetX)/width;
	        	_u1 = (_uvt[uint(3)] - _offsetX)/width;
	        	_u2 = (_uvt[uint(6)] - _offsetX)/width;
	        	_v0 = (_uvt[uint(1)] - _offsetY)/height;
	        	_v1 = (_uvt[uint(4)] - _offsetY)/height;
	        	_v2 = (_uvt[uint(7)] - _offsetY)/height;
	   		} else {
	   			_u0 = _uvs[0].u - _offsetX/width;
	        	_u1 = _uvs[1].u - _offsetX/width;
	        	_u2 = _uvs[2].u - _offsetX/width;
	        	_v0 = 1 - _uvs[0].v - _offsetY/height;
	        	_v1 = 1 - _uvs[1].v - _offsetY/height;
	        	_v2 = 1 - _uvs[2].v - _offsetY/height;
	   		}
        	
        	if (_rotation) {
        		_faceMaterialVO.uvtData[uint(0)] = (_u0*_cos - _v0*_sin)/_scaleX;
	        	_faceMaterialVO.uvtData[uint(1)] = (_u0*_sin + _v0*_cos)/_scaleY;
	        	_faceMaterialVO.uvtData[uint(3)] = (_u1*_cos - _v1*_sin)/_scaleX;
	        	_faceMaterialVO.uvtData[uint(4)] = (_u1*_sin + _v1*_cos)/_scaleY;
	        	_faceMaterialVO.uvtData[uint(6)] = (_u2*_cos - _v2*_sin)/_scaleX;
	        	_faceMaterialVO.uvtData[uint(7)] = (_u2*_sin + _v2*_cos)/_scaleY;
        	} else {
        		_faceMaterialVO.uvtData[uint(0)] = _u0/_scaleX;
        		_faceMaterialVO.uvtData[uint(1)] = _v0/_scaleY;
        		_faceMaterialVO.uvtData[uint(3)] = _u1/_scaleX;
        		_faceMaterialVO.uvtData[uint(4)] = _v1/_scaleY;
        		_faceMaterialVO.uvtData[uint(6)] = _u2/_scaleX;
        		_faceMaterialVO.uvtData[uint(7)] = _v2/_scaleY;
        	}
        	 	
			return _faceMaterialVO.uvtData;
		}
		
		/**
		 * Determines whether a projected texture is visble on the faces pointing away from the projection.
		 * 
		 * @see projectionVector
		 */
        public function get throughProjection():Boolean
        {
        	return _throughProjection;
        }
        
        public function set throughProjection(val:Boolean):void
        {
        	_throughProjection = val;
        	_projectionDirty = true;
        }
        
        /**
        * Determines whether a projected texture uses offsetX, offsetY and projectionVector values relative to scene cordinates.
        * 
        * @see projectionVector
        * @see offsetX
        * @see offsetY
        */
        public function get globalProjection():Boolean
        {
        	return _globalProjection;
        }
        
        public function set globalProjection(val:Boolean):void
        {
        	_globalProjection = val;
        	_projectionDirty = true;
        }
        
        /**
        * Transforms the texture in uv-space
        */
        public function get transform():Matrix
        {
        	return _transform;
        }
        
        public function set transform(val:Matrix):void
        {
        	_transform = val;
        	
        	if (_transform) {
	        	
	        	//recalculate rotation
	        	_rotation = Math.atan2(_transform.b, _transform.a);
				_cos = Math.cos(_rotation);
				_sin = Math.sin(_rotation);
				
	        	//recalculate scale
	        	_scaleX = _transform.a/Math.cos(_rotation);
	        	_scaleY = _transform.d/Math.cos(_rotation);
	        	
	        	//recalculate offset
	        	_offsetX = _transform.tx;
	        	_offsetY = _transform.ty;
	        } else {
	        	_scaleX = _scaleY = 1;
	        	_offsetX = _offsetY = _rotation = 0;
	        }
        	
	        //_materialDirty = true;
        }
        
        /**
        * Scales the x coordinates of the texture in uv-space
        */
        public function get scaleX():Number
        {
        	return _scaleX;
        }
        
        public function set scaleX(val:Number):void
        {
        	if (isNaN(val))
                throw new Error("isNaN(scaleX)");
			
            if (val == Infinity)
                Debug.warning("scaleX == Infinity");
			
            if (val == -Infinity)
                Debug.warning("scaleX == -Infinity");
			
            if (val == 0)
                Debug.warning("scaleX == 0");
            
        	_scaleX = val;
        	
        	_transformDirty = true;
        }
        
        /**
        * Scales the y coordinates of the texture in uv-space
        */
        public function get scaleY():Number
        {
        	return _scaleY;
        }
        
        public function set scaleY(val:Number):void
        {
        	if (isNaN(val))
                throw new Error("isNaN(scaleY)");
			
            if (val == Infinity)
                Debug.warning("scaleY == Infinity");
			
            if (val == -Infinity)
                Debug.warning("scaleY == -Infinity");
			
            if (val == 0)
                Debug.warning("scaleY == 0");
            
        	_scaleY = val;
        	
        	_transformDirty = true;
        }
        
        /**
        * Offsets the x coordinates of the texture in uv-space
        */
        public function get offsetX():Number
        {
        	return _offsetX;
        }
        
        public function set offsetX(val:Number):void
        {
        	if (isNaN(val))
                throw new Error("isNaN(offsetX)");
			
            if (val == Infinity)
                Debug.warning("offsetX == Infinity");
			
            if (val == -Infinity)
                Debug.warning("offsetX == -Infinity");
            
        	_offsetX = val;
        	
        	_transformDirty = true;
        }
        
        /**
        * Offsets the y coordinates of the texture in uv-space
        */
        public function get offsetY():Number
        {
        	return _offsetY;
        }
        
        public function set offsetY(val:Number):void
        {
        	if (isNaN(val))
                throw new Error("isNaN(offsetY)");
			
            if (val == Infinity)
                Debug.warning("offsetY == Infinity");
			
            if (val == -Infinity)
                Debug.warning("offsetY == -Infinity");
            
        	_offsetY = val;
        	
        	_transformDirty = true;
        }
        
        /**
        * Rotates the texture in uv-space
        */
        public function get rotation():Number
        {
        	return _rotation;
        }
        
        public function set rotation(val:Number):void
        {
        	if (isNaN(val))
                throw new Error("isNaN(rotation)");
			
            if (val == Infinity)
                Debug.warning("rotation == Infinity");
			
            if (val == -Infinity)
                Debug.warning("rotation == -Infinity");
            
        	_rotation = val;
        	
        	_cos = Math.cos(_rotation);
			_sin = Math.sin(_rotation);
			
        	_transformDirty = true;
        }
        
        /**
        * Projects the texture in object space, ignoring the uv coordinates of the vertex objects.
        * Texture renders normally when set to <code>null</code>.
        */
        public function get projectionVector():Vector3D
        {
        	return _projectionVector;
        }
        
        public function set projectionVector(val:Vector3D):void
        {
        	_projectionVector = val;
        	if (_projectionVector) {
        		_N = DOWN.crossProduct(_projectionVector);
	            if (!_N.length) _N = RIGHT;
	            _M = _projectionVector.crossProduct(_N);
	            _N = _projectionVector.crossProduct(_M);
	            _N.normalize();
	            _M.normalize();
        	}
        	_projectionDirty = true;
        }
        
		/**
		 * @inheritDoc
		 */
        public override function getPixel32(u:Number, v:Number):uint
        {
			if (_transform) {
	        	x = u*_bitmap.width;
				y = (1 - v)*_bitmap.height;
				
				t = _transform.clone();
				t.invert();
				if (repeat) {
					px = (x*t.a + y*t.c + t.tx)%_bitmap.width;
					py = (x*t.b + y*t.d + t.ty)%_bitmap.height;
					if (px < 0)
						px += _bitmap.width;
					if (py < 0)
						py += _bitmap.height;
        			return _bitmap.getPixel32(px, py);
    			} else
        			return _bitmap.getPixel32(x*t.a + y*t.c + t.tx, x*t.b + y*t.d + t.ty);
   			}
        	return super.getPixel32(u, v);
        }
        
		/**
		 * Creates a new <code>TransformBitmapMaterial</code> object.
		 * 
		 * @param	bitmap				The bitmapData object to be used as the material's texture.
		 * @param	init	[optional]	An initialisation object for specifying default instance properties.
		 */
        public function TransformBitmapMaterial(bitmap:BitmapData, init:Object = null)
        {
            super(bitmap, init);
            
            transform = ini.getObject("transform", Matrix) as Matrix;
            scaleX = ini.getNumber("scaleX", _scaleX);
            scaleY = ini.getNumber("scaleY", _scaleY);
            offsetX = ini.getNumber("offsetX", _offsetX);
            offsetY = ini.getNumber("offsetY", _offsetY);
            rotation = ini.getNumber("rotation", _rotation);
            projectionVector = ini.getObject("projectionVector", Vector3D) as Vector3D;
            throughProjection = ini.getBoolean("throughProjection", true);
            globalProjection = ini.getBoolean("globalProjection", false);
        }
    }
}
