// ***************** COPYRIGHT (c) 2006 STEFAN WANER ******************
// *********************** ALL RIGHTS RESERVED ************************


// Globals
// window.onerror = myErrorTrap;
var e = 2.718281828459045;
var pi = 3.141592653589793;
var epsilon = .000000000001;
var canvas, ctx; // for excanvas
var MouseUp = false;
var okToRoll = true;
var theMode = 0;// (0 = explicit, 1 = parametric)
var theDirection = "up" // rotation direction
var justErased = false; // pressed the erase button

var canvasWidth = 400;
var canvasHeight = 400;
var theTriangle = 0;	// the current triangle

var theColor = 0; // the color of a pixel
var numColors = 7;
var lineColor = new Array();
	lineColor[0] = 	"red";
	lineColor[1] = 	"blue";
	lineColor[2] = 	"purple";
	lineColor[3] = 	"green";
	lineColor[4] = 	"magenta";
	lineColor[5] = 	"grey";
	lineColor[6] = 	"orange";
	lineColor[7] = 	"yellow";

// Surface polygon globals
var maxNumTriangles = 1024;
var numTriangles = 0;
var numIncrements = 10; 		// number of rectangles = square of this
var numX = 400; // the actual screen size of the palette
var numY = 400; 
var squareUp = false; // if true, all three scales equal
var framed = false; // whether or mot to put it in a framed box
var u = 0; var v = 0; 		// needed for eval parameters
var theTriangleArray = new makeArray2(maxNumTriangles,12);
// format of [i] th entry: 	must draw? 0/1 , color , minx, miny, maxx, maxy, x1, y1, x2, y2, x3, y3

var vertexArray = new makeArray3(numIncrements+1, numIncrements+1,3);


var theTriangleData = new makeArray2(maxNumTriangles, 6);
// format of [i] the entry: inverse matrix of adjacent direction vectors and coordinates of opposte vertex in this basis.
var theTriangleData2 = new makeArray2(maxNumTriangles, 3);
// format front/back (1 or 0), light intensity, distance to viewer
var theSequence = new makeArray(maxNumTriangles)


var xMin = -.2;				// viewport coords
var xMax = .2 ;
var yMin = -.2-.02;
var yMax = .2-.02 ;
var DXX = xMax-xMin;
var DYY = yMax-yMin;
var cs = Math.cos(pi/20);			// rotation stps
var sn = Math.sin(pi/20);
var xL = 5; var yL = 5; var zL = 10;		// light source position
var deltaIntens = 0;						// to adjust the light intensity
var xv = 5; var yv = 5; var zv = 2;			// camera lens position
var lenv = Math.pow(xv*xv+yv*yv+zv*zv,.5); 	// length of above vector

var xup = 0; var yup = 0; var zup = 1;		// up direction of camera		
var xt = 0; var  yt = 0; var  zt = 0;		// target point you are looking at
var xn = xt - xv;				// needed globals
var yn = yt - yv;
var zn = zt - zv;
var norm =  Math.pow(xn*xn + yn*yn + zn*zn, 0.5);
xn = xn/norm;
yn = yn/norm;
zn = zn/norm;

var xe = xv - xn;
var ye = yv - yn;
var ze = zv - zn;
var dot = xn*xup + yn*yup + zn*zup;
var xtu = xup - dot*xn;
var ytu = yup - dot*yn;
var ztu = zup - dot*zn;
var xtx = yn * ztu - zn*ytu;
var ytx = zn * xtu - xn*ztu;
var ztx = xn * ytu - yn*xtu;

norm = Math.pow(xtu*xtu + ytu*ytu + ztu*ztu, 0.5);
xtu = xtu/norm;
ytu = ytu/norm;
ztu = ztu/norm;

var sigDig = 6;		// default accuracy 

// graphing globals
var theFunctionString = ''; // the function it will graph

var x = 0;  // wants a global x for eval...
// *** Generate the bounding cube (again)
var theResult = new makeArray(3);
var x1, y1, z1, x2, y2, z2;
var xCube1, xCube2, xCube3, xCube4, xCube5, xCube6, xCube7, xCube8;
var yCube1, yCube2, yCube3, yCube4, yCube5, yCube6, yCube7, yCube8;
x1 = -1;
y1 = -1;
z1 = -1;
theResult = persp_proj(x1, y1, z1);
xCube1 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube1= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = -1;
y1 = 1;
z1 = -1;
theResult = persp_proj(x1, y1, z1);
xCube2 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube2= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = -1;
y1 = 1;
z1 = 1;
theResult = persp_proj(x1, y1, z1);
xCube3 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube3= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = -1;
y1 = -1;
z1 = 1;
theResult = persp_proj(x1, y1, z1);
xCube4 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube4= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = 1;
y1 = -1;
z1 = -1;
theResult = persp_proj(x1, y1, z1);
xCube5 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube5= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = 1;
y1 = 1;
z1 = -1;
theResult = persp_proj(x1, y1, z1);
xCube6 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube6= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = 1;
y1 = 1;
z1 = 1;
theResult = persp_proj(x1, y1, z1);
xCube7 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube7= Math.round( numY*(yMax- theResult[2])/DYY );

x1 = 1;
y1 = -1;
z1 = 1;
theResult = persp_proj(x1, y1, z1);
xCube8 = Math.round( numX*(theResult[1]-xMin)/DXX );
yCube8= Math.round( numY*(yMax- theResult[2])/DYY );

// *** save and recover globals
var cookiesEnabled = true;
// *** undo erase globals
var functionstrt, xMint, xMaxt, yMint, yMaxt, xfunct, yfunct, zfunct, uMint, vMint, uMaxt, vMaxt;


// * Now rotation function definitions
var rotate = (function(){
  var isRunning;
  var action;
  return {
    start : function(){
// alert(direction);
		if (theDirection == "up") action = calc2(9);
		else if (theDirection == "down") action = calc2(8);
		else if (theDirection == "left") action = calc2(5);
		else if (theDirection == "right") action = calc2(4);
      isRunning = setTimeout('rotate.start();', 200);
    },
    stop : function (){
      if (isRunning) clearTimeout(isRunning);
	getReady(); drawFilled(true); // draw the graph
    }
  }

})();

 

// ***testing *****

// generate a surface
var theFunctionX = "u";
var theFunctionY = "v";
var theFunctionZ = "Math.pow(u,2) + Math.pow(v,2)";

// **** end testing ****

function makeArray3 (X,Y,Z)
	{
	var count;
	this.length = X+1;
	for (var count = 1; count <= X+1; count++)
		// to allow starting at 1
		this[count] = new makeArray2(Y,Z);
	} // makeArray2

function makeArray2 (X,Y)
	{
	var count;
	this.length = X+1;
	for (var count = 1; count <= X+1; count++)
		// to allow starting at 1
		this[count] = new makeArray(Y);
	} // makeArray2

function makeArray (Y)
	{
	var count;
	this.length = Y+1;
	for (var count = 1; count <= Y+1; count++)
		this[count] = 0;
	} // makeArray

function readAccuracy() {
var acc = parent.right.document.inputPanel.accuracy.value;
if (acc == "") document.output.value = "You must enter a value 1-10";
else 	{
	var accn = eval(acc);
	if ( (accn < 1) || (accn >10) ) document.output.value = "You must enter a value 1-10";
	else sigDig = accn;
	}

} // read accuracy

