/**
 * Geometry
 * A set of functions to assist in geometric calculations.
 * The y axis is flipped because these functions are intended to be used
 * for positioning DOM objects.
 *
 * @author john.bell@REMOVEME.umit.maine.edu
 * @version 1.3.20040531
 * @extends native Math object
 * @uses defined.js
 */

/**
 * Math.interpolate function
 * Given a begin int, end int, and number of steps, returns an array which interpolates
 * [steps] integers between the start and end point.
 *
 * @argumnet	begin		int, beginning of the set.
 * @argument	end			int, end of the set.
 * @argument	steps		int, number of ints to fill.
 *
 * @returns	an array with [steps] items which describes a path from begin to end, or
 * 			0 if there is an error.
 */
Math.interpolate = function(args){
	var required = ["begin", "end", "steps"];
	if(!validate("Math.interpolate", args, required)) return false;
	var pointArray = new Array();
	
	for(var i=0; i<args.steps; i++){
		pointArray.push(Math.round(args.begin + (((args.end - args.begin) / (args.steps-1)) * i)));
	}
	
	return pointArray;	
}

/**
 * Math.hypotenuse
 * Given two points, calculates the distance between them.
 *
 * @argument	begin		2 value relational array containing the x,y coordinates of
 *							the first point.
 * @argument	end			2 value relational array containing the x,y coordinates of
 *							the first point.
 *
 * returns an integer length of the distance between the two points.
 *
 * @example myLength = Math.hypotenuse({begin:{x:100,y:100}, end:{x:200,y:200}});
 */
Math.hypotenuse = function(args){
	var required = ["begin", "end"];
	if(!validate("Math.hypotenuse", args, required)) return false;
	
	var x = Math.abs(args.begin.x - args.end.x);
	var y = Math.abs(args.begin.y - args.end.y);
	return (parseInt(Math.sqrt(x*x + y*y)));
}

/**
 * Math.angle
 * Given two points, calculates the angle of the line between them.
 *
 * @argument	begin		2 value relational array containing the x,y coordinates of
 *							the first point.
 * @argument	end			2 value relational array containing the x,y coordinates of
 *							the first point.
 *
 * returns an integer angle in degrees
 *
 * @example myLength = Math.angle({begin:{x:100,y:100}, end:{x:200,y:200}});
 */ 
Math.angle = function(args){
	var required = ["begin", "end"];
	if(!validate("Math.hypotenuse", args, required)) return false;

	var x = args.end.x - args.begin.x;
	var y = args.begin.y - args.end.y;
	
	if(x==0) return y>0 ? 270 : 90;
	var angle = Math.atan(y/x) * 180 / Math.PI;
	if(x<0) angle += 180;
	
	return(angle);
} 
 
/**
 * Math.ellipticalArc function
 * Given parameters describing an ellipse, returns an array of
 * points that make up the ellipse.  Does not draw rotates ellipses.
 *
 * @argument	center		2 value relations array containing the x,y coordinates of
 *							the center of the arc
 * @argument	begin		Beginning of the arc, in degrees.  Defaults to 0
 * @argument	end			End of the arc, in degrees.  Defaults to 180
 * @argument	direction	Direction to draw the arc.  "Clockwise" or "CounterClockwise"
 *							Defaults to "Clockwise"
 * @argument	rotation	Angle of rotation of the ellipse, in degrees.  Defaults to 0.
 * @argument	xRadius		horizontal radius of the arc in pixels
 * @argument	yRadius		vertical radius of the arc in pixels
 * @argument	steps		number of points in the arc to calculate
 *
 * @returns an array with [steps] points that describes the ellipse.
 *			Each point is a relational array containing x,y, and index of the point
 *
 * @example myEllipseArray = Math.ellipse({center:{x:100,y:100}, xRadius:50, yRadius: 100, steps:50});
 */
