/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */

///////////////////////////////////////////////////////////////////////
// Class Utils
//      Math Utility functions
///////////////////////////////////////////////////////////////////////
var VecUtils = require("js/helper-classes/3D/vec-utils").VecUtils,
	ViewUtils = require("js/helper-classes/3D/view-utils").ViewUtils,
	Rectangle = require("js/helper-classes/3D/rectangle").Rectangle;

var MathUtilsClass = exports.MathUtilsClass = Object.create(Object.prototype, {
    ///////////////////////////////////////////////////////////////////////
    // Instance variables
    ///////////////////////////////////////////////////////////////////////
//    VecUtils: { value: null, writable: true },
    
    EPSILON: { value: 1.e-5, writable: true },

    // these are used in containment tests
    INSIDE: { value: -1, writable: true },
    ON: { value: 0, writable: true },
    OUTSIDE: { value: 1, writable: true },

    PI2: { value: 2*Math.PI, writable: true },
    RAD_TO_DEG: { value: 180/Math.PI, writable: true },
    DEG_TO_RAD: { value: Math.PI/180, writable: true },

    ///////////////////////////////////////////////////////////////////////
    // Property accessors
    ///////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////
    // Vector Methods
    ///////////////////////////////////////////////////////////////////////

	vecIntersectPlaneForParam: {
        value: function( pt0, vec, plane )
        {
            // declare the variable to return - undefined when there is no solution
            var param;

            var     a = plane[0],  b = plane[1], c = plane[2], d = plane[3];
            var     dx = vec[0],  dy = vec[1],  dz = vec[2];
            var     x0 = pt0[0],  y0 = pt0[1],  z0 = pt0[2];

            var numerator = -(a*x0 + b*y0 + c*z0 + d);
            var denominator = a*dx + b*dy + c*dz;

            var rtnPt;
            if (this.fpSign(denominator) != 0)
                param = numerator / denominator;

            return param;
        }
    },

	vecMag3: {
        value: function( vec )
        {
            if (vec.length < 3)  return;
            var mag = vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2];
            mag = Math.sqrt( mag );
            return mag;
        }
    },

	vecMag: {
        value: function( dimen, vec )
        {
            var sum = 0.0;
            for (var i=0;  i<dimen;  i++)
                sum += vec[i]*vec[i];
            return Math.sqrt( sum );
        }
    },

	vecSubtract: {
        value: function( a, b )
        {
            var rtnVec;
            var n = a.length;
            if (b.length < n)  n = b.length;
            if (n > 0)
            {
                rtnVec = Vector.create([0]);
                for (var i=0;  i<n;  i++)
                    rtnVec[i] = a[i] - b[i];
            }

            return rtnVec;
        }
    },

	vecAdd: {
        value: function( a, b )
        {
            var rtnVec;
            var n = a.length;
            if (b.length < n)  n = b.length;
            if (n > 0)
            {
                rtnVec = Vector.create([0]);
                for (var i=0;  i<n;  i++)
                    rtnVec[i] = a[i] + b[i];
            }

            return rtnVec;
        }
    },

	vecDist: {
        value: function( a, b )
        {
            var sum;
            var n = a.length;
            if (b.length < n)  n = b.length;
            if (n > 0)
            {
                var sum = 0.0;
                for (var i=0;  i<n;  i++)
                {
                    var d = a[i] - b[i];
                    sum += d*d;
                }

                sum = Math.sqrt( sum );
            }

            return sum;
        }
    },

    vecIntersectPlane: {
        value: function( pt0, vec, plane )
        {
            // declare the return point.  May be undefined in ill-conditioned cases.
            var rtnPt;

            var t = this.vecIntersectPlaneForParam( pt0, vec, plane );
            if (t != undefined)
            {
                var     x0 = pt0[0],  y0 = pt0[1],  z0 = pt0[2];
                var     dx = vec[0],  dy = vec[1],  dz = vec[2];
                rtnPt = Vector.create( [x0 + t*dx,
                    y0 + t*dy,
                    z0 + t*dz] );
            }

            return rtnPt;
        }
    },

	getPointOnPlane: {
        value: function( plane )
        {
            // abreviate the plane equation
            var a = plane[0],  b = plane[1],  c = plane[2],  d = plane[3];

            var x = 0.0,  y = 0.0,  z = 0.0;
            if ( Math.abs(plane[0]) > Math.abs(plane[1]) )
            {
                if ( Math.abs(plane[0]) > Math.abs(plane[2]) )
                    x = -d/a;
                else
                    z = -d/c;
            }
            else
            {
                if (Math.abs(plane[1]) > Math.abs(plane[2]) )
                    y = -d/b;
                else
                    z = -d/c;
            }

            // get the point on the plane
            return [x, y, z];
        }
    },

	transformPlane: {
        value: function( plane,  mat )
        {
            // we will project a point down one of the coordinate axes to find a point on the plane
            // that point and the normal to the plane will be transformed by the matrix, and the 'd'
            // component of the plane equation will be reset using the new normal and point.

            // find a point on the plane
            var ptOnPlane = this.getPointOnPlane(plane);

            ptOnPlane[3] = 1.0;	// 4 dimen so we can transform it

            // transform the point
            //ptOnPlane = mat.multiply( ptOnPlane );
            ptOnPlane = glmat4.multiplyVec3(mat, ptOnPlane, []);
            plane = this.transformVector( plane, mat );
            plane[3] = -this.dot3(plane, ptOnPlane );

            return plane;
        }
    },

	transformHomogeneousPoint: {
        value: function( srcPt, mat )
        {
            var pt = srcPt.slice(0);
            this.makeDimension4( pt );
            var	x = VecUtils.vecDot(4,  pt, [mat[0], mat[4], mat[ 8], mat[12]] ),
                    y = VecUtils.vecDot(4,  pt, [mat[1], mat[5], mat[ 9], mat[13]] ),
                    z = VecUtils.vecDot(4,  pt, [mat[2], mat[6], mat[10], mat[14]] ),
                    w = VecUtils.vecDot(4,  pt, [mat[3], mat[7], mat[11], mat[15]] );

            return [x, y, z, w];
        }
    },

	applyHomogeneousCoordinate: {
        value: function( hPt )
        {
            var w = hPt[3];
            hPt[0] /= w;
            hPt[1] /= w;
            hPt[2] /= w;
            hPt[3]  = 1;

            return hPt;
        }
    },

	transformAndDivideHomogeneousPoint: {
        value: function( pt, mat )
        {
            return this.applyHomogeneousCoordinate( this.transformHomogeneousPoint(pt, mat) );
        }
    },

	transformPoint: {
        value: function( srcPt, mat )
        {
            var pt = srcPt.slice(0);
            this.makeDimension3( pt );
            var	x = VecUtils.vecDot(3,  pt, [mat[0], mat[4], mat[ 8]] ) + mat[12],
                    y = VecUtils.vecDot(3,  pt, [mat[1], mat[5], mat[ 9]] ) + mat[13],
                    z = VecUtils.vecDot(3,  pt, [mat[2], mat[6], mat[10]] ) + mat[14];

            return [x, y, z];
        }
    },

	transformVector: {
        value: function( vec, mat )
        {
            this.makeDimension3( vec );
            var	x = VecUtils.vecDot(3,  vec, [mat[0], mat[4], mat[ 8]] ),
                    y = VecUtils.vecDot(3,  vec, [mat[1], mat[5], mat[ 9]] ),
                    z = VecUtils.vecDot(3,  vec, [mat[2], mat[6], mat[10]] );

            return [x, y, z];
        }
    },

	interpolateLine3D: {
        value: function( pt0, pt1, t )
        {
            var x0 = pt0[0],  y0 = pt0[1],  z0 = pt0[2],
                    x1 = pt1[0],  y1 = pt1[1],  z1 = pt1[2];
            var pt = Vector.create( [
                x0 + t*(x1 - x0),
                y0 + t*(y1 - y0),
                z0 + t*(z1 - z0)
            ] );

            return pt;
        }
    },


    dot: {
        value: function( v0, v1 )
        {
            var dimen = v0.length;
            if (v1.length < v0.length)  dimen = v1.length;

            var sum = 0.0;
            for (var i=0;  i<dimen;  i++)
                sum += v0[i] * v1[i];

            return sum;
        }
    },

    dot3: {
        value: function( v0, v1 )
        {
            var sum = 0.0;
            if ((v0.length < 3) || (v1.length < 3))  return;
            for (var i=0;  i<3;  i++)
                sum += v0[i] * v1[i];

            return sum;
        }
    },

    dot2: {
        value: function( v0, v1 )
        {
            if ((v0.length < 2) || (v1.length < 2))  return;
            var sum  = v0[0]*v1[0] + v0[1]*v1[1];
            return sum;
        }
    },

    cross: {
        value: function( v0, v1 )
        {
            var rtnVal;
            if ((v0.length == 2) && (v1.length == 2))
                rtnVal = v0[0]*v1[1] - v0[1]*v1[0];
            else if ((v0.length == 3) && (v1.length == 3))
                rtnVal = VecUtils.vecCross(3, v0, v1 );

            return rtnVal;
        }
    },

    negate: {
        value: function( v )
        {
            var dimen = v.length;
            for (var i=0;  i<dimen;  i++)
                v[i] = -v[i];

            return v;
        }
    },

    //returns the intersection point between the two segments (null if no intersection)
    segSegIntersection2D: {
        value: function (seg0Start, seg0End, seg1Start, seg1End, epsilon) {
            //check for parallel segments
            var denom = (seg1End[1] - seg1Start[1]) * (seg0End[0] - seg0Start[0]) - (seg1End[0] - seg1Start[0]) * (seg0End[1] - seg0Start[1]);
            if (Math.abs(denom) <= epsilon) {
                return null; //no intersection reported for segments that are close to parallel or parallel
            }

            //the parameter value of intersection point on seg0
            var paramSeg0 = (seg1End[0] - seg1Start[0]) * (seg0Start[1] - seg1Start[1]) - (seg1End[1] - seg1Start[1]) * (seg0Start[0] - seg1Start[0]);
            paramSeg0 /= denom;

            //the parameter value of intersection point on seg1
            var paramSeg1 = (seg0End[0] - seg0Start[0]) * (seg0Start[1] - seg1Start[1]) - (seg0End[1] - seg0Start[1]) * (seg0Start[0] - seg1Start[0]);
            paramSeg1 /= denom;

            //check whether the parameters are both between 0 and 1
            if (Math.abs(paramSeg0) > 1.0 || Math.abs(paramSeg1) > 1.0) {
                return null; //no intersection unless the the intersection point lies on both segments
            }

            var intPt = Vector.create([seg0Start[0] + paramSeg0 * (seg0End[0] - seg0Start[0]),
                seg0Start[1] + paramSeg0 * (seg0End[1] - seg0Start[1])]);

            return intPt;
        }
    }, //this.segSegIntersection = function (seg0, seg1)

    distPointToRay: {
        value: function (pt, rayOrig, rayDir)
        {
            var rayMagSq = rayDir[0]*rayDir[0] + rayDir[1]*rayDir[1] + rayDir[2]*rayDir[2]; //sq. of the ray direction magnitude (need not be 1)
            //find the projection of pt on ray
            var U = (
                    ( (pt[0] - rayOrig[0] ) * ( rayDir[0]) ) +
                            ( (pt[1] - rayOrig[1] ) * ( rayDir[1]) ) +
                            ( (pt[2] - rayOrig[2] ) * ( rayDir[2]) )
                    ) / ( rayMagSq );

            if( U < 0.0 ) {
                // closest point falls behind rayOrig
                // so return the min. of distance to rayOrig
                return this.vecDist(rayOrig, pt);
            }//if( U < 0.0) {

            var intersection = Vector.create([
                rayOrig[0] + U * (rayDir[0]),
                rayOrig[1] + U * (rayDir[1]),
                rayOrig[2] + U * (rayDir[2])]);

            return this.vecDist(intersection, pt);
        }
    },

    //returns the parameter value of projection of pt on SegP0P1 (param. at P0 is 0, param at P1 is 1)
    paramPointProjectionOnSegment: {
        value: function(pt, segP0, segP1){
            var segMag = this.vecDist(segP0, segP1);

            return (
                    ( (pt[0] - segP0[0] ) * ( segP1[0] - segP0[0]) ) +
                            ( (pt[1] - segP0[1] ) * ( segP1[1] - segP0[1]) ) +
                            ( (pt[2] - segP0[2] ) * ( segP1[2] - segP0[2]) )
            ) / ( segMag * segMag );
        }
    },

    //returns the distance of pt to segment P0P1
    // note the distance is to segment, not to line
    distPointToSegment: {
        value: function (pt, segP0, segP1)
        {
            var U = this.paramPointProjectionOnSegment(pt, segP0, segP1);
            if( U < 0.0 || U > 1.0 ) {
                // closest point does not fall within the segment
                // so return the min. of distance to segment endpoints
                var distToP0 = this.vecDist(segP0, pt);
                var distToP1 = this.vecDist(segP1, pt);

                if (distToP0 < distToP1) {
                    return distToP0;
                } else {
                    return distToP1;
                }
            }//if( U < 0.0 || U > 1.0 ) {

            var intersection = Vector.create([
                segP0[0] + U * (segP1[0] - segP0[0]),
                segP0[1] + U * (segP1[1] - segP0[1]),
                segP0[2] + U * (segP1[2] - segP0[2])]);

            return this.vecDist(intersection, pt);
        }
    },

	nearestPointOnLine2D: {
        value: function( linePt,  lineDir,  pt )
        {
            var vec = this.vecSubtract( pt, linePt );
            var dot = this.dot( lineDir, vec );

            var mag = this.vecMag(2,lineDir);
            if (this.fpSign(mag) == 0)  return;
            var d = dot/mag;
            var dVec = VecUtils.vecNormalize( 2, lineDir, d );
            return this.vecAdd( linePt, dVec );
        }
    },

	parameterizePointOnLine2D: {
        value: function( linePt, lineDir, ptOnLine )
        {
            var t;
            if (Math.abs(lineDir[0]) > Math.abs(lineDir[1]))
            {
                var x1 = linePt[0] + lineDir[0];
                if (this.fpCmp(ptOnLine[0],x1) == 0)
                    t = 1.0;
                else
                    t = (ptOnLine[0] - linePt[0]) / (linePt[0]+lineDir[0] - linePt[0]);
            }
            else
            {
                var y1 = linePt[1] + lineDir[1];
                if (this.fpCmp(ptOnLine[1],y1) == 0)
                    t = 1.0;
                else
                    t = (ptOnLine[1] - linePt[1]) / (linePt[1]+lineDir[1] - linePt[1]);
            }

            return t;
        }
    },

	pointsEqual: {
        value: function( dimen,  a, b )
        {
            if ((a.length < dimen) || (b.length < dimen))
                throw new Error( "dimension error in VecUtils.vecAdd" );

            for (var i=0;  i<dimen;  i++)
            {
                if (this.fpCmp(a[i],b[i]) != 0)  return false;
            }

            return true;
        }
    },

    makeDimension4: {
        value: function( vec )
        {
            var dimen = vec.length;
            if (dimen < 4)
            {
                for (var i=0;  i<3-dimen;  i++)
                    vec.push(0);
                vec.push(1);
            }
            else if (dimen > 4)
            {
                for (var i=0;  i<dimen-4;  i++)
                    vec.pop();
            }

            return vec;
        }
    },

    makeDimension3: {
        value: function( vec )
        {
            var dimen = vec.length;
            if (dimen < 3)
            {
                for (var i=0;  i<3-dimen;  i++)
                    vec.push(0);
            }
            else if (dimen > 3)
            {
                for (var i=0;  i<dimen-3;  i++)
                    vec.pop();
            }

            return vec;
        }
    },

    styleToNumber: {
        value: function( str )
        {
            var index = str.indexOf( "px" );
            if (index >= 0)
                str = str.substr( 0, index );

            var n = Number( str );
            return n;
        }
    },

	rectsOverlap:
	{
		value: function( pt, width, height,  elt )
		{
			// only consider rectangles with non-zero area
			if ((width == 0) || (height == 0))  return false;

			// get the mins/maxs of the onput rectangle
			var xMin, xMax, yMin, yMax;
			if (width > 0)  {  xMin = pt[0];   xMax = pt[0] + width;  }
			else  {  xMax = pt[0];  xMin = pt[0] + width;  }
			if (height > 0)  {  yMin = pt[1];   yMax = pt[1] + height;  }
			else  {  yMax = pt[1];  yMin = pt[1] + height;  }

			// get the bounds of the element in global screen space
			var bounds = ViewUtils.getElementViewBounds3D( elt );
			var bounds3D = [];
			for (var i=0;  i<4;  i++)
				bounds3D[i] = ViewUtils.localToGlobal( bounds[i],  elt );

			// get the min/maxs for the element
			var xMinElt = bounds3D[0][0],  xMaxElt = bounds3D[0][0],
				yMinElt = bounds3D[0][1],  yMaxElt = bounds3D[0][1];
			for (var i=1;  i<4;  i++)
			{
				if (bounds3D[i][0] < xMinElt)  xMinElt = bounds3D[i][0];
				else if  (bounds3D[i][0] > xMaxElt)  xMaxElt = bounds3D[i][0];
				if (bounds3D[i][1] < yMinElt)  yMinElt = bounds3D[i][1];
				else if  (bounds3D[i][1] > yMaxElt)  yMaxElt = bounds3D[i][1];
			}

			// test 1.  Overall bounding box test
			if ((xMaxElt < xMin) || (xMinElt > xMax) || (yMaxElt < yMin) || (yMinElt > yMax))
				return false;
			
			// test 2.  See if any of the corners of the element are contained in the rectangle
			var rect = Object.create(Rectangle, {});
			rect.set( pt[0], pt[1], width, height );
			for (var i=0;  i<4;  i++)
			{
				if (rect.contains( bounds3D[i][0], bounds3D[i][1] ))  return true;
			}

			// test 3.  Bounding box tests on individual edges of the element
			for (var i=0;  i<4;  i++)
			{
				var pt0 = bounds3D[i],
					pt1 = bounds3D[(i+1)%4];

				// get the extremes of the edge
				if (pt0[0] < pt1[0])  {  xMinElt = pt0[0];  xMaxElt = pt1[0];  }
				else {  xMaxElt = pt0[0];  xMinElt = pt1[0]; }
				if (pt0[1] < pt1[1])  {  yMinElt = pt0[1];  yMaxElt = pt1[1];  }
				else {  yMaxElt = pt0[1];  yMinElt = pt1[1]; }

				if ((xMaxElt < xMin) || (xMinElt > xMax) || (yMaxElt < yMin) || (yMinElt > yMax))
					continue;
				else
				{
					// intersect the element edge with the 4 sides of the rectangle
					// vertical edges
					var xRect = xMin;
					for (var j=0;  j<2;  j++)
					{
						if ((xMinElt < xRect) && (xMaxElt > xRect))
						{
							var t = (xRect - pt0[0])/(pt1[0] - pt0[0]);
							var y = pt0[1] + t*(pt1[1] - pt0[1]);
							if ((y >= yMin) && (y <= yMax))  return true;
						}
						xRect = xMax;
					}

					// horizontal edges
					var yRect = yMin;
					for (var j=0;  j<2;  j++)
					{
						if ((yMinElt < yRect) && (yMaxElt > yRect))
						{
							var t = (yRect - pt0[1])/(pt1[1] - pt0[1]);
							var x = pt0[0] + t*(pt1[0] - pt0[0]);
							if ((x >= xMin) && (x <= xMax))  return true;
						}
						yRect = yMax;
					}
				}
			}

			// if we get here there is no overlap
			return false;
		}
	},

    ///////////////////////////////////////////////////////////////////////
    // Bezier Methods
    ///////////////////////////////////////////////////////////////////////
	// this function returns the quadratic Bezier approximation to the specified
	// circular arc.  The input can be 2D or 3D, determined by the minimum dimension
	// of the center and start point.
	// includedAngle is in radians, can be positiveor negative
	circularArcToBezier: {
        value: function( ctr_, startPt_, includedAngle )
        {
            var dimen = 3;
            var ctr = ctr_.slice();  MathUtils.makeDimension3( ctr );
            var startPt = startPt_.slice();  MathUtils.makeDimension3( startPt );

            // make sure the start point is good
            var pt = VecUtils.vecSubtract(dimen, startPt, ctr);
            var rad = VecUtils.vecMag(dimen, pt);

            if ((dimen != 3) || (MathUtils.fpSign(rad) <= 0) || (MathUtils.fpSign(includedAngle) == 0))
            {
                if (dimen != 3)  console.log( "MathUtils.circularArcToBezier works for 3 dimensional points only.  Was " + dimen );
                return [ startPt.slice(0), startPt.slice(0), startPt.slice(0) ];
            }

            // determine the number of segments.  45 degree span maximum.
            var nSegs = Math.ceil( Math.abs(includedAngle)/(0.25*Math.PI) );
            if (nSegs <= 0)  return [ startPt.slice(0), startPt.slice(0), startPt.slice(0) ];
            var dAngle = includedAngle/nSegs;

            // determine the length of the center control point from the circle center
            var cs = Math.cos( 0.5*Math.abs(dAngle) ),  sn = Math.sin( 0.5*Math.abs(dAngle) );
            var  c = rad*sn;
            var  h = c*sn/cs;
            var  d = rad*cs + h;

            var rtnPts = [ VecUtils.vecAdd(dimen, pt, ctr) ];
            var rotMat = Matrix.RotationZ( dAngle );
            for ( var i=0;  i<nSegs;  i++)
            {
                // get the next end point
                var pt2 = MathUtils.transformPoint( pt, rotMat );

                // get the next center control point
                var midPt = vec3.add(pt, pt2, []);
                VecUtils.vecScale(dimen, midPt, 0.5);
                midPt = VecUtils.vecNormalize( dimen, midPt, d );

                // save the next segment
                rtnPts.push( VecUtils.vecAdd(dimen, midPt, ctr) );
                rtnPts.push( VecUtils.vecAdd(dimen,   pt2, ctr) );

                // advance for the next segment
                pt = pt2;
            }
            return rtnPts;
        }
    },

    ///////////////////////////////////////////////////////////////////////
    // Polygon Methods
    ///////////////////////////////////////////////////////////////////////
    getPolygonNormal: {
        value: function( n, xPts, yPts, zPts )
        {
            var xNrm = 0.0,  yNrm = 0.0,  zNrm = 0.0;
            for (var i=0;  i<n;  i++)
            {
                var j = (i+1) % n;

                xNrm += (yPts[i] - yPts[j]) * (zPts[i] + zPts[j]);
                yNrm += (zPts[i] - zPts[j]) * (xPts[i] + xPts[j]);
                zNrm += (xPts[i] - xPts[j]) * (yPts[i] + yPts[j]);
            }
            var normal = Vector.create( [xNrm, yNrm, zNrm] );

            // the area of the polygon is the length of the normal
            var area = VecUtils.vecMag(3, normal );
            if (this.fpSign(area) != 0)
                vec3.scale(normal, 1.0/area);

            return normal;
        }
    },

	getNormalFromBounds3D: {
        value: function( b )
        {
            var xNrm = 0.0,  yNrm = 0.0,  zNrm = 0.0;
            for (var i=0;  i<4;  i++)
            {
                var j = (i+1) % 4;

                xNrm += (b[i][1] - b[j][1]) * (b[i][2] + b[j][2]);
                yNrm += (b[i][2] - b[j][2]) * (b[i][0] + b[j][0]);
                zNrm += (b[i][0] - b[j][0]) * (b[i][1] + b[j][1]);
            }
            var normal = Vector.create( [xNrm, yNrm, zNrm] );

            // the area of the polygon is the length of the normal
            var area = VecUtils.vecMag(3, normal );
            if (this.fpSign(area) != 0)
                vec3.scale( normal, 1.0/area );

            return normal;
        }
    },

	getCenterFromBounds: {
        value: function( dimen, bounds )
        {
            var minVals = bounds[0].slice(0),
                    maxVals = bounds[0].slice(0);

            for (var iPt=1;  iPt<4;  iPt++)
            {
                for (var i=0;  i<dimen;  i++)
                {
                    if (bounds[iPt][i] < minVals[i])  minVals[i] = bounds[iPt][i];
                    if (bounds[iPt][i] > maxVals[i])  maxVals[i] = bounds[iPt][i];
                }
            }

            var ctr = VecUtils.vecAdd(dimen, minVals, maxVals);
            VecUtils.vecScale( dimen, ctr, 0.5 );

            return ctr;
        }
    },

    boundaryContainsPoint: {
        value: function( bounds,  targetPt,  backFacing )
        {
            var pt = targetPt.slice(0);
            while (pt.length > 2)  pt.pop();

            // this function returns -1 for inside, 0 for on and 1 for outside.
            // values defined as instance variables above
            var nPts = bounds.length;
            var pt1 = bounds[nPts-1].slice(0);
            while (pt1.length > 2)  pt1.pop();
            for (var i=0;  i<nPts;  i++)
            {
                // get the vector along the edge of the boundary
                var pt0 = pt1;
                pt1 = bounds[i].slice(0);
                while (pt1.length > 2)  pt1.pop();
                var vec0 = VecUtils.vecSubtract(2, pt1, pt0 );
                if (vec0.length == 3)  vec0.pop();

                // get a vector from the target point to pt0
                //var vec1 = pt.subtract( pt0 );
                var vec1 = VecUtils.vecSubtract(2, pt, pt0 );
                if (vec1.length == 3)  vec1.pop();

                // look at the cross product of the 2 vectors
                var cross = VecUtils.vecCross(2, vec0, vec1 );
                var sign = this.fpSign( cross );
                if (sign == 0)
                {
                    //var t = vec1.modulus() / vec0.modulus();
                    var t = VecUtils.vecMag(2, vec1)/VecUtils.vecMag(2, vec0);
                    if ((this.fpSign(t) >= 0) && (this.fpCmp(t,1.0) <= 0))
                        return this.ON;
                    else
                        return this.OUTSIDE;
                }

                if (backFacing)
                {
                    if (this.fpSign(cross) < 0)  return this.OUTSIDE;
                }
                else
                {
                    if (this.fpSign(cross) > 0)  return this.OUTSIDE;
                }
            }

            return this.INSIDE;
        }
    },


    ///////////////////////////////////////////////////////////////////////
    // floating point Methods
    ///////////////////////////////////////////////////////////////////////
    fpSign: {
        value: function( d )
        {
            var sign = 0;
            if (d < -this.EPSILON)      sign = -1;
            else if (d > this.EPSILON)  sign =  1;
            return sign;
        }
    },

    fpCmp: {
        value: function(x,y)
        {
            return this.fpSign( x-y );
        }
    },

    ///////////////////////////////////////////////////////////////////////
    // Utility method to convert numbers in scientific notation to decimal.
    // This is needed for matrix3d which does not support values in
    // scientific notation (that are often returned by Matrix calculations).
    // You pass in the flattened Array value of the Matrix (arr) and the
    // desired number of significant digits after the decimal (sigDig).
    ///////////////////////////////////////////////////////////////////////
    scientificToDecimal: {
        value: function(arr, sigDig)
        {
            if(!sigDig)
            {
                sigDig = 10;
            }

            var arrLen = arr.length;
            for(var k=0; k<arrLen; k++)
            {
                arr[k] = Number(arr[k].toFixed(sigDig));
            }

            return arr;
        }
    },

    ///////////////////////////////////////////////////////////////////////
    // Utility method to calculate angle between two points
    ///////////////////////////////////////////////////////////////////////
    getAngleBetweenPoints: {
        value: function(pt0, pt1, origin)
        {
//        var v0 = pt0.slice(0);
//        var v1 = pt1.slice(0);
//
//        var origin = Vector.create([0, 0]);
//
//        if(origin)
//        {
//            v0 = VecUtils.vecSubtract(2, pt0, origin);
//            v1 = VecUtils.vecSubtract(2, pt1, origin);
//        }
//
//        var cross = VecUtils.vecCross(2, v0, v1);
//        console.log("cross is " + cross);
//
//        var angle = Math.asin(cross / (VecUtils.vecMag(2, v0) * VecUtils.vecMag(2, v1)));

            var angle = (Math.atan2(pt1[1], pt1[0]) - Math.atan2(pt0[1], pt0[0]));

            if(angle < 0)
            {
                angle = angle + this.PI2;
            }

            return angle;
        }
    },

     ///////////////////////////////////////////////////////////////////////
     // Utility method to calculate angle between two 3D vectors 
     ///////////////////////////////////////////////////////////////////////
    getAxisAngleBetween3DVectors: {
        value: function (vec1, vec2, axis) {
            //compute magnitudes of the vectors
            var v1n = VecUtils.vecNormalize(3, vec1, 1.0);
            var v2n = VecUtils.vecNormalize(3, vec2, 1.0);
            //angle between the vectors (acos for now...)
            var angle = Math.acos(VecUtils.vecDot(3, v1n, v2n));
            if (Math.abs(angle) < this.EPSILON) {
                return 0;
            }
            //TODO testing...remove this block
            console.log("getAxisAngleBetween3DVectors Angle: "+angle);
            if (isNaN(angle)){
                console.log("getAxisAngleBetween3DVectors Angle is NaN");
            }
            //TODO end testing block
            //optionally, if axis is provided, create the axis of rotation as well
            var rotAxis = VecUtils.vecCross(3, v1n, v2n);
            rotAxis = VecUtils.vecNormalize(3, rotAxis, 1);
            axis[0] = rotAxis[0];
            axis[1] = rotAxis[1];
            axis[2] = rotAxis[2];
            return angle;
        }
    },

    getLocalPoint: {
        value: function (x, y, planeEq, matL)
        {
            var globalPt = [x, y, 0];
            var vec = [0,0,1];

            var localPt = this.vecIntersectPlane(globalPt, vec, planeEq);

            if(!localPt)
            {
                return null;
                return Vector.create( [1, 1]);
                return [1, 1];
            }
            localPt = this.transformPoint(localPt, matL);

            return localPt;
        }
    },

	colorToHex: {
        value: function( colorArray )
        {
            if (colorArray.length < 3)  return "#000000";

            var str = "#";
            for (var i=0;  i<3;  i++)
            {
                var c = colorArray[i];
                if (c < 0)  c = 0;
                else if (c > 1)  c = 1.0;
                c *= 255;
                var h = c.toString(16);
                if (h.length < 2)  h = "0" + h;
                if (h.length < 2)  h = "0" + h;
                str = str + h;
            }

            return str;
        }
    },

    ///////////////////////////////////////////////////////////////////////
    // Utility method to calculate angle between two vectors from origin
    ///////////////////////////////////////////////////////////////////////
    getAngleBetweenVectors: {
        value: function(v0, v1)
        {
            var dot = this.dot3(v0, v1);
            var v0Mag = this.vecMag3(v0);
            var v1Mag = this.vecMag3(v1);
            var angle = Math.acos(dot / (v0Mag*v1Mag));
            return angle;
        }
    },

    // Simple decomposition that does not take scale or perspective into account
    decomposeMatrix: {
        value: function(m)
        {
            var rY = Math.atan2(-m[2], m[10]) * this.RAD_TO_DEG;
            var rX = Math.asin(m[6]) * this.RAD_TO_DEG;
            var rZ = Math.atan2(-m[4], m[5]) * this.RAD_TO_DEG;

            return {rotX: rX, rotY: rY, rotZ: rZ};
        }
    },

/**
* decompose matrix in javascript found at https://github.com/joelambert/morf/blob/master/js/src/WebkitCSSMatrix.ext.js
* used with permission from Joe Lambert: "as long as the original licence text and attribution is left in then you're
* good to use it as you see fit."
*
* WebKitCSSMatrix Extensions
*
* Copyright 2011, Joe Lambert (http://www.joelambert.co.uk)
* Free to use under the MIT license.
* http://joelambert.mit-license.org/
*/

/**
* Decomposes the matrix into its component parts.
* A Javascript implementation of the pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition
* @author Joe Lambert
* @returns {Object} An object with each of the components of the matrix (perspective, translate, skew, scale, rotate) or identity matrix on failure
*/

//      Input: matrix       ; a 4x4 matrix
//      Output: translation ; a 3 component vector
//              rotation    ; Euler angles, represented as a 3 component vector
//              scale       ; a 3 component vector
//              skew        ; skew factors XY,XZ,YZ represented as a 3 component vector
//              perspective ; a 4 component vector
//      Returns false if the matrix cannot be decomposed. An object with the above output values if it can.
    decomposeMatrix2: {
        value: function(m)
        {
            var matrix = glmat4.transpose(m, []),
                    i = 0,
                    j = 0,
                    perspectiveMatrix,
                    inversePerspectiveMatrix,
                    transposedInversePerspectiveMatrix,
                    perspective = Vector.create([0,0,0,0]),
                    translate = Vector.create([0,0,0]),
                    scale = Vector.create([0,0,0]),
                    skew = Vector.create([0,0,0]),
                    rotate = Vector.create([0,0,0]),
                    rightHandSide = Vector.create([0,0,0,0]);
            // Normalize the matrix.
            if (matrix[15] === 0)
            {
                return false;
            }

            for (i = 0; i < 4; i++)
            {
                var index = i;
                for (j = 0; j < 4; j++)
                {
                    matrix[index] /= matrix[15];
                    index += 4;
                }
            }

            // perspectiveMatrix is used to solve for perspective, but it also provides
            // an easy way to test for singularity of the upper 3x3 component.
            perspectiveMatrix = matrix.slice(0);

            for (i = 0; i < 3; i++)
            {
                perspectiveMatrix[i+12] = 0;
            }

            perspectiveMatrix[15] = 1;

            if (glmat4.determinant(perspectiveMatrix) === 0)
            {
                return false;
            }

            // First, isolate perspective.
            if (matrix[12] !== 0 || matrix[13] !== 0 || matrix[14] !== 0)
            {
                // rightHandSide is the right hand side of the equation.
                rightHandSide[0] = matrix[12];
                rightHandSide[1] = matrix[13];
                rightHandSide[2] = matrix[14];
                rightHandSide[3] = matrix[15];

                // Solve the equation by inverting perspectiveMatrix and multiplying
                // rightHandSide by the inverse.
                //inversePerspectiveMatrix = perspectiveMatrix.inverse();
                inversePerspectiveMatrix = glmat4.inverse( perspectiveMatrix, []);
                transposedInversePerspectiveMatrix = glmat4.transpose(inversePerspectiveMatrix, []);
                perspective = MathUtils.transformPoint(rightHandSide, transposedInversePerspectiveMatrix);

                // Clear the perspective partition
                matrix[12] = matrix[13] = matrix[14] = 0;
                matrix[15] = 1;
            }
            else
            {
                // No perspective.
                perspective[0] = perspective[1] = perspective[2] = 0;
                perspective[3] = 1;
            }

            // Next take care of translation
            translate[0] = matrix[3];
            matrix[3] = 0;
            translate[1] = matrix[7];
            matrix[7] = 0;
            translate[2] = matrix[11];
            matrix[11] = 0;

            // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
            var row = Matrix.I(4);
            for (i = 0; i < 3; i++)
            {
                row[i  ] = matrix[i  ];
                row[i+4] = matrix[i+4];
                row[i+8] = matrix[i+8];
            }

            // Compute X scale factor and normalize first row.
            var rowX = Vector.create([row[0], row[0+4], row[0+8]]);
            var rowY = Vector.create([row[1], row[1+4], row[1+8]]);
            var rowZ = Vector.create([row[2], row[2+4], row[2+8]]);
            scale[0] = VecUtils.vecMag(3, rowX);
            rowX = VecUtils.vecNormalize(3, rowX);
            row[0] = rowX[0];
            row[4] = rowX[1];
            row[8] = rowX[2];

            // Compute XY shear factor and make 2nd row orthogonal to 1st.
            skew[0] = VecUtils.vecDot(3, rowX, rowY);
            rowY = this.combine(rowY, rowX, 1.0, -skew[0]);

            // Now, compute Y scale and normalize 2nd row.
            scale[1] = VecUtils.vecMag(3, rowY);
            rowY = VecUtils.vecNormalize(3, rowY);
            skew[0] /= scale[1];
            row[1] = rowY[0];
            row[5] = rowY[1];
            row[9] = rowY[2];

            // Compute XZ and YZ shears, orthogonalize 3rd row
            skew[1] = VecUtils.vecDot(3, rowX, rowZ);
            rowZ = this.combine(rowZ, rowX, 1.0, -skew[1]);
            skew[2] = VecUtils.vecDot(3, rowY, rowZ);
            rowZ = this.combine(rowZ, rowY, 1.0, -skew[2]);

            // Next, get Z scale and normalize 3rd row.
            scale[2] = VecUtils.vecMag(3, rowZ);
            rowZ = VecUtils.vecNormalize(3, rowZ);
            skew[1] /= scale[2];
            skew[2] /= scale[2];
            row[ 2] = rowZ[0];
            row[ 6] = rowZ[1];
            row[10] = rowZ[2];

            // At this point, the matrix (in rows) is orthonormal.
            // Check for a coordinate system flip.  If the determinant
            // is -1, then negate the matrix and the scaling factors.
            var pdum3 = VecUtils.vecCross(3, rowY, rowZ);
            if (VecUtils.vecDot(3, rowX, pdum3) < 0)
            {
                for (i = 0; i < 3; i++)
                {
                    scale[0] *= -1;
                    row[i  ] *= -1
                    row[i+4] *= -1
                    row[i+8] *= -1
                }
            }

            // Now, get the rotations out
            rotate[1] = Math.asin(-row[8]);
            if (Math.cos(rotate[1]) !== 0)
            {
                rotate[0] = Math.atan2(row[9], row[10]);
                rotate[2] = Math.atan2(row[4], row[ 0]);
            }
            else
            {
                rotate[0] = Math.atan2(-row[2], row[5]);
                rotate[2] = 0;
            }

            return {translation: translate,
                rotation: rotate,
                scale: scale,
                skew: skew,
                perspective: perspective};

        }
    },

/**
* Helper function required for matrix decomposition
* A Javascript implementation of pseudo code available from http://www.w3.org/TR/css3-2d-transforms/#matrix-decomposition
* @param {Vector4} aPoint A 3D point
* @param {float} ascl
* @param {float} bscl
* @author Joe Lambert
* @returns {Vector4}
*/
    combine: {
        value: function(a, b, ascl, bscl)
        {
            var result = [0,0,0];
            result[0] = (ascl * a[0]) + (bscl * b[0])
            result[1] = (ascl * a[1]) + (bscl * b[1])
            result[2] = (ascl * a[2]) + (bscl * b[2])
            return result
        }
    }

});