// ******** RESET THE GLOBAL VIEWING VARIABLES
function resetView() {
		var norm =  Math.pow(xn*xn + yn*yn + zn*zn, 0.5);
		xn = xn/norm;
		yn = yn/norm;
		zn = zn/norm;

		xe = xv - xn;
		ye = yv - yn;
		ze = zv - zn;
		dot = xn*xup + yn*yup + zn*zup;
		xtu = xup - dot*xn;
		ytu = yup - dot*yn;
		ztu = zup - dot*zn;
		xtx = yn * ztu - zn*ytu;
		ytx = zn * xtu - xn*ztu;
		ztx = xn * ytu - yn*xtu;

		norm = Math.pow(xtu*xtu + ytu*ytu + ztu*ztu, 0.5);
		xtu = xtu/norm;
		ytu = ytu/norm;
		ztu = ztu/norm;

	// *** Generate the bounding cube (again)
	var theResult = new makeArray(3);
	var x1, y1, z1, x2, y2, z2;
	x1 = -1;
	y1 = -1;
	z1 = -1;
	theResult = persp_proj(x1, y1, z1);
	xCube1 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube1= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = -1;
	y1 = 1;
	z1 = -1;
	theResult = persp_proj(x1, y1, z1);
	xCube2 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube2= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = -1;
	y1 = 1;
	z1 = 1;
	theResult = persp_proj(x1, y1, z1);
	xCube3 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube3= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = -1;
	y1 = -1;
	z1 = 1;
	theResult = persp_proj(x1, y1, z1);
	xCube4 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube4= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = 1;
	y1 = -1;
	z1 = -1;
	theResult = persp_proj(x1, y1, z1);
	xCube5 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube5= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = 1;
	y1 = 1;
	z1 = -1;
	theResult = persp_proj(x1, y1, z1);
	xCube6 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube6= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = 1;
	y1 = 1;
	z1 = 1;
	theResult = persp_proj(x1, y1, z1);
	xCube7 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube7= Math.round( numY*(yMax- theResult[2])/DYY );

	x1 = 1;
	y1 = -1;
	z1 = 1;
	theResult = persp_proj(x1, y1, z1);
	xCube8 = Math.round( numX*(theResult[1]-xMin)/DXX );
	yCube8= Math.round( numY*(yMax- theResult[2])/DYY );






}





// ********* FUNCTION PARSER ***********
function myEval(theString)
{


return(eval(myParse(theString)));
	
}

// The above function requires all the following routines
function breakApart(InString) {
	// ******* Input: any string such as aaa*bbb or (aa*aa)*bbb
	// *** This Routine Retuns two pieces
	// *** bbb starts at the left-most operation *, /,  - or +
	// *** if it sees a paren, it stops after closure.

	theString = InString;
	var Length = theString.length;
	var outArray = new Array();
	outArray[1] = theString;
	outArray[2] = "";
	var parenCount = 0;
	var parenWatch = false;
	var looking = true;
	
	
	if (theString.substring (0,1) == "(")
		{
		parenCount++;
		parenWatch = true;
		looking = false;
		}
	// Look for operators
	
	for (Count=1; Count < Length; Count++)  
		{
		TempChar=theString.substring (Count, Count+1);
		if (TempChar == "(") 
			{parenCount ++;}
		else if (TempChar == ")") {parenCount = parenCount-1};

		if ((parenCount == 0) && (parenWatch == true))
			 {
			parenWatch = false;
			outArray[1] = theString.substring (0, Count+1);
			outArray[2] = theString.substring (Count+1, Length);
			}

		if (looking == true)
			{
		if ( ( (TempChar == "*") || (TempChar == "/") || (TempChar == "-")  ) || ( (TempChar == "+") || (TempChar == ')' )  )  )
				{ 
				
					{
					// alert(Count);
					looking = false;
					outArray[1] = theString.substring (0, Count);
					outArray[2] = theString.substring (Count, Length);
					// alert (outArray[1]);
					// alert(outArray[2]);
					} // end if hit one
				} 

			} // end if looking

		} // end of loop


	return (outArray);

} // end of breakApart

function isNumberChar (InString)  {
	if(InString.length!=1) 
		return (false);
	RefString="1234567890";
	if (RefString.indexOf (InString, 0)==-1) 
		return (false);
	return (true);
}

function isCharHere (InString, RefString)  {
	if(InString.length!=1) 
		return (false);
	if (RefString.indexOf (InString, 0)==-1) 
		return (false);
	return (true);
}

function putProduct(InString) {
OutString="";
for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (!isCharHere(TempChar,"tTyYzZxXeslcap") || (Count == 0) )
			{OutString=OutString+TempChar}
		else 
			{
			if (isNumberChar(InString.substring(Count-1,Count)))
				{OutString=OutString+"*"+TempChar}
			else OutString=OutString+TempChar
			}
	}
	return (OutString);
}

function reverse (InString)  {
	OutString="";
	var Length = InString.length;
	for (Count=Length; Count > -1; Count--)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar == "(") {TempChar = ")"}
		else if  (TempChar == ")") {TempChar = "("}
		OutString=OutString+TempChar;
		}
	return (OutString);
	}

function powFix2(InString) {
	// ****Replaces one "^" by "pow"

	theString = InString;
	var Length = theString.length;
	outString = theString; 
		// in case nothing happens
	
		// Look for wedge
	var looking = true;
	for (Count=0; Count < Length; Count++)  
		{
		if (looking)
			{
			TempChar=theString.substring (Count, Count+1);
			if (TempChar == "^")
				{
				looking = false;
				rightStr = theString.substring (Count+1,Length);
				leftStr = theString.substring (0,Count);
				// deal with right-hand string
				Aray = breakApart(rightStr);
				Arg2 = Aray[1];
				rightRest = Aray[2];
			
				backString = reverse(leftStr);
				Aray = breakApart(backString);
				Arg1 = reverse(Aray[1]);
				leftRest = reverse(Aray[2]);
				outString = leftRest+"Math.pow("+Arg1+","+Arg2+")"+rightRest;
				
				} // end hif hit a wedge

			} // end of looking for a wedge
		} // end of loop

// ****** testing *******
// document.Extra.diagnostics.value += outString + cr;
// ***** end testing *****

return (outString);

} // end of powFix2

function powCheck(InString) {
	// ****checks for ^
	
	theString = InString;
	var Length = theString.length;
	
	// Look for wedge
	var found = false;
	for (Count=0; Count < Length; Count++)  
		{
		TempChar=theString.substring (Count, Count+1);
		if (TempChar == "^")
			{
			found = true;
			} // end if hit a wedge
		} // end of looking for a wedge
	return(found);


} // end of powCheck

function myParse(expression)
{
		var theString = stripSpaces(expression);
		theString = replaceSubstring (theString,"cos","Math.cos");
		theString = replaceSubstring (theString,"sin","Math.sin");
		theString = replaceSubstring (theString,"ln","Math.ln");
		theString = replaceSubstring (theString,"log","Math.log");
		theString = replaceSubstring (theString,")(",")*(");
		theString = replaceSubstring (theString,")ln",")*ln");
		theString = replaceSubstring (theString,"sqrt","Math.sqrt");	
		with (Math)
			{
		// now convert formatting from GC formatting		
		theString = putProduct(theString);
		theString = replaceSubstring(theString,"log","(1/log(10))*log");
		theString = replaceSubstring(theString,"ln","log");
			while (powCheck(theString))
				{
				theString = powFix2(theString);
				// alert (theString);
				}
		theString = replaceChar(theString,"X","x");
			} // with Math\

	return(theString);
} // myParse
// ******** END FUNCTION PARSER **************


function replaceChar (InString,oldSymbol,newSymbol)  {
	var OutString="";
	var TempChar = "";
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=oldSymbol)
			OutString=OutString+TempChar
		else OutString=OutString+newSymbol;
	}
	return (OutString);
}

function replaceSubstring (InString,oldSubstring,newSubstring)  {
	OutString="";
	var sublength = oldSubstring.length;
	for (Count=0; Count < InString.length; Count++)  {
		TempStr=InString.substring (Count, Count+sublength);
		TempChar=InString.substring (Count, Count+1);
		if (TempStr!= oldSubstring)
			OutString=OutString+TempChar
		else 
			{
			OutString=OutString+ newSubstring;
			Count +=sublength-1
			}

	}
	return (OutString);
}