Math.ellipticalArc = function(args){
	var required = ["center", "xRadius", "yRadius", "steps"];
	if(!validate("Math.ellipticalArc", args, required)) return false;
	
	if(!defined(args.begin)) args.begin=0;
	if(!defined(args.end)) args.end=180;
	args.begin = (args.begin * Math.PI) / 180;
	args.end = (args.end * Math.PI) / 180;
	var rotation = args.rotation * Math.PI / 180;
	var arcLength=(args.end-args.begin) / (args.steps-1);
	var angle = new Array();

	for(var i=0; i<args.steps; i++)	angle[i] = (arcLength * i) + args.begin;
	if(args.direction=="CounterClockwise") angle.reverse();

	var pointArray = new Array();
	for(var j=0; j<args.steps; j++){
		x = args.xRadius * Math.cos(angle[j]);
		y = args.yRadius * Math.sin(angle[j]) * -1;
		var rotatedX = x * Math.cos(rotation) + y * Math.sin(rotation);
		var rotatedY = y * Math.cos(rotation) - x * Math.sin(rotation);	

		var out = {x:Math.round(rotatedX + args.center.x), y:Math.round(rotatedY + args.center.y), idx:j};
		pointArray[j] = out;
	}
	return pointArray;
}

/**
 * Math.ellipse function
 * Given parameters describing an ellipse, returns an array of
 * points that make up the ellipse.  Does not draw rotates ellipses.
 *
 * @argument	center		2 value relations array containing the x,y coordinates of
 *							the center of the arc
 * @argument	begin		Beginning of the ellipse, in degrees.  Defaults to 0
 * @argument	direction	Direction to draw the ellipse.  "Clockwise" or "CounterClockwise"
 *							Defaults to "Clockwise"
 * @argument	rotation	Rotation of the ellipse around its center point, in degrees.  Defaults to 0
 * @argument	xRadius		horizontal radius of the ellipse in pixels
 * @argument	yRadius		vertical radius of the ellipse in pixels
 * @argument	steps		number of points in the arc to calculate
 *
 * @returns an array with [steps] points that describes the ellipse.
 *			Each point is a relational array containing x,y, and index of the point
 *
 * @example myEllipseArray = Math.ellipse({center:{x:100,y:100}, xRadius:50, yRadius: 100, steps:50});
 */
Math.ellipse = function(args){
	var required = ["center", "xRadius", "yRadius", "steps"];
	if(!validate("Math.ellipse", args, required)) return false;
	
	if(!defined(args.begin)) args.begin=0;
	args.begin = (args.begin * Math.PI) / 180;
	var rotation = defined(args.rotation) ? args.rotation * Math.PI / 180 : 0;
	var arcLength=(Math.PI * 2) / (args.steps);
	var angle = new Array();

	for(var i=0; i<args.steps; i++)	angle[i] = (arcLength * i) + args.begin;
	if(args.direction=="CounterClockwise") angle.reverse();

	var pointArray = new Array();
	for(var j=0; j<args.steps; j++){
		var point=new Array();
		var x = args.xRadius * Math.cos(angle[j]);
		var y = args.yRadius * Math.sin(angle[j]);
		var rotatedX = x * Math.cos(rotation) + y * Math.sin(rotation);
		var rotatedY = y * Math.cos(rotation) - x * Math.sin(rotation);	
		
		var out = {x:Math.round(rotatedX + args.center.x), y:Math.round(rotatedY + args.center.y), idx:j};
		pointArray[j] = out;
	}
	return pointArray;
}

/**
 * Math.arc function
 * Given parameters describing an arc, returns an array of
 * points that make up that arc
 *
 * @argument	center	2 value relational array contain the x,y coordinates of
 *						the center of the arc
 * @argument	radius	radius of the arc in pixels
 * @argument	begin	beginning of the arc sweep, in degrees.  Defaults to 0.
 * @argument	end		end of the arc sweep, in degrees.  Defaults to 0.
 * @argument	steps	number of points in the arc to calculate
 *
 * @returns an array with [steps] points that describes the arc.
 *          Each point is a relational array containing x, y, and index of the point
 *
 * @example myArcArray = Math.arc({center:{x:200,y:200}, radius:50, begin:90, end:270, steps:30});
 */