// ****Get the function and prepare to generate surface
function getReady() {
justErased = false;
canvas = document.getElementById("cv");
ctx = canvas.getContext("2d");
okToRoll = true;
if(document.inputPanel.S1[0].checked) squareUp = false;
else squareUp = true;
if(document.inputPanel.R1[0].checked) theMode = 0;
else theMode = 1;
if(document.inputPanel.F1[0].checked) framed = true;
else framed = false;

// alert(theMode);
if (theMode == 0) 

	{
	// explicit
	var functionExpr = stripSpaces(document.inputPanel.functionstr.value);
	var xMinstr = document.inputPanel.xMin.value;
	var xMaxstr = document.inputPanel.xMax.value;
	var yMinstr = document.inputPanel.yMin.value;
	var yMaxstr = document.inputPanel.yMax.value;
	if (functionExpr == "") {alert("You must enter a function to graph."); okToRoll = false}
	else if (!looksLikeANumber(xMinstr)) { alert("You have not entered a numerical value for xMin."); okToRoll = false}
	else if (!looksLikeANumber(xMaxstr)) { alert("You have not entered a numerical value for xMax."); okToRoll = false}
	else if (!looksLikeANumber(yMinstr)) { alert("You have not entered a numerical value for yMin."); okToRoll = false}
	else if (!looksLikeANumber(yMaxstr)) { alert("You have not entered a numerical value for yMax."); okToRoll = false}
	if (okToRoll)
		{
		var uMin = myEval(xMinstr);
		var uMax = myEval(xMaxstr);
		var vMin = myEval(yMinstr);
		var vMax = myEval(yMaxstr);
		if (uMin >= uMax) { alert("xMin should be less than xMax."); okToRoll = false}
		else if (vMin >= vMax) { alert("yMin should be less than yMax."); okToRoll = false}
		}

	if (okToRoll)
		{
		var theString = myParse(document.inputPanel.functionstr.value);
		theString = replaceChar (theString,"x","u");
		theString = replaceChar (theString,"y","v");
		theString = replaceChar (theString,"X","u");
		theString = replaceChar (theString,"Y","v");
		generateTriangles("u", "v", theString, uMin, uMax, vMin, vMax);
		// drawFilled(true);
		}
	}
else			
	{
	// parametric
	var functionExprx = stripSpaces(document.inputPanel.xfunc.value);
	var functionExpry = stripSpaces(document.inputPanel.yfunc.value);
	var functionExprz = stripSpaces(document.inputPanel.zfunc.value);
	var uMinstr = document.inputPanel.uMin.value;
	var uMaxstr = document.inputPanel.uMax.value;
	var vMinstr = document.inputPanel.vMin.value;
	var vMaxstr = document.inputPanel.vMax.value;
	if ((functionExprx == "") ||(functionExpry == "") || (functionExprz == "") ) {alert("You must enter x, y, and z as funcitons of u and v."); okToRoll = false}
	else if (!looksLikeANumber(uMinstr)) { alert("You have not entered a numerical value for uMin."); okToRoll = false}
	else if (!looksLikeANumber(uMaxstr)) { alert("You have not entered a numerical value for uMax."); okToRoll = false}
	else if (!looksLikeANumber(vMinstr)) { alert("You have not entered a numerical value for vMin."); okToRoll = false}
	else if (!looksLikeANumber(vMaxstr)) { alert("You have not entered a numerical value for vMax."); okToRoll = false}
	if (okToRoll)
		{
		var uMin = myEval(uMinstr);
		var uMax = myEval(uMaxstr);
		var vMin = myEval(vMinstr);
		var vMax = myEval(vMaxstr);
	if (uMin >= uMax) { alert("uMin should be less than uMax."); okToRoll = false}
	else if (vMin >= vMax) { alert("vMin should be less than vMax."); okToRoll = false}
		}

	if (okToRoll)
		{
		var theStringx = myParse(document.inputPanel.xfunc.value);
		var theStringy = myParse(document.inputPanel.yfunc.value);
		var theStringz = myParse(document.inputPanel.zfunc.value);
		generateTriangles(theStringx, theStringy, theStringz, uMin, uMax, vMin, vMax);
	//	drawFilled(true);
		}
	
	}


} 
// *** End of getReady

// *** GENERATE TRIANGLES FOR A FUNCTION ****
function generateTriangles(theFunctionX, theFunctionY, theFunctionZ, uMin, uMax, vMin, vMax) {

// the parametric variables are "u" and "v"
numTriangles = 0;
var deltau = (uMax-uMin)/numIncrements;
var deltav = (vMax-vMin)/numIncrements;
var x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, t1, t2, t3, xg1, yg1, xg2, yg2, norm, norm2;
u = uMin;
v = vMin; 
var xLow = eval(theFunctionX);	// these are the bounds of the enveloping parallelopiped
var xHigh = xLow; 
if (isNaN(xLow)) {xLow = 1000000; xHigh = -1000000}
var yLow = eval(theFunctionY);
var yHigh = yLow;
if (isNaN(yLow)) {yLow = 1000000; yHigh = -1000000}
var zLow = eval(theFunctionZ);
var zHigh = zLow;
if (isNaN(zLow)) {zLow = 1000000; zHigh = -1000000}


for (var i = 1; i <= numIncrements+1; i++)
	{
	for (var j = 1; j <= numIncrements+1; j++)
		{
		x1 = eval(theFunctionX);
		if (x1 < xLow) xLow = x1;
		else if (x1 > xHigh) xHigh = x1;
		vertexArray[i][j][1] = x1;
		
		y1 = eval(theFunctionY);
		if (y1 < yLow) yLow = y1;
		else if (y1 > yHigh) yHigh = y1;
		vertexArray[i][j][2] = y1;
		
		z1 = eval(theFunctionZ);
		if (z1 < zLow) zLow = z1;
		else if (z1 > zHigh) zHigh = z1;
		vertexArray[i][j][3] = z1;

// alert("u = " + u + "; v = " + v + "("+x1 + " " + y1 + " " + z1 + ")");

// alert(vertexArray[i][j][3]);
		v += deltav;
		} // j
	u += deltau;
	v = vMin;
	} // i

var deltaX = xHigh-xLow;
var deltaY = yHigh-yLow;
var deltaZ = zHigh-zLow;
// alert("deltaX = "+ deltaX + "deltay = "+ deltaY + "deltaZ = "+ deltaZ);

if((squareUp)&&(theMode == 1)) {
var biggest = deltaX;
var biggestScale = "X";
if (deltaY > biggest) {biggest = deltaY; biggestScale = "Y"}
if (deltaZ > biggest) {biggest = deltaZ; biggestScale = "Z"}
if (biggestScale == "X") {
	yLow -= (biggest-deltaY)/2;
	zLow -= (biggest-deltaZ)/2;
	yHigh += (biggest-deltaY)/2;
	zHigh += (biggest-deltaZ)/2
	}
else if (biggestScale == "Y") {
	xLow -= (biggest-deltaX)/2;
	zLow -= (biggest-deltaZ)/2;
	xHigh += (biggest-deltaX)/2;
	zHigh += (biggest-deltaZ)/2
	}
else{
	xLow -= (biggest-deltaX)/2;
	yLow -= (biggest-deltaY)/2;
	xHigh += (biggest-deltaX)/2;
	yHigh += (biggest-deltaY)/2
	}
deltaX = xHigh-xLow;
deltaY = yHigh-yLow;
deltaZ = zHigh-zLow;
// alert("deltaX = "+ deltaX + "deltay = "+ deltaY + "deltaZ = "+ deltaZ);
} // for squaring up scales

// now scale everything to fit into a unit cube [-1, 1]^3
for (var i = 1; i <= numIncrements+1; i++)
	{
	for (var j = 1; j <= numIncrements+1; j++)
		{
		x1 = vertexArray[i][j][1];
		y1 = vertexArray[i][j][2];
		z1 = vertexArray[i][j][3];
		vertexArray[i][j][1] = 2*(x1-xLow)/deltaX - 1;
		vertexArray[i][j][2] = 2*(y1-yLow)/deltaY - 1;
		vertexArray[i][j][3] = 2*(z1-zLow)/deltaZ - 1;
// alert("i = " + i + "; j = " + j + "("+x1 + " " + y1 + " " + z1 + ")");
		} // i
	} // j

// alert ("HERE***HERE");

// now generate the actual triangles
var theResult = new makeArray(3);
var dd1 = 0;
var dd2 = 0;
var dd3 = 0;	// this will be the vector from the lens to the midpoint of the face
for (var i = 1; i <= numIncrements; i++)
	{
	for (var j = 1; j <= numIncrements; j++)
		{
		numTriangles += 1;
		theTriangleArray[numTriangles][1] = 1;	// set to draw it
		x1 = vertexArray[i][j][1];
		y1 = vertexArray[i][j][2];
		z1 = vertexArray[i][j][3];
		theResult = persp_proj(x1, y1, z1);
		xg1 = theResult[1];	// x1
		yg1 = theResult[2];	// y1
		xg2 = Math.round( numX*(xg1-xMin)/DXX );
		yg2= Math.round( numY*(yMax-yg1)/DYY );
		theTriangleArray[numTriangles][7] = xg2;	// x1
		theTriangleArray[numTriangles][8] = yg2;	// y1
// alert("i = " + i + "; j = " + j + "("+xg1 + " " + yg1 + ")");
		x2 = vertexArray[i+1][j][1];
		y2 = vertexArray[i+1][j][2];
		z2 = vertexArray[i+1][j][3];
		theResult = persp_proj(x2, y2, z2);
		xg1 = theResult[1];	// x1
		yg1 = theResult[2];	// y1
		xg2 = Math.round( numX*(xg1-xMin)/DXX );
		yg2= Math.round( numY*(yMax-yg1)/DYY );
		theTriangleArray[numTriangles][9] = xg2;	// x1
		theTriangleArray[numTriangles][10] = yg2;	// y1
// alert("i = " + i + "; j = " + j + "("+xg1 + " " + yg1 + ")");
		x3 = vertexArray[i][j+1][1];
		y3 = vertexArray[i][j+1][2];
		z3 = vertexArray[i][j+1][3];
		theResult = persp_proj(x3, y3, z3);
		xg1 = theResult[1];	// x1
		yg1 = theResult[2];	// y1
		xg2 = Math.round( numX*(xg1-xMin)/DXX );
		yg2= Math.round( numY*(yMax-yg1)/DYY );
		theTriangleArray[numTriangles][11] = xg2;	// x1
		theTriangleArray[numTriangles][12] = yg2;	// y1
		x4 = vertexArray[i+1][j+1][1];
		y4 = vertexArray[i+1][j+1][2];
		z4 = vertexArray[i+1][j+1][3]; // these for later (next triangle)
// alert("i = " + i + "; j = " + j + "("+xg1 + " " + yg1 + ")");
		
		// now set up the normal stuff and light intensity
// alert(x1 + " " + y1 + " " + z1 + ") ( " + x2 + " " + y2 + " " + z2 + ") ( " + x3 + " " + y3 + " " + z3);
		theResult = cross(x2-x1,y2-y1,z2-z1,x3-x1,y3-y1,z3-z1);
	norm = Math.pow(theResult[1]*theResult[1] + theResult[2]*theResult[2] + theResult[3]*theResult[3],0.5);

  		if(norm < epsilon) {theResult = cross(x4-x1,y4-y1,z4-z1,x4-x2,y4-y2,z4-z2); norm = Math.pow(theResult[1]*theResult[1] + theResult[2]*theResult[2] + theResult[3]*theResult[3],0.5)}

			var dot = theResult[1]*(x1-xv) + theResult[2]*(y1-yv) + theResult[3]*(z1-zv);
			if (dot < 0) theTriangleData2[numTriangles][1] = 1; 
			else theTriangleData2[numTriangles][1] = 0;
			theTriangleData2[numTriangles+1][1] = theTriangleData2[numTriangles][1];
			// both triangles face the same way
		
			norm2 = Math.pow((xL-x1)*(xL-x1) + (yL-y1)*(yL-y1) + (zL-z1)*(zL-z1),0.5);
// alert(norm + " " + norm2);
		
			dot =  (theResult[1]*(xL-x1) + theResult[2]*(yL-y1) + theResult[3]*(zL-z1))/(norm*norm2);
// alert(dot);
			theTriangleData2[numTriangles][2] = -dot;
			theTriangleData2[numTriangles+1][2] = -dot; // both have same intensity
			// distance to viewer squared	
			dd1 = xv - (x2+x3)/2;
			dd2 = yv - (y2+y3)/2;
			dd3 = zv - (z2+z3)/2;
			norm = dd1*dd1+dd2*dd2+dd3*dd3;
			theTriangleData2[numTriangles][3] = norm;
			theTriangleData2[numTriangles+1][3] = norm; // both have same distance

	
		// now the next triangle;
		numTriangles ++;
		

		theResult = persp_proj(x4, y4, z4);
		xg1 = theResult[1];	// x1
		yg1 = theResult[2];	// y1
		xg2 = Math.round( numX*(xg1-xMin)/DXX );
		yg2= Math.round( numY*(yMax-yg1)/DYY );
		theTriangleArray[numTriangles][7] = xg2;	// x1
		theTriangleArray[numTriangles][8] = yg2;	// y1
// alert("i = " + i + "; j = " + j + "("+xg1 + " " + yg1 + ")");
		// now the other vertices (already computed)
		theTriangleArray[numTriangles][9] = theTriangleArray[numTriangles-1][11];			theTriangleArray[numTriangles][10] = theTriangleArray[numTriangles-1][12];
		theTriangleArray[numTriangles][11] = theTriangleArray[numTriangles-1][9];			theTriangleArray[numTriangles][12] = theTriangleArray[numTriangles-1][10];	
// alert(z2);
// alert(i);
// alert(j);
		} // j
	
	} // i

// alert(numTriangles);

 // ok now  ** HERE we need to sort the distances and normalize the intensities (1-10)
var dmin = theTriangleData2[1][3];	// distance of first triangle
var dmax = dmin;
if (isNaN(dmin)) {dmin = 50000; dmax = 0} // should be ok because everything is only a few units away in the unit cube
var dist;
var theTriangle = 1;
var theTriangle2 = 1;
var sorting = true;
var theIndex = 1;
var found = false;
var theStart = 1; 		// loop starts looking here


while (sorting) 
	{
	for (var i = theStart; i <= numTriangles; i++)
		{ 
		found = false;
		if (theTriangleArray[i][1] != 0)
			{
			dist = theTriangleData2[i][3];
			if  (dist <= dmin)  { dmin = dist; theTriangle2 = i}
			else if (dist >= dmax) { dmax = dist; theTriangle = i}
			else if (isNaN(dist)) {theTriangle2 = i}
			}
		i++; 	// do it in twos
		} // i
	 
	theTriangleArray[theTriangle][1] = 0;
	theTriangleArray[theTriangle+1][1] = 0;
	theTriangleArray[theTriangle2][1] = 0;
	theTriangleArray[theTriangle2+1][1] = 0;
	theSequence[theIndex] = theTriangle;
	theSequence[theIndex+1] = theTriangle+1;
	theSequence[numTriangles-theIndex] = theTriangle2;
	theSequence[numTriangles-theIndex+1] = theTriangle2+1;

	theIndex += 2;
// alert(theIndex);
// alert(theTriangle);
	sorting = false;
	for (var i = theStart; i <= numTriangles; i +=2)
		{
		if (theTriangleArray[i][1] == 1) 
			{
			sorting = true;
			dmin = theTriangleData2[i][3];
			dmax = dmin;
			if (isNaN(dmin)) {dmin = 50000; dmax = 0} // should be ok because everything is only a few units away in the unit cube
			theTriangle = i;
			theTriangle2 = i; 
			theStart = i;	
			break;
			// next round starts here
			} // if saw a 1
	
		} // i
	
	} // whle sorting;

// alert("sorted");

	
// now do the colors.
// intensities should be between -1 and +1
var intens, theColor;
for (var i = 1; i <= numTriangles; i++)
	{
	intens = Math.round((theTriangleData2[i][2] + 1)*5);	// should be scaled 0-10 now
// alert(theTriangleData2[i][2]);
// now adjust the intensity as per control panel;
	intens += deltaIntens;
	if(intens < 0) intens = 0;
	if(intens > 9) intens = 9;
	if (theTriangleData2[i][1] == 1) theColor = 10+intens;
	else theColor = 20+intens;
	theTriangleArray[i][2] = theColor;
	}
// alert(numTriangles);
// testing .....
// this puts up all the coordinates
// var theSt = "";
// for (var i = 1; i <= numTriangles; i++)
// 	{
//	for (var j = 1; j <= 12; j++)
//		{
//		theSt += theTriangleArray[i][j] + " ";
//		}
//		theSt += unescape( "%0D" );
//	} 
// document.inputPanel.theText.value = theSt;
// end testing ...

// document.inputPanel.theText.value += "DONE";
} // generate triangles