Math.arc = function(args){
	var required = ["center", "radius", "steps"];
	if(!validate("Math.arc", args, required)) return false;
	
	if(!defined(args.begin)) args.begin = 0;
	if(!defined(args.end)) args.end = 0;
	args.begin = (args.begin * Math.PI) / 180;
	args.end = (args.end * Math.PI) / 180;
	var arcLength = (args.end - args.begin) / (args.steps - 1);
	var pointArray = new Array();
	for(var i=0; i<args.steps; i++){
		var point = new Array();
		var angle = (arcLength * i) + args.begin;

		point[0] = args.radius * Math.cos(angle);
		point[1] = -1 * args.radius * Math.sin(angle);

		var out = {x:Math.round(point[0] + args.center.x), y:Math.round(point[1] + args.center.y), idx:i};

		pointArray[i] = out;
	}
	return pointArray;
}

/**
 * Math.circle function
 * Given the parameters of a circle, returns an
 * array of points that make up the circle
 *
 * @argument	center		2-value relational array containing the x,y coordinates
 *							of the center of the circle
 * @argument	radius		Radius of the circle
 * @argument	begin		Beginning of the circle, in degrees.  Defaults to 0.
 * @argument	direction	Direction to draw the circle:  "Clockwise" or "CounterClockwise"
 *							Defaults to counterclockwise
 * @argument	steps		Number of points in the circle to calculate
 *
 * @returns	an array with [steps] points that describes the outline of the circle
 *          Each point is a relational array containing x, y, and index of the point
 *
 * @example myCircleArray = Math.circle({center:{x:200,y:200}, radius:50, begin: 30, steps:30, direction:"Clockwise"});
 */
Math.circle = function(args){
	var required = ["center", "radius", "steps"];
	if(!validate("Math.circle", args, required)) return false;
	
	var dir = args.direction == "Clockwise" ? -360 : 360;
	var begin = defined(args.begin) ? args.begin : 0;
	var pointArray = Math.arc({center:args.center, radius:args.radius, begin:begin, end:begin + dir, steps:args.steps + 1});
	pointArray.pop();
	return pointArray;
}

/**
 * Math.line function
 * Interpolates points on a line
 *
 * @argument	begin	2-value relational array containing the x,y coordinates
 *						of the beginning point of the line
 * @argument	end		2-value array containing the x,y coordinates
 *						of the end point of the line
 * @argument	steps	number of points to plot on the line
 *
 * @returns an array with [steps] points that fall along the line
 *          Each point is a relational array containing x, y, and index of the point
 *
 * @example myLineArray = Math.line({begin:{x:200,y:100}, end:{x:200,y:300}, steps:10});
 */
Math.line = function(args){
	var required = ["begin", "end", "steps"];
	if(!validate("Math.line", args, required)) return false;
	
	var pointArray = new Array();
	for(var i=0; i<args.steps; i++){
		var point = new Array();
		point.x = Math.round(args.begin.x + (((args.end.x - args.begin.x) / (args.steps-1)) * i));
		point.y = Math.round(args.begin.y + (((args.end.y - args.begin.y) / (args.steps-1)) * i));
		point.idx = i;
		pointArray[i] = point;
	}
	return pointArray;
}

/**
 * Math.heading function
 * Draws a line from an initial point and a heading
 *
 * @argument	begin	2-value relations array containing the x,y coordinates
 *						of the beginning point of the line
 * @argument	heading	direction the line moves from its beginning point, in degrees
 * @argument	len		length of the line, in pixels
 * @argument	steps	number of points to plot on the line
 *
 * @returns an array with [steps] points that fall along the line
 *          Each point is a relational array containing x, y, and index of the point
 *
 * @example myHeadingArray = Math.heading({begin:{x:100,y:200}, heading:0, steps:10, len:200});
 */
Math.heading = function(args){
	var required = ["begin", "heading", "len", "steps"];
	if(!validate("Math.heading", args, required)) return false;
	
	var end = new Array();
	args.heading = (args.heading * Math.PI)/180;

	end.x = Math.round((args.len * Math.cos(args.heading)) + args.begin.x);
	end.y = Math.round((-1 * args.len * Math.sin(args.heading)) + args.begin.y);

	return Math.line({begin: args.begin, end:end, steps:args.steps});
}