// ******* END GENERATE FUNCTION ***********

// ***********************
// *    cross product    *
// ***********************
function cross(x1,y1,z1,x2,y2,z2) {
var out = new makeArray(3);
out[1] = y1*z2-z1*y2;
out[2] = z1*x2-z2*x1;
out[3] = x1*y2-y1*x2;
return(out);
} // cross product
// *** END CROSS PRODUCT***

// *******************************
// *   routine perspective projection   *
// *******************************
// globals are various: xn, yn, zn, xr, yr, ze
function persp_proj(xp,yp,zp) {
var tq,xq,yq,zq, xvq, yvq, zvq
var out = new makeArray(3);

tq = (xn*(xv-xp) + yn*(yv-yp) + zn*(zv-zp)) / (xn*(xe-xp) + yn*(ye-yp) + zn*(ze-zp));


xvq = xp + (xe - xp)*tq -xv;
yvq = yp+ (ye - yp)*tq -yv;
zvq = zp+ (ze - zp)*tq -zv;

out[1] = xvq*xtx+yvq*ytx+zvq*ztx;
out[2] = xvq*xtu+yvq*ytu+zvq*ztu;
return(out)
} // persp proj
// ******** END PERSPECTIVE PROJ *************

// ******* FUNCTION FIX TRIANGLE ARRAY BOUNDS
// sets drawing windows for each triangle

function fixArrayBounds() {
var minx = 0; var maxx = 0; var miny = 0; var maxy = 0;
var x1, x2, x3, y1, y2, y3;
var crossProd = new makeArray(3);
for (var i = 1; i <= numTriangles; i++)
	{ 
	if (theTriangleArray[i][1] == 1)
		{
		x1 = theTriangleArray[i][7];
		y1 = theTriangleArray[i][8];
		x2 = theTriangleArray[i][9];
		y2 = theTriangleArray[i][10];
		x3 = theTriangleArray[i][11];
		y3 = theTriangleArray[i][12];
		minx = x1;
		maxx = x1;
		miny = y1;
		maxy = y1;
		if (x2 > maxx) maxx = x2;
		if (x3 > maxx) maxx = x3;
		if (x2 < minx) minx = x2;
		if (x3 < minx) minx = x3;
		if (y2 > maxy) maxy = y2;
		if (y3 > maxy) maxy = y3;
		if (y2 < miny) miny = y2;
		if (y3 < miny) miny = y3;
		theTriangleArray[i][3] = Math.round(minx);
		theTriangleArray[i][4] = Math.round(miny);
		theTriangleArray[i][5] = Math.round(maxx);
		theTriangleArray[i][6] = Math.round(maxy);
		
		}
	} // end of main loop i
} // fix array bounds

// ****** END FIX ARRAY BOUNDS ***************

// ******* FUNCTION DRAW FILLED RECTANGLE *******
// Needs a rectangle array theTriangleangles of dimension 	numTriangles x 12
// format of [i] th entry: 	must draw? 0/1 , color , minx, miny, maxx, maxy, x1, y1, x2, y2, x3, y3
// dealing with screen coords by this point...
// the rectangles are assumed sorted furthest to closest
// if edges are on, just paint part of outer boundary

function drawFilled (edgeon) { 
var x1, x2, x3, y1, y2, y3, minx, miny, maxx, maxy;
fixArrayBounds();		// insert all array bounds

	var p = 1;

 // Step 2 Draw them
// set up excanvas
ctx.clearRect(0,0,canvasWidth, canvasHeight);

// ***Testing
// var theStringTest = '';
// for (var k = 1; k <= numTriangles; k++) { p = theSequence[k]; theStringTest += (" "+p)}
// alert(theStringTest);
// END of test

// first draw the box if required
if(framed)drawBackOfBox();
theTriangle = 0;	// resets the current triangle
for (var k = 1; k <= numTriangles; k+=2)
	{
	p = theSequence[k];
// alert("k = "+k + " p = " + p + "color = "+ theTriangleArray[p][2]);
	var style = '';

// now create the colors
// stored in theTriangleArray[p][2]
// 10-19 purples 
// 20-29: greens

var theColorCode = theTriangleArray[p][2];

 
if ((10 <= theColorCode) && (theColorCode <= 19))
	{
	// purples
	var theRed = 130 + (theColorCode-10)*25;
	if (theRed > 255) theRed = 255;
	var theGreen = 50 + (theColorCode-10)*20;
	if (theGreen > 255) theGreen = 255;
	var theBlue = 220 + (theColorCode-10)*25;
	if (theBlue > 255) theBlue = 255;
	}
else if ((20 <= theColorCode) && (theColorCode <= 30))
	{
	// greens
	var theRed = (theColorCode-20)*25;
	if (theRed > 255) theRed = 255;
	var theGreen = 100 + (theColorCode-20)*33;
	if (theGreen > 255) theGreen = 255;
	var theBlue = (theColorCode-20)*30;
	if (theBlue > 255) theBlue = 255;
	}
style = "rgb(" + theRed + "," + theGreen + "," + theBlue + ")";

	if (!isNaN(theColorCode)) ctx.strokeStyle = style;
	else ctx.strokeStyle = "rgb(100,100,100)"; // will not plot this actually
	if (!isNaN(theColorCode)) ctx.fillStyle = style;
	else ctx.fillStyle = "rgb(100,100,100)"; // will not plot this actually
	var x1 = theTriangleArray[p][7];
	var y1 = theTriangleArray[p][8];
	var x2 = theTriangleArray[p][9];
	var y2 = theTriangleArray[p][10];
	var x3 = theTriangleArray[p][11];
	var y3 = theTriangleArray[p][12];
	var xn1 = theTriangleArray[p+1][7];
	var yn1 = theTriangleArray[p+1][8];
	var xn2 = theTriangleArray[p+1][9];
	var yn2 = theTriangleArray[p+1][10];
	var xn3 = theTriangleArray[p+1][11];
	var yn3 = theTriangleArray[p+1][12];

// alert("(" + x1 + "," + y1 + "), ("+ x2 + "," + y2 + "), ("+ x3 + "," + y3 + ")");
// if (k == 101) alert(isNaN(y1));
if (!isNaN(x1)&&!isNaN(x2)&&!isNaN(x3)&&!isNaN(xn1)&&!isNaN(xn2)&&!isNaN(xn3)&& !isNaN(y1)&&!isNaN(y2)&&!isNaN(y3)&&!isNaN(yn1)&&!isNaN(yn2)&&!isNaN(yn3)) {
	ctx.beginPath();
	ctx.moveTo(x1,y1);
	ctx.lineTo(x2,y2);
	ctx.lineTo(x3,y3);
	ctx.fill();
	ctx.beginPath();
	ctx.moveTo(x2,y2);
	ctx.lineTo(x3,y3);
	ctx.stroke();
	ctx.beginPath();
	ctx.moveTo(xn1,yn1);
	ctx.lineTo(xn2,yn2);
	ctx.lineTo(xn3,yn3);
	ctx.fill();
	ctx.beginPath();
	ctx.moveTo(xn2,yn2);
	ctx.lineTo(xn3,yn3);
	ctx.stroke();

	// now the outline
	theRed = Math.round(theRed*.7);
	theGreen = Math.round(theGreen*.7);
	theBlue = Math.round(theBlue*.7);
	style = "rgb(" + theRed + "," + theGreen + "," + theBlue + ")";

	ctx.strokeStyle = style;
	ctx.beginPath();
	ctx.moveTo(x3,y3);
	ctx.lineTo(x1,y1);
	ctx.lineTo(x2,y2);
	ctx.stroke();
}


	} // done with all the triangles
if(framed)drawFrontOfBox();


	
	



} // drawFilled








 

function det(A)
	{
	var Length = A.length-1;
		// formal length of a matrix is one bigger
	if (Length == 1) return (A[1][1]);
	else
		{
		var i;
		var sum = 0;
		var factor = 1;
		for (var i = 1; i <= Length; i++)
			{
			if (A[1][i] != 0)
				{
				// create the minor
				minor = new makeArray2(Length-1,Length-1);
				var m;
				var n;
				var theColumn;
				for (var m = 1; m <= Length-1; m++) // columns
					{
					if (m < i) theColumn = m;
					else theColumn = m+1;
					for (var n = 1; n <= Length-1; n++)
						{
						minor[n][m] = A[n+1][theColumn];
// alert(minor[n][m]);
						} // n
					} // m
				// compute its determinant
				sum = sum + A[1][i]*factor*det(minor);
				}
			factor = -factor;	// alternating sum
			} // end i
		} // recursion
	return(sum);
	} // end determinant

function inverse(A) {
	var Length = A.length - 1;
	B = new makeArray2(Length, Length);  // inverse
	var d = det(A);
	if (d == 0) alert("singular matrix--check data");
	else
		{
		var i;
		var j;
		for (var i = 1; i <= Length; i++)
			{
			for (var j = 1; j <= Length; j++)
				{
				// create the minor
				minor = new makeArray2(Length-1,Length-1);
				var m;
				var n;
				var theColumn;
				var theRow;
				for (var m = 1; m <= Length-1; m++) // columns
					{
					if (m < j) theColumn = m;
					else theColumn = m+1;
					for (var n = 1; n <= Length-1; n++)
						{
						if (n < i) theRow = n;
						else theRow = n+1;
						minor[n][m] = A[theRow][theColumn];
// alert(minor[n][m]);
						} // n
					} // m
				// inverse entry
				var temp = (i+j)/2;
				if (temp == Math.round(temp)) factor = 1;
				else factor = -1;
				
				B[j][i] =  det(minor)*factor/d; 

				
				} // j
			
			} // end i
		} // recursion
	return(B);
	} // end inverse

function shiftRight(theNumber, k) {
	if (k == 0) return (theNumber)
	else
		{
		var k2 = 1;
		var num = k;
		if (num < 0) num = -num;
		for (var i = 1; i <= num; i++)
			{
			k2 = k2*10
			}
		}
	if (k>0) 
		{return(k2*theNumber)}
	else 
		{return(theNumber/k2)}
	}


function roundSigDig(theNumber, numDigits) {
	with (Math)
		{
		if (theNumber == 0) return(0);
		else if(abs(theNumber) < 0.000000000001) return(0);
// WARNING: ignores numbers less than 10^(-12)
		else
			{
			var k = floor(log(abs(theNumber))/log(10))-numDigits+1
			var k2 = shiftRight(round(shiftRight(abs(theNumber),-k)),k)
			if (theNumber > 0) return(k2);
			else return(-k2)
			} // end else
		}
} // roundSigDig

function clearForms (){
	document.inputPanel.output.value=""
	document.inputPanel.coeff.value =""
	for (i = 0; i <= 15; i++)
		{
		document.inputPanel[2+3*i].value = "";	// xval[i]
		document.inputPanel[3+3*i].value = "";	// yval[i]
		document.inputPanel[4+3*i].value = "";	// ansl[i]
		// document.inputPanel.xval[i].value=""
		// document.inputPanel.yval[i].value=""
		// document.inputPanel.ans[i].value=""
		}
	}

function stripSpaces (InString)  {
	OutString="";
	for (var Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=" ")
			OutString=OutString+TempChar;
		}
	return (OutString);
}

function stripChar(InString,symbol)  {
	var OutString="";
	for (var Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=symbol)
			OutString=OutString+TempChar;
	}
	return (OutString);
}

function parser (InString, Sep)  {
	var NumSeps, Count, parse, Start, ParseMark, TestMark, LoopCtrl;
	NumSeps=1;
	for (Count=1; Count < InString.length; Count++)  {
		if (InString.charAt(Count)==Sep)
			NumSeps++;
	}
	parse = new makeArray (NumSeps);
	Start=0; Count=1; ParseMark=0;
	LoopCtrl=1;
	while (LoopCtrl==1)  {
		ParseMark = InString.indexOf(Sep, ParseMark);
		TestMark=ParseMark+0;
		if  (TestMark==-1){
			parse[Count]= InString.substring (Start, InString.length);
			LoopCtrl=0;
			break;
		}
		parse[Count] = InString.substring (Start, ParseMark);
		Start=ParseMark+1;
		ParseMark=Start;
		Count++;
	}
	parse[0]=Count;
	return (parse);
}


function buildxy()  {
	readAccuracy();
	e = 2.718281828459045;
	pi = 3.141592653589793;	
	with (Math)
		{
		N = 0; 		// number of variables 
		var searching = true;
		for ( i = 0; i <= 15; i++)			// arrays start at 0
			{
			theString = stripSpaces(document.inputPanel[2+3*i].value);	
							
			if (theString == "") searching = false;
			if (searching)
				{ 
				N++;
				X[N] = eval(theString);
				theString = stripSpaces(document.inputPanel[3+3*i].value);
				Y[N] = eval(theString);
				}
			} // of i = 1 to 15
		} // end of with math
	}

function looksLikeANumber(theString) {
// returns true if theString looks like it can be evaluated
var result = true;
theString = stripSpaces(theString);
if (theString == "") return(false);
theString = replaceSubstring(theString,"pi","3");	// just temporary!!!
theString = replaceSubstring(theString,"e","2");	// ditto
theString = replaceSubstring(theString,"sin","2");	// ditto
theString = replaceSubstring(theString,"cos","2");	// ditto
theString = replaceSubstring(theString,"ln","2");	// ditto
theString = replaceSubstring(theString,"log","2");	// ditto
theString = replaceSubstring(theString,"sqrt","2");	// ditto
var length = theString.length;
var x = ""
var y = "1234567890-+/*.()"
var yLength = y.length;
for (var i = 0; i <= length; i++)
	{ 
	x = theString.charAt(i);
		result = false;
		for (var j = 0; j <= yLength; j++) 
			{
			if (x == y.charAt(j)) {result = true; break}
			} // j
	if (result == false) return(false);
	} // i
return(result);
} // looks like a number