/**
 * Math.polygon function
 * Given the parameters of a regular polygon, returns an array of points
 * that make up the boundaries of the polygon.
 *
 * @argument	center		2 value relational array containing the x,y coordinates
 *							of the center of the polygon
 * @argument	radius		radius of the circle in which the polygon is inscribed
 * @argument	sides		number of sides of the polygon
 * @argument	begin		degrees to rotate the first vertex of the polygon.  Defaults to 0.
 * @argument	direction	direction to draw the polygon: "Clockwise" or "CounterClockwise"
 *							defaults to "CounterClockwise"
 * @argument	steps		number of points to plot on each side of the polygon
 *
 * @returns an array with [steps] points that fall along the edges of the polygon
 *          Each point is a relational array containing x, y, and index of the point
 *
 * @example myPolygonArray = Math.polygon({center:{x:500,y:200}, radius:100, steps:8, direction: "Clockwise", begin:90, sides:6});
 */
Math.polygon = function(args){
	var required = ["center", "radius", "sides", "steps"];
	if(!validate("Math.polygon", args, required)) return false;
	
	var steps = args.steps;
	var circleArray  = args;
	args.steps = args.sides;
	var verticies = Math.circle(args);
	var pointArray = new Array();
	for(var s=0; s<args.sides; s++){
		var side = new Array();
		var beginpoint = verticies[s];
		var endpoint = s == args.sides - 1 ? verticies[0] : verticies[s+1];
		side = Math.line({begin:beginpoint, end:endpoint, steps:steps});
		for(var j=0; j<side.length; j++){
			side[j].idx = pointArray.length;
			pointArray.push(side[j]);
		}
		pointArray.pop();
	}
	return pointArray;
}

/**
 * Math.bezier function
 * Given an array with 3 or 4 control points, plots a bezier curve
 *
 * @argument	controls	array containing 3 or 4 control points.  Each point	is
 *							a 2 value relational array containing the x,y coordinates of the point
 * @argument	steps		number of points to plot along the curve
 *
 * @returns an array with [steps] points that fall along the path of the curve
 *          Each point is a relational array containing x, y, and index of the point
 *
 * @example myBezierArray = Math.bezier({controls: [{x:300,y:400},{x:500,y:400},{x:500,y:500}], steps:25});
 */
Math.bezier = function(args){
	var required = ["controls", "steps"];
	if(!validate("Math.bezier", args, required)) return false;
	
	var muSteps = 1 / (args.steps-1);
	var pointArray = new Array();
	if(args.controls.length==3){
		for(var i=0; i<args.steps; i++){
			var mu = muSteps * i;
			var mum1 = 1 - mu;
			var mu2 = mu * mu;
			var mum12 = mum1 * mum1;

			var point = new Array();
			point.x = Math.round((args.controls[0].x*mum12) + (2*args.controls[1].x*mum1*mu) + (args.controls[2].x*mu2));
			point.y = Math.round((args.controls[0].y*mum12) + (2*args.controls[1].y*mum1*mu) + (args.controls[2].y*mu2));
			point.idx = i;

			pointArray.push(point);
		}
	return pointArray;
	}
	if(args.controls.length==4){
		for(var j=0; j<args.steps; j++){
			var mu = muSteps * j;
			var mum1 = 1 - mu;
			var mu3 = mu * mu * mu;
			var mum13 = mum1 * mum1 * mum1;

			var point = new Array();
			point.x = Math.round((args.controls[0].x*mum13) + (3*mum1*mum1*mu*args.controls[1].x) + (3*mu*mu*mum1*args.controls[2].x) + (mu3*args.controls[3].x));
			point.y = Math.round((args.controls[0].y*mum13) + (3*mum1*mum1*mu*args.controls[1].y) + (3*mu*mu*mum1*args.controls[2].y) + (mu3*args.controls[3].y));
			point.idx = j;

			pointArray.push(point);
		}
	return pointArray;
	}
	if(confirm("Incorrect number of control points when calling Math.bezier.  Exit?")) return false;
	return true;
}