function calc2(){
	
	var num = calc2.arguments[0];

	//**********

	// Option 1	
	if (num == 1)
		{
		if(okToRoll) drawFilled(true);
		}

	// Option 2 Draw box
	else  if (num == 2)
		{
		drawBox();

		} // of this option

	// Option 3 OLD UP
	else  if (num == 3)
		{
		// rotate up vector too
		var xupold = xup;
		var yupold = yup;
		var zupold = zup;
		
		var xvold = xv;
		var yvold = yv;
		var zvold = zv;
		xv = xvold*cs - zvold*sn;
		zv = xvold*sn + zvold*cs;
		xup = xupold*cs - zupold*sn;
		zup = xupold*sn + zupold*cs;
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
		}
	
	// Option 4 RIGHT
	else  if (num == 4)
		{
		var xupold = xup;
		var yupold = yup;
		var zupold = zup;
		var xvold = xv;
		var yvold = yv;
		var zvold = zv;
		xv = xvold *cs + yvold *sn;
		yv = -xvold *sn + yvold *cs;
		xup = xupold *cs + yupold *sn;
		yup = -xupold *sn + yupold *cs;
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
		}


	// Option 5 LEFT
else  if (num == 5)
		{
		var xupold = xup;
		var yupold = yup;
		var zupold = zup;
		var xvold = xv;
		var yvold = yv;
		var zvold = zv;
		xv = xvold *cs - yvold *sn;
		yv = xvold *sn + yvold *cs;
		xup = xupold *cs - yupold *sn;
		yup = xupold *sn + yupold *cs;
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
	} // end option 5

	// Option 6 OLD DOWN
	else  if (num == 6)
		{
		// rotate up vector too
		var xupold = xup;
		var yupold = yup;
		var zupold = zup;

		var xvold = xv;
		var yvold = yv;
		var zvold = zv;
		xv = xvold *cs + zvold *sn;
		zv = -xvold *sn + zvold *cs;
		xup = xupold*cs + zupold*sn;
		zup = -xupold*sn + zupold*cs;
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
		}
	// Option 7 RESET
	else  if (num == 7)
		{
		// rotate up vector too
		xv = 5;
		yv = 5;
		zv = 2;
		xup = 0; yup = 0; zup = 1;
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
		}

		// Option 8 TRUE UP
	else  if (num == 8)
		{
		// rotate up vector too


		// now fix up up vector (right angles to this)
		var crossprod = cross(xv,yv,zv,xup,yup,zup);
		var xr = crossprod[1];
		var yr = crossprod[2];
		var zr = crossprod[3];  // this is a vector at right angles to up & v

		// var xvt = .2*xup + .8*xv;
		// var yvt = .2*yup + .8*yv;
		// var zvt = .2*zup + .8*zv;

		var xvt = 5*sn*xup + cs*xv;
		var yvt = 5*sn*yup + cs*yv;
		var zvt = 5*sn*zup + cs*zv;

		var len = Math.pow(xvt*xvt+yvt*yvt+zvt*zvt,.5);
		var rat = lenv/len;
		xv = xvt*rat;			// fix up length
		yv = yvt*rat;
		zv = zvt*rat;
// alert(xv + " " + yv + " " + zv);
		var crossprod2 = cross(xr,yr,zr,xv,yv,zv);
		var xupt = crossprod2[1];
		var yupt = crossprod2[2];
		var zupt = crossprod2[3];
		len = Math.pow(xupt*xupt+yupt*yupt+zupt*zupt,.5);
		xup = xupt/len;
		yup = yupt/len;
		zup = zupt/len
// alert(xup + " " + yup + " " + zup);
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
		}	

// Option 9 TRUE DOWN
	else  if (num == 9)
		{
		// rotate up vector too


		// now fix up up vector (right angles to this)
		var crossprod = cross(xv,yv,zv,xup,yup,zup);
		var xr = crossprod[1];
		var yr = crossprod[2];
		var zr = crossprod[3];  // this is a vector at right angles to up & v

		var xvt = -5*sn*xup + cs*xv;
		var yvt = -5*sn*yup + cs*yv;
		var zvt = -5*sn*zup + cs*zv;
		var len = Math.pow(xvt*xvt+yvt*yvt+zvt*zvt,.5);
		var rat = lenv/len;
		xv = xvt*rat;			// fix up length
		yv = yvt*rat;
		zv = zvt*rat;
// alert(xv + " " + yv + " " + zv);
		var crossprod2 = cross(xr,yr,zr,xv,yv,zv);
		var xupt = crossprod2[1];
		var yupt = crossprod2[2];
		var zupt = crossprod2[3];
		len = Math.pow(xupt*xupt+yupt*yupt+zupt*zupt,.5);
		xup = xupt/len;
		yup = yupt/len;
		zup = zupt/len
// alert(xup + " " + yup + " " + zup);
		xn = xt - xv;
		yn = yt - yv;
		zn = zt - zv;		// new zn
		resetView();
		drawBox();
		}	
}

function drawLine(x1,y1,x2,y2) {
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}

//************ Draw Box
function drawBox() {
canvas = document.getElementById("cv");
ctx = canvas.getContext("2d");
ctx.clearRect(0,0,canvasWidth, canvasHeight);
drawBackOfBox();
drawFrontOfBox();

} // drawBox

function drawBackOfBox() {
ctx.strokeStyle = "rgb(100,100,100)";
		if (1+xe >= 0)
			{
			drawLine(xCube1,yCube1,xCube2,yCube2);
			drawLine(xCube1,yCube1,xCube4,yCube4);
			drawLine(xCube3,yCube3,xCube4,yCube4);
			drawLine(xCube2,yCube2,xCube3,yCube3);
			}
		if (1-xe >=0)
			{
			drawLine(xCube5,yCube5,xCube6,yCube6);
			drawLine(xCube6,yCube6,xCube7,yCube7);
			drawLine(xCube7,yCube7,xCube8,yCube8);
			drawLine(xCube8,yCube8,xCube5,yCube5);		
			}

		if (1+ye >=0)
			{
			drawLine(xCube1,yCube1,xCube5,yCube5);
			drawLine(xCube5,yCube5,xCube8,yCube8);
			drawLine(xCube8,yCube8,xCube4,yCube4);
			drawLine(xCube4,yCube4,xCube1,yCube1);
			}	
		if (1-ye>=0)
			{
			drawLine(xCube2,yCube2,xCube3,yCube3);
			drawLine(xCube3,yCube3,xCube7,yCube7);
			drawLine(xCube7,yCube7,xCube6,yCube6);
			drawLine(xCube6,yCube6,xCube2,yCube2);		
			}
	
		if (1+ze>=0)
			{
			drawLine(xCube6,yCube6,xCube5,yCube5);
			drawLine(xCube5,yCube5,xCube1,yCube1);
			drawLine(xCube1,yCube1,xCube2,yCube2);
			drawLine(xCube2,yCube2,xCube6,yCube6);
			}	
		if (1-ze >=0)
			{
			drawLine(xCube8,yCube8,xCube7,yCube7);
			drawLine(xCube7,yCube7,xCube3,yCube3);
			drawLine(xCube3,yCube3,xCube4,yCube4);
			drawLine(xCube4,yCube4,xCube8,yCube8);		
			}


} // drawBackOfBox

function drawFrontOfBox() {
ctx.strokeStyle = "rgb(100,100,100)";

		if (1+xe < 0)
			{
			drawLine(xCube1,yCube1,xCube2,yCube2);
			drawLine(xCube1,yCube1,xCube4,yCube4);
			drawLine(xCube3,yCube3,xCube4,yCube4);
			drawLine(xCube2,yCube2,xCube3,yCube3);
			}
		if (1-xe < 0)
			{
			drawLine(xCube5,yCube5,xCube6,yCube6);
			drawLine(xCube6,yCube6,xCube7,yCube7);
			drawLine(xCube7,yCube7,xCube8,yCube8);
			drawLine(xCube8,yCube8,xCube5,yCube5);		
			}

		if (1+ye < 0)
			{
			drawLine(xCube1,yCube1,xCube5,yCube5);
			drawLine(xCube5,yCube5,xCube8,yCube8);
			drawLine(xCube8,yCube8,xCube4,yCube4);
			drawLine(xCube4,yCube4,xCube1,yCube1);
			}	

		if (1-ye< 0)
			{
			drawLine(xCube2,yCube2,xCube3,yCube3);
			drawLine(xCube3,yCube3,xCube7,yCube7);
			drawLine(xCube7,yCube7,xCube6,yCube6);
			drawLine(xCube6,yCube6,xCube2,yCube2);		
			}
	
		if (1+ze< 0)
			{
			drawLine(xCube6,yCube6,xCube5,yCube5);
			drawLine(xCube5,yCube5,xCube1,yCube1);
			drawLine(xCube1,yCube1,xCube2,yCube2);
			drawLine(xCube2,yCube2,xCube6,yCube6);
			}	
		if (1-ze < 0)
			{
			drawLine(xCube8,yCube8,xCube7,yCube7);
			drawLine(xCube7,yCube7,xCube3,yCube3);
			drawLine(xCube3,yCube3,xCube4,yCube4);
			drawLine(xCube4,yCube4,xCube8,yCube8);		
			}

} // drawFrontofBox

// ********************** Saving and Loading Utilities ************
function sesame(url,hsize,vsize){ 
// Default size is 550 x 400
        var tb="toolbar=0,directories=0,status=0,menubar=0"
        tb+=",scrollbars=1,resizable=1,"
    var tbend="width="+hsize+",height="+vsize;
    if(tbend.indexOf("<undefined>")!=-1){tbend="width=550,height=400"}
      tb+=tbend
     	Win_1 = window.open("","win1",tb);
      Win_1 = window.open(url,"win1",tb);
 	Win_1.focus();
}

function setCookie (cookieName, cookieValue, expires, path, domain, 
secure) {
  document.cookie = 
    escape(cookieName) + '=' + escape(cookieValue) 
    + (expires ? '; EXPIRES=' + expires.toGMTString() : '')
    + (path ? '; PATH=' + path : '')
    + (domain ? '; DOMAIN=' + domain : '')
    + (secure ? '; SECURE' : '');
}

function getCookie (cookieName) {
  var cookieValue = null;
  var posName = document.cookie.indexOf(escape(cookieName) + '=');
  if (posName != -1) {
    var posValue = posName + (escape(cookieName) + '=').length;
    var endPos = document.cookie.indexOf(';', posValue);
    if (endPos != -1)
      cookieValue = unescape(document.cookie.substring(posValue, 
endPos));
    else
      cookieValue = unescape(document.cookie.substring(posValue));
  }
  return cookieValue;
}

function saveIt()  {
// first test to see if cookies are enabled
var now = new Date();
var tomorrow = new Date(now.getTime() + 1000 * 60 * 60 * 24);
var nextyear = new Date(now.getTime() + 1000 * 60 * 60 * 24 * 365);
var twosecs = new Date(now.getTime() + 1000 * 2);
var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);
var twomin = new Date(now.getTime() + 1000 * 60 * 2);
setCookie('cookieTest','You will meet a tall dark stranger.', twosecs);
var testing = getCookie('cookieTest');
if(testing == null) {alert("Cookies are disabled on your computer. 'Save' and 'Load' will not work unless cookies are enabled."); cookiesEnabled = false}
// now get the various data to save
// resetView();
// getReady();
var theString = stripSpaces(document.inputPanel.functionstr.value); //1
theString += '$' + stripSpaces(document.inputPanel.xMin.value); //2
theString += '$' + stripSpaces(document.inputPanel.xMax.value); //3
theString += '$' + stripSpaces(document.inputPanel.yMin.value); //4
theString += '$' + stripSpaces(document.inputPanel.yMax.value); //5
theString += '$' + stripSpaces(document.inputPanel.xfunc.value); //6
theString += '$' + stripSpaces(document.inputPanel.yfunc.value); //7
theString += '$' + stripSpaces(document.inputPanel.zfunc.value); //8
theString += '$' + stripSpaces(document.inputPanel.uMin.value); //9
theString += '$' + stripSpaces(document.inputPanel.uMax.value); //10
theString += '$' + stripSpaces(document.inputPanel.vMin.value); //11
theString += '$' + stripSpaces(document.inputPanel.vMax.value); //12
var testString = stripChar(theString,'$');
if (cookiesEnabled && (stripSpaces(testString) != '')) {
// *** Open a window
	verifyList(); // reconcile save list with older saved files
	// add the global orientation data
	theString += '$' + xup; //13
	theString += '$' + yup; //14
	theString += '$' + zup; //15
	theString += '$' + xv; //16
	theString += '$' + yv; //17
	theString += '$' + zv; //18
	theString += '$' + xn; //19
	theString += '$' + yn; //20
	theString += '$' + zn; //21
	theString += '$' + theMode; //explicit = 0 parametric = 1
	theString += '$' + squareUp; //true or false
	setCookie('temp1', theString, twomin);
	var pqr = sesame("save.html",500,150);
	} // if cookies are enabled
else if (!cookiesEnabled) alert("Cookies are not enabled on your computer, so Save will not work.");
else alert("You have entered no data to save.");

} // saveIt

function verifyList() {
// This reconciles the save list with the actual files. 
// In particular it removes files that have expired.
var now = new Date();
var nextyear = new Date(now.getTime() + 1000 * 60 * 60 * 24 * 365);
var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);
var theList = getCookie('saveList');
// alert(theList);
var theStr = '';
var theName = '';
if (theList != null) {
	var theArray = parser(theList,"$");
	var n = theArray[0]-1;
// alert(theArray[2]);
	if (n <= 0) setCookie('saveList','',yesterday);
	else {
		for (var i = 1; i <= n; i++) {
			theName = theArray[i];
			var theData = getCookie(theName);
			if (theData != null) {
				theStr += theName + '$';
				setCookie(theName, theData, nextyear);
				// to update old data cookies
				} // end if
			} // i
		if (theStr == '') setCookie('saveList','',yesterday);
		else setCookie('saveList',theStr,nextyear);
		} // end else
	} // end if
}

function loadData() {
// first test to see if cookies are enabled
var now = new Date();
var tomorrow = new Date(now.getTime() + 1000 * 60 * 60 * 24);
var nextyear = new Date(now.getTime() + 1000 * 60 * 60 * 24 * 365);
var twosecs = new Date(now.getTime() + 1000 * 2);
var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);
var twomin = new Date(now.getTime() + 1000 * 60 * 2);
setCookie('cookieTest','You will meet a tall dark stranger.', twosecs);
var testing = getCookie('cookieTest');
if(testing == null) {alert("Cookies are disabled on your computer. 'Save' and 'Load' will not work unless cookies are enabled."); cookiesEnabled = false}
// now do the actual loading
if (cookiesEnabled) sesame('recover.html',500,150);
// note that the code for this is in that document
// all it does is create a temporary cookie of the saed data fot eh page to load on focus
// the function updateData takes the temporary cookie data and loads it onto the page
} // loadData

function updateData() {
// this is activated on focus and updates any graph data currently available in a short-lived cookie called temp
// should really strt with a cookies enabled test but the code takes care of it if not
if (cookiesEnabled) {
	var theS = getCookie('temp');
	var now = new Date();
	var tomorrow = new Date(now.getTime() + 1000 * 60 * 60 * 24);
	var nextyear = new Date(now.getTime() + 1000 * 60 * 60 * 24 * 365);
	var twosecs = new Date(now.getTime() + 1000 * 2);
	var yesterday = new Date(now.getTime() - 1000 * 60 * 60 * 24);
	setCookie('temp', '', yesterday);
// if(theS == null) alert(theS);
	if ((theS == null) || (theS == '')) theS = "hello";	// in case not enabled
	else {
		var kdata = parser(theS, '$');
// alert(theS)
		document.inputPanel.functionstr.value = kdata[1];
		document.inputPanel.xMin.value = kdata[2];
		document.inputPanel.xMax.value = kdata[3];
		document.inputPanel.yMin.value = kdata[4];
		document.inputPanel.yMax.value = kdata[5];
		document.inputPanel.xfunc.value = kdata[6];
		document.inputPanel.yfunc.value = kdata[7];
		document.inputPanel.zfunc.value = kdata[8];
		document.inputPanel.uMin.value = kdata[9];
		document.inputPanel.uMax.value = kdata[10];
		document.inputPanel.vMin.value = kdata[11];
		document.inputPanel.vMax.value = kdata[12];
		xup = kdata[13];
		yup = kdata[14];
		zup = kdata[15];
		xv = kdata[16];
		yv = kdata[17];
		zv = kdata[18];
		xn = kdata[19];
		yn = kdata[20];
		zn = kdata[21];
		theMode = kdata[22];
		if (theMode == 0) document.inputPanel.R1[0].checked = true;
		else document.inputPanel.R1[1].checked = true;
		squareUp = kdata[22];
		if (!squareUp) document.inputPanel.S1[0].checked = true;
		document.inputPanel.S1[1].checked = true;
// alert(kdata[21]);
		// now throw up the graph
		resetView();
		getReady(); 
		drawFilled(true);
		} // data to load
	for (var i = 1; i <= 100; i++) {theS = ''}
// I forget why the above line is there
	} // if cookies are enabled

} // updateData
