// This file contains the main raytracing functions and some interface related things.

// settings
var xres = 60; yres = 60;
var aathreshold = .1, aadepth = 3, aajitter = .5;
var maxtracelevel = 10; bailout = 1/255;
var maxactivitytime = 500; // in milliseconds
var pauseaftereveryline = true;

// variables, arrays, flags
var objects, lights, camera;
var colors, aaed, currentstartindex;
var perlinnoise;
var timesofar, timespentpaused, lasteventtime, numpixelsrendered;
var table, tbody, tr, pixsize, curdisplayyres, tablevisible = false;
var raytracingnow = false, paused = false, xpos, ypos, directionalaa, displayasrendered, rgbarray;
var errored = false;

// this function helps the process of (saving memory by only remembering colors and aaed values of pixels we will need them for in the future).
// how exactly it does that is complicated. Just forget about it for now.
function getindex(thexpos,theypos)
{
	//var toreturn = (ypos*xres+xpos)-(theypos*xres+thexpos) + currentstartindex; // equivalent to next line
	var toreturn = (ypos-theypos)*xres+xpos-thexpos + currentstartindex;
	if (toreturn >= xres+1) toreturn -= xres+1;
	return toreturn;
}

// gets the color of a pixel, or performs anti-aliasing on a pixel whose color we already know.
function getpixelcolor(cam, objects,lights, xpos,ypos, forceAA_horiz,forceAA_vert)
{
	var thisone = getindex(xpos,ypos);
	
	//status = xpos + " " + ypos;

	if (!forceAA_horiz && !forceAA_vert) { // only goes in here if this is the first time we're getting a color for this pixel
		aaed[thisone] = 0;

		// get this pixel's color
		colors[thisone] = cam.trace(objects,lights,(xpos+.5)/xres,(ypos+.5)/yres);
		colors[thisone].clip();
		
		// should we do AA? if so, recursively AA the adjacent pixels.
		if (aadepth > 1 && directionalaa && ypos > 0 && xpos > 0 && Color.difference(colors[thisone],colors[getindex(xpos,ypos-1)]) >= aathreshold && Color.difference(colors[thisone],colors[getindex(xpos-1,ypos)]) >= aathreshold)
		{
			if (aaed[getindex(xpos,ypos-1)] != 2 && aaed[getindex(xpos,ypos-1)] != 3) getpixelcolor(cam,objects,lights, xpos,ypos-1, false,true);
			if (aaed[getindex(xpos-1,ypos)] != 1 && aaed[getindex(xpos-1,ypos)] != 3) getpixelcolor(cam,objects,lights, xpos-1,ypos, true,false);
			forceAA_horiz = force_aa_vert = true;
		}
		else if (aadepth > 1 && ypos > 0 && Color.difference(colors[thisone],colors[getindex(xpos,ypos-1)]) >= aathreshold)
		{
			if (aaed[getindex(xpos,ypos-1)] != 2 && aaed[getindex(xpos,ypos-1)] != 3) getpixelcolor(cam,objects,lights, xpos,ypos-1, !directionalaa,true);
			forceAA_vert = true;
			forceAA_horiz = !directionalaa;
		}
		if ((!directionalaa || !forceAA_vert) && aadepth > 1 && xpos > 0 && Color.difference(colors[thisone],colors[getindex(xpos-1,ypos)]) >= aathreshold)
		{
			if (!aaed[getindex(xpos-1,ypos)] != 1 && aaed[getindex(xpos-1,ypos)] != 3) getpixelcolor(cam,objects,lights, xpos-1,ypos, true,!directionalaa);
			forceAA_horiz = true;
			forceAA_vert = !directionalaa;
		}
	}
	if (forceAA_horiz || forceAA_vert) // only goes in here if we're doing AA on this pixel. we know at least the center point of this pixel has been AA'ed already.
	{
		var aaxend = 1, aayend = 1, finalcolor;
		if (forceAA_horiz || aaed[thisone] > 0)
			aaxend = aadepth;
		if (forceAA_vert || aaed[thisone] > 0)
			aayend = aadepth;
		
		if (aadepth % 2 != 0)
		{
			if (aaed[thisone] == 0)
			{
				finalcolor = colors[thisone];
				if (forceAA_horiz && forceAA_vert) aaed[thisone] = 3;
				else if (forceAA_horiz) aaed[thisone] = 1;
				else if (forceAA_vert) aaed[thisone] = 2;
			}
			else
			{
				aaed[thisone] = 3;
				finalcolor = Color.scalar(colors[thisone],aadepth);
			}
		}
		else
			finalcolor = new Color(0,0,0);
		
		var xsamplepos,ysamplepos,xoffsetamnt = .5, yoffsetamnt = .5;
		for (var aay=0; aay < aayend; aay++)
		{
			for (var aax=0; aax < aaxend; aax++)
			{
				if (aadepth %2 == 0 ||
					((aaed[thisone] < 3 || (forceAA_horiz && forceAA_vert)) && (aax != (aaxend-1)/2 || aay != (aayend-1)/2)) ||
					((aaed[thisone] == 3 && !forceAA_horiz) && aay != (aayend-1)/2) ||
					((aaed[thisone] == 3 && !forceAA_vert) && aax != (aaxend-1)/2)
				)
				{
					if (aajitter != 0)
					{
						xoffsetamnt = .5 + (Math.random()-.5)*aajitter;
						yoffsetamnt = .5 + (Math.random()-.5)*aajitter;
					}
					xsamplepos = (xpos + (aax+xoffsetamnt)/aaxend)/xres;
					ysamplepos = (ypos + (aay+yoffsetamnt)/aayend)/yres;
					addtocolor = cam.trace(objects,lights, xsamplepos, ysamplepos);
					addtocolor.clip();
					finalcolor = Color.add(finalcolor, addtocolor);
				}
			}
		}
		colors[thisone] = Color.scalar(finalcolor,1/(aaxend*aayend));
	}
}

function toggleraytracing()
{
	if (!raytracingnow)
		raytrace();
	else {
		if (timesofar + timespentpaused >= 600000) { // 10 minutes spent already?
			if (!confirm('Are you sure you want to stop the render? It cannot be resumed.'))
				return;
		}
		finishraytrace();
	}
}

function togglepausestate()
{
	if (!raytracingnow) return;
	paused = !paused;
	if (paused)
	{
		document.getElementById('pausebutton').value = 'Resume';
		pausetimer();
	}
	else
	{
		document.getElementById('pausebutton').value = 'Pause';
		mainraytracingloop();
	}
}

checker = function (pos) {
	return (Math.floor(pos.x)+Math.floor(pos.y)+Math.floor(pos.z)) & 1;
}
noise = function (pos) {
	return perlinnoise.evaluate(pos);
}

function raytrace()
{
	if (numcameras != 1) {
		alert('The scene must contain exactly one camera in order to be rendered.');
		return;
	}

	lasteventtime = new Date();
	timespentpaused=0;
	timesofar=0;

	raytracingnow = true;
		
	document.getElementById('actionbutton').value = 'Cancel Render';
	document.getElementById('pausebutton').value = 'Pause';
	document.getElementById('pausebutton').style.visibility = 'visible';
	document.getElementById('pausebutton').style.display = 'inline';
	document.getElementById('removedisplaybutton').style.visibility = 'hidden';
	document.getElementById('removedisplaybutton').style.display = 'none';
	document.getElementById('showdisplaybutton').style.visibility = 'hidden';
	document.getElementById('showdisplaybutton').style.display = 'none';
	paused = false;
	
	xres = parseFloat(document.controls.xres.value);
	yres = parseFloat(document.controls.yres.value);
	aathreshold = parseFloat(document.controls.aathresh.value);
	aadepth = parseFloat(document.controls.aadepthval.value);
	if (aathreshold >= 3) aadepth = 1;
	aajitter = parseFloat(document.controls.aajitterval.value);
	directionalaa = document.controls.directionalaa_checkbox.checked;

	displayasrendered = document.controls.displayasrendering_checkbox.checked;
	
	objects = new Array();
	lights = new Array();
	for (var a=0; a < preliminaryobjects.length; a++)
	{
		if (preliminaryobjects[a].active) {
			if (creatableobjecttypenames[preliminaryobjects[a].type] == 'camera')
				camera = ObjectInterface.constructObject(preliminaryobjects[a].obj, creatableobjecttypenames[preliminaryobjects[a].type]);
			else if (creatableobjecttypenames[preliminaryobjects[a].type] == 'lightsource')
				lights[lights.length] = ObjectInterface.constructObject(preliminaryobjects[a].obj, creatableobjecttypenames[preliminaryobjects[a].type]);
			else {
				objects[objects.length] = ObjectInterface.constructObject(preliminaryobjects[a].obj, creatableobjecttypenames[preliminaryobjects[a].type]);
				objects[objects.length-1].initialize(); // all top-level objects must be initialized exactly once.
			}
		}
	}
		
	if (typeof(perlinnoise) == 'undefined')
	{
		window.status = 'Setting up Perlin noise function...';
		perlinnoise = new PerlinNoise(.8296815753293, 11);
		window.status = '';
	}
	
	numpixelsrendered = 0;
	updatestatus();
	
	colors = new Array(xres+1);
	aaed = new Array(xres+1);
	currentstartindex = 0;

	xpos = ypos = curdisplayyres = 0;

	if (tablevisible)
	{
		var obj = document.getElementById('rendered');
		obj.removeChild(obj.firstChild);
		tablevisible = false;
	}

	if (displayasrendered)
		inittable();
	else
		rgbarray = new Array(xres*yres*3);	
	
	setTimeout('mainraytracingloop();',1);
	startinactivetimer();
}
function inittable()
{
	var obj = document.getElementById('rendered');

	table = document.createElement('table');
	table.id = "display";
	
	var colgroup = document.createElement('colgroup');
	table.appendChild(colgroup);
	for (var a=0; a < xres; a++)
	{
		colgroup.appendChild(document.createElement('col'));
	}
	table.appendChild(tbody = document.createElement('tbody'));
	obj.appendChild(table);
	tablevisible = true;

	updatepixelsize();
}

function mainraytracingloop() {
	if (!raytracingnow || paused) return;
	
	startrendertimer();

	while (ypos < yres)
	{
		if (xpos == 0) startingline();
		while (xpos < xres)
		{
			getpixelcolor(camera, objects,lights, xpos,ypos, false,false);
			if (!finishedpixel()) return;
		}
		if (!finishedline()) return;
	}
	finishraytrace();
}

function finishedpixel()
{
	if (ypos > 0)
		assignpixelcolor(xpos,ypos-1);
	
	xpos++;
	numpixelsrendered++;
	
	currentstartindex--;
	if (currentstartindex == -1) currentstartindex = xres; // xres+1 -1

	now = new Date();
	if ((now.getTime()-lasteventtime.getTime()) > maxactivitytime || errored)
	{
		if (!errored) setTimeout('mainraytracingloop()',5);
		startinactivetimer();
		return false;
	}
	return true;
}
function startingline()
{
	if (ypos > 0 && displayasrendered)
		tr = document.createElement('tr');
}
function finishedline()
{
	if (ypos > 0 && displayasrendered)
		tbody.appendChild(tr);
	if (ypos == yres-1)
	{
		// show last line. we've been one line behind the whole render in order to display AAed pixels *after* AAing.
		if (displayasrendered)
			tr = document.createElement('tr');
		for (a=0; a < xres; a++)
			assignpixelcolor(a,ypos);
		if (displayasrendered)
			tbody.appendChild(tr);
	}
	if (displayasrendered)
	{
		curdisplayyres = ypos;
		table.style.height = ypos*pixsize + 'px';
	}
	
	xpos = 0;
	ypos++;
	
	if (pauseaftereveryline) {
		setTimeout('mainraytracingloop()',5);
		startinactivetimer();
		return false;
	}
	return true;
}
function assignpixelcolor(xpos,ypos)
{
	if (displayasrendered)
	{
		var td = document.createElement('td');;
		td.style.background = colors[getindex(xpos,ypos)];
		tr.appendChild(td);
	}
	else
	{
		var theindex = ((ypos)*xres+xpos)*3;
		var thecolor = colors[getindex(xpos,ypos)];
		rgbarray[theindex] = thecolor.red;
		rgbarray[theindex+1] = thecolor.green;
		rgbarray[theindex+2] = thecolor.blue;
	}
}

function finishraytrace(reason,numpixels)
{
	startrendertimer();
	paused = false;
	
	raytracingnow = false;
	
	document.getElementById('actionbutton').value = 'Render';
	document.getElementById('pausebutton').style.visibility = 'hidden';
	document.getElementById('pausebutton').style.display = 'none';
	if (!displayasrendered) {
		document.getElementById('showdisplaybutton').style.visibility = 'visible';
		document.getElementById('showdisplaybutton').style.display = 'inline';
	}
	else {
		document.getElementById('removedisplaybutton').style.visibility = 'visible';
		document.getElementById('removedisplaybutton').style.display = 'inline';
	}

	delete colors;
	delete aaed;

	startinactivetimer();
	// done. =)
}

// timer functions
var timersuspended = false;
function startinactivetimer()
{
	now = new Date();
	if (!timersuspended)
		timesofar += now.getTime()-lasteventtime.getTime();
	timersuspended = false;
	lasteventtime = now;
	updatestatus();
	
	if (errored) {
		errored = false;
		finishraytrace();
	}
}
function startrendertimer()
{
	now = new Date();
	if (!timersuspended)
		timespentpaused += now.getTime()-lasteventtime.getTime();
	timersuspended = false;
	lasteventtime = now;
}
function pausetimer()
{
	now = new Date();
	timespentpaused += now.getTime()-lasteventtime.getTime();
	lasteventtime = now;
	timersuspended = true;
	updatestatus();
}

// status div updater
function updatestatus()
{
	var newhtml = '';
	if (paused) newhtml += 'Paused.<br>';
	percentcomplete = Math.round(numpixelsrendered/(xres*yres)*100);
	if (!raytracingnow || percentcomplete == 100)
	{
		if (percentcomplete == 100)
			newhtml += 'Render completed.<br>';
		else
			newhtml += 'Render cancelled.<br>';
		newhtml += 'Render took <span class="number">' + (timesofar+timespentpaused)/1000 + '</span> seconds at <span class="number">' + Math.round(numpixelsrendered/((timesofar+timespentpaused)/1000)*1000)/1000 + '</span> pixels per second.<br>';
		newhtml += '<span class="number">' + timespentpaused/1000 + '</span> seconds were spent inactive. <span class="number">' + timesofar/1000 + '</span> seconds were spent active.<br>';
	}
	else if (percentcomplete == 0)
	{
		newhtml += 'Initializing...<br>';
	}
	else
	{
		newhtml += '<span class="number">'+percentcomplete+'%</span> complete.<br>';
		newhtml += '<span class="number">' + (timesofar+timespentpaused)/1000 + '</span> seconds so far at <span class="number">' + Math.round(numpixelsrendered/((timesofar+timespentpaused)/1000)*1000)/1000 + '</span> pixels per second.<br>';
		newhtml += '<span class="number">' + timespentpaused/1000 + '</span> seconds have been spent inactive. <span class="number">' + timesofar/1000 + '</span> seconds have been spent active.<br>';
	}
	document.getElementById('statusreport').innerHTML = newhtml;
}

function showdisplay()
{
	inittable();
	var tr,td,theindex;
	for (var y=0; y < ypos; y++)
	{
		tr = document.createElement('tr');
		for (var x=0; x < xres; x++)
		{
			td = document.createElement('td');
			theindex = (y*xres+x)*3;
			td.style.background = (new Color(rgbarray[theindex],rgbarray[theindex+1],rgbarray[theindex+2])).toString();
			tr.appendChild(td);
		}
		tbody.appendChild(tr);
	}
	table.style.height = ypos*pixsize + 'px';
	
	delete colors;
	
	document.getElementById('showdisplaybutton').style.visibility = 'hidden';
	document.getElementById('showdisplaybutton').style.display = 'none';
	
	setTimeout("document.getElementById('removedisplaybutton').style.visibility = 'visible'; document.getElementById('removedisplaybutton').style.display = 'inline';",100);
}

function removedisplay()
{
	if (!confirm('Are you sure you want to remove the display? This cannot be undone (you will have to rerender the image).'))
		return;
	table.parentNode.removeChild(table);
	tablevisible = false;
	
	document.getElementById('removedisplaybutton').style.visibility = 'hidden';
	document.getElementById('removedisplaybutton').style.display = 'none';
}

function updatepixelsize()
{
	if (typeof(document.styleSheets) == 'undefined')
		return false;

	pixsize = parseFloat(document.controls.pixelsize.value);
	if (!tablevisible) return true;

	if (pixsize < 1) pixsize = 1;
	var therules;
	if (document.styleSheets[0].cssRules)
		therules = document.styleSheets[0].cssRules;
	else if (document.styleSheets[0].rules) // damn IE
		therules = document.styleSheets[0].rules;
	if (therules)
	{
		for (var a=0; a < therules.length; a++)
		{
			if (therules[a].selectorText.toLowerCase() == 'table#display td')
			{
				therules[a].style.width = pixsize+'px';
				therules[a].style.height = pixsize+'px';
			}
			else if (therules[a].selectorText.toLowerCase() == 'table#display tr')
			{
				therules[a].style.height = pixsize+'px';
			}
			else if (therules[a].selectorText.toLowerCase() == 'table#display col')
			{
				therules[a].style.width = pixsize+'px';
			}
		}
		/*msg = '';
		obj = therules[2].style;
		for (prop in obj)
			msg += prop + ' = ' + obj[prop] + ' ||| ';
		alert(msg);*/
	}
	document.getElementById('rendered').style.height = (yres*pixsize + 50) + "px";
	table.style.width = xres*pixsize + 'px'; // mozilla seems to like 0px width cells without this
	table.style.height = curdisplayyres*pixsize + 'px';
	
	return true;
}

// ========================== The rest of this code has nothing to do with raytracing. It is related only to the page's interface. =========================

ObjectInterface.objecttypes = {
	bool: new ObjectType(ObjectType.TYPE_BOOL),
	scalar: new ObjectType(ObjectType.TYPE_SCALAR),
	string: new ObjectType(ObjectType.TYPE_STRING),
	multiline: new ObjectType(ObjectType.TYPE_MULTILINE),
	vector: new ObjectType(ObjectType.TYPE_VECTOR),
	color: new ObjectType(ObjectType.TYPE_COLOR),
	
	objectmodifiers: new ObjectType(ObjectType.TYPE_PROPERTYLIST,new Array(
		new ObjectProperty('bool','inversed','Inversed',false),
		new ObjectProperty('bool','castshadows','Casts Shadows',true),
		new ObjectProperty('transformation','transformation','Transformation'),
		new ObjectProperty('texture','texture','Texture')
		),
		function(obj) {
			obj.inversed = this.inversed;
			obj.castshadows = this.castshadows;
			obj.transform = this.transformation;
			obj.texture = this.texture.texture;
			obj.textureoverridesparent = this.texture.overridesparent;
		}
	),
	
	camera: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('cameratypechoice','cameratypechoice','Camera Parameters',0),
		new ObjectProperty('transformation','transformation','Transformation')
		),
		function() {
			var toreturn;
			if (this.cameratypechoice == 0) toreturn = this.simplecamera;
			else toreturn = this.complexcamera;
			toreturn.transform(this.transformation);
			return toreturn;
		}
	),
	cameratypechoice: new ObjectType(ObjectType.TYPE_OBJECTCHOICE,new Array(
		new ObjectProperty('simplecamera','simplecamera','Simple'),
		new ObjectProperty('complexcamera','complexcamera','Complex')
		)
	),
	coordinatetypelist: new ObjectType(ObjectType.TYPE_CHOICE,new Array('Left Handed','Right Handed')),
	simplecamera: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','pos','Position',new Vector(0,0,-5)),
		new ObjectProperty('vector','lookat','Look At',new Vector(0,0,0)),
		new ObjectProperty('vector','sky','Sky Direction',new Vector(0,1,0)),
		new ObjectProperty('scalar','zoomfactor','Zoom Amount',1),
		new ObjectProperty('scalar','aspectratio','Scene Aspect Ratio (width/height)',4/3),
		new ObjectProperty('coordinatetypelist','handedness','Coordinate Type',0)
		),
		function() {
			var camdir,vcrossed,camright;
			camdir=Vector.normalize(Vector.add(this.lookat,this.pos.neg()));
			vcrossed = Vector.cross(this.sky,camdir);
			if (vcrossed.x == 0 && vcrossed.y == 0 && vcrossed.z == 0) {
				if (camdir.x == 0 && camdir.z == 0)
					camright = Vector.X;
				else
					camright = Vector.cross(Vector.Y,camdir);
			}
			else
				camright=Vector.normalize(vcrossed);
			return new Camera(
				this.pos,
				Vector.scalar(camdir,this.zoomfactor),
				Vector.scalar(camright,this.aspectratio * ((this.handedness==0)?1:-1)),
				Vector.cross(camright,camdir)
			);
		},
		false
	),
	complexcamera: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','pos','Position',new Vector(0,0,-5)),
		new ObjectProperty('vector','dir','Direction',new Vector(0,0,1)),
		new ObjectProperty('vector','right','Right',new Vector(4/3,0,0)),
		new ObjectProperty('vector','down','Down',new Vector(0,-1,0))
		),
		function() {
			return new Camera(this.pos,this.dir,this.right,this.down);
		},
		false
	),
	
	lightsource: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','pos','Position',new Vector(-10,10,-10)),
		new ObjectProperty('color','color','Color',new Color(1,1,1)),
		new ObjectProperty('arealight','arealight','Area Light'),
		new ObjectProperty('spotlight','spotlight','Spot Light'),
		new ObjectProperty('transformation','transformation','Transformation')
		),
		function() {
			var toreturn = new LightSource(this.pos,this.color);
			if (this.arealight.use)
			{
				if (this.arealight.dim1.x != 0 || this.arealight.dim1.y != 0 || this.arealight.dim1.z != 0)
				{
					toreturn.samplev1 = true;
					toreturn.v1 = this.arealight.dim1;
				}
				if (this.arealight.dim2.x != 0 || this.arealight.dim2.y != 0 || this.arealight.dim2.z != 0)
				{
					toreturn.samplev2 = true;
					toreturn.v2 = this.arealight.dim2;
				}
				toreturn.minsampledepth = this.arealight.minsampledepth;
				toreturn.maxsampledepth = this.arealight.maxsampledepth;
				toreturn.jitteramnt = this.arealight.jitteramnt;
			}
			if (this.spotlight.use)
			{
				toreturn.spotlight = true;
				toreturn.dir = Vector.normalize(Vector.add(this.spotlight.pointat,toreturn.pos.neg()));
				toreturn.outerangle = this.spotlight.outerangle*Math.PI/180;
				toreturn.angledifference = (this.spotlight.outerangle - this.spotlight.innerangle)*Math.PI/180;
				toreturn.inneranglecosine = Math.cos(this.spotlight.innerangle*Math.PI/180);
				toreturn.outeranglecosine = Math.cos(this.spotlight.outerangle*Math.PI/180);
			}
			toreturn.transform(this.transformation);
			return toreturn;
		}
	),
	arealight: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('bool','use','Use Area Light',false),
		new ObjectProperty('vector','dim1','Dimension 1',new Vector(1,0,0)),
		new ObjectProperty('vector','dim2','Dimension 2',new Vector(0,1,0)),
		new ObjectProperty('scalar','minsampledepth','Minimum Sampling Depth',0),
		new ObjectProperty('scalar','maxsampledepth','Maximum Sampling Depth',0),
		new ObjectProperty('scalar','jitteramnt','Jitter Amount',1)
		),
		function() {
			return {use:this.use,dim1:this.dim1,dim2:this.dim2,minsampledepth:this.minsampledepth,maxsampledepth:this.maxsampledepth,jitteramnt:this.jitteramnt};
		},
		false
	),
	spotlight: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('bool','use','Use Spot Light',false),
		new ObjectProperty('vector','pointat','Point At',new Vector(0,0,0)),
		new ObjectProperty('scalar','outerangle','Outer Angle',35),
		new ObjectProperty('scalar','innerangle','Inner Angle',30)
		),
		function() {
			return {use:this.use,pointat:this.pointat,outerangle:this.outerangle,innerangle:this.innerangle};
		},
		false
	),	
	
	sphere: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','pos','Center',new Vector(0,0,0)),
		new ObjectProperty('scalar','rad','Radius',1),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			return new Sphere(this.pos,this.rad);
		}
	),
	box: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','v1','Corner 1',new Vector(0,0,0)),
		new ObjectProperty('vector','v2','Corner 2',new Vector(1,1,1)),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			return new Box(this.v1,this.v2);
		}
	),
	plane: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','norm','Normal',new Vector(0,1,0)),
		new ObjectProperty('scalar','dist','Distance Along Normal',0),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			return new Plane(this.norm,this.dist);
		}
	),
	cylinder: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','end1','End 1',new Vector(0,0,0)),
		new ObjectProperty('vector','end2','End 2',new Vector(1,1,1)),
		new ObjectProperty('scalar','rad','Radius',1),
		new ObjectProperty('bool','open','Ends Open',false),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			return new Cylinder(this.end1,this.end2,this.rad,this.open);
		}
	),
	cone: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','end1','End 1',new Vector(0,0,0)),
		new ObjectProperty('scalar','rad1','Radius 1',1),
		new ObjectProperty('vector','end2','End 2',new Vector(1,1,1)),
		new ObjectProperty('scalar','rad2','Radius 2',0),
		new ObjectProperty('bool','open','Ends Open',false),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			return new Cone(this.end1,this.rad1,this.end2,this.rad2,this.open);
		}
	),
	
	union: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('objectlist','objects','Objects'),
		new ObjectProperty('bool','testboundingbox','Test Bounding Box',false),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			var toreturn = new CSGUnion(this.objects);
			toreturn.testboundingbox = this.testboundingbox;
			return toreturn;
		}
	),
	intersection: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('objectlist','objects','Objects'),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			return new CSGIntersection(this.objects);
		}
	),
	difference: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('objectlist','posobjects','Positive Objects'),
		new ObjectProperty('objectlist','negobjects','Negative Objects'),
		new ObjectProperty('objectmodifiers')
		),
		function() {
			var objects = new Array();
			if (this.posobjects.length <= 1)
				objects = this.posobjects;
			else
				objects[0] = new CSGUnion(this.posobjects);
			for (var a=0; a < this.negobjects.length; a++) {
				this.negobjects[a].inversed = !this.negobjects[a].inversed;
				objects[objects.length] = this.negobjects[a];
			}
			return new CSGIntersection(objects);
		}
	),
	objectlist: new ObjectType(ObjectType.TYPE_ARRAY,new Array(
		new ObjectProperty('sphere','','Sphere'),
		new ObjectProperty('box','','Box'),
		new ObjectProperty('plane','','Plane'),
		new ObjectProperty('cylinder','','Cylinder'),
		new ObjectProperty('cone','','Cone'),
		new ObjectProperty('union','','Union'),
		new ObjectProperty('intersection','','Intersection'),
		new ObjectProperty('difference','','Difference')
		),
		function() {
			return this.array;
		},
		false
	),
	
	texture: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('colorpigmentchoice','colorpigmentchoice','Pigment',0),
		new ObjectProperty('finish','finish','Finish'),
		new ObjectProperty('bool','overridesparent',"Overrides parent's texture (when part of union, intersection, etc.)")
		),
		function() {
			var toreturn;
			if (this.colorpigmentchoice == 0)
				toreturn = new Texture(this.color,this.finish);
			else {
				toreturn = new Texture(new Color(0,0,0),this.finish,this.pigment.func);
				toreturn.transform = this.pigment.trans;
			}
			return {texture:toreturn,overridesparent:this.overridesparent};
		}
	),
	finish: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('scalar','specular','Specular Amount',0),
		new ObjectProperty('scalar','glossiness','Specular Glossiness',10),
		new ObjectProperty('scalar','reflection','Reflection Amount',0),
		new ObjectProperty('scalar','diffuse','Diffuse Light Multiplier',.8),
		new ObjectProperty('scalar','ambient','Ambient Light Multiplier',.2)
		),
		function() {
			return new Finish(this.specular,this.glossiness,this.reflection,this.ambient,this.diffuse);
		}
	),
	pigment: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('multiline','func','JavaScript Function','/*\n"pos" is the point in space being evaluated. (try pos.x, pos.y, pos.z)\nThe functions noise(pos) (perlin noise) and checker(pos) (checker pattern) are available to you.\nBe sure to use "var" to declare your variables.\nReturn a Color object, as shown:\n*/\nreturn new Color(1,1,1); /*red,green,blue values*/'),
		new ObjectProperty('transformation','transformation','Transformations')
		),
		function() {
			this.func.replace(/\/\/[^\n]\n/,'');
			this.func.replace(/\n/, ' ');
			try{
				return {func:new Function('pos',this.func),trans:this.transformation};
			}
			catch(err){
				alert('Pigment function parse error:\n'+err.message);
				return {func:function(pos){return new Color(0,0,0);},trans:this.transformation};
			}
		}
	),
	colorpigmentchoice: new ObjectType(ObjectType.TYPE_OBJECTCHOICE,new Array(
		new ObjectProperty('color','color','Solid Color',new Color(1,1,1)),
		new ObjectProperty('pigment','pigment','Function Defined')
		)
	),
	
	transformation: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('transformationlist','components','Components'),
		new ObjectProperty('bool','inversed','Inverse')
		),
		function() {
			var toreturn = this.components;
			if (this.inversed && !toreturn.identity)
			{
				var temp = toreturn;
				toreturn = toreturn.inverse;
				toreturn.inverse = temp;
				delete toreturn.inverse.inverse;
			}
			return toreturn;
		}
	),
	transformationlist: new ObjectType(ObjectType.TYPE_ARRAY,new Array(
		new ObjectProperty('rotation','','Rotation'),
		new ObjectProperty('translation','','Translation'),
		new ObjectProperty('scale','','Scale'),
		new ObjectProperty('matrixtransformation','','Matrix'),
		new ObjectProperty('transformation','','Transformation')
		),
		function() {
			return Transformation.multipletrans(this.array);
		},
		false
	),
	axislist: new ObjectType(ObjectType.TYPE_CHOICE,new Array('X','Y','Z')),
	rotation: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('axislist','axis','Axis to Rotate Around',0),
		new ObjectProperty('scalar','amount','Degrees of Rotation',0)
		),
		function() {
			return Transformation.rotate(this.axis,this.amount/180*Math.PI);
		},
		false
	),
	translation: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','amnt','Amount',new Vector(0,0,0))
		),
		function() {
			return Transformation.translate(this.amnt);
		},
		false
	),
	scale: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','amnt','Amount',new Vector(1,1,1))
		),
		function() {
			return Transformation.scale(this.amnt);
		},
		false
	),
	matrixtransformation: new ObjectType(ObjectType.TYPE_OBJECT,new Array(
		new ObjectProperty('vector','vx','Map X to',new Vector(1,0,0)),
		new ObjectProperty('vector','vy','Map Y to',new Vector(0,1,0)),
		new ObjectProperty('vector','vz','Map Z to',new Vector(0,0,1)),
		new ObjectProperty('vector','c','Add',new Vector(0,0,0))
		),
		function() {
			return new Transformation(this.vx, this.vy, this.vz, this.c);
		},
		false
	)
};

var preliminaryobjects = new Array(), currentiface;
var creatingobject = false, editing = false, editingobjnum, objtype;
var creatableobjecttypenames = new Array('camera','lightsource','sphere','box','plane','cylinder','cone','union','intersection','difference','texture','finish','pigment','transformation');
var creatableobjectdisplaynames = new Array('Camera','Light Source','Sphere','Box','Plane','Cylinder','Cone','Union','Intersection','Difference','Texture','Finish','Function Pigment','Transformation');
var numberofobjects = new Array(); for (var a=0; a < creatableobjecttypenames.length; a++) numberofobjects[a] = 0;
var scenelevelobjectindexcutoff = 9; // last index of creatable objects that can be placed in the scene
var numcameras = 0;

ObjectInterface.createvariable = function(objinterface) {
	var thetype;
	for (var a=0; a < creatableobjecttypenames.length; a++) {
		if (objinterface.parentscorrespondingprop.objecttypename == creatableobjecttypenames[a])
			thetype = a;
	}
	var name = prompt('Name for new object?', objinterface.parentscorrespondingprop.objecttypename + numberofobjects[thetype]);
	if (name == null) return;
	for (a=0; a < preliminaryobjects.length; a++)
	{
		if (preliminaryobjects[a].name == name)
		{
			alert("An object already exists with the same name.");
			return;
		}
	}
	var thisone = preliminaryobjects.length;
	var objlist = document.getElementById('createdobjectlist');
	preliminaryobjects[thisone] = new Object();
	preliminaryobjects[thisone].name = name;
	preliminaryobjects[thisone].active = false;
	preliminaryobjects[thisone].type = thetype;
	
	preliminaryobjects[thisone].obj = objinterface.getcurrentinstance();

	objlist.options[objlist.options.length] = new Option(preliminaryobjects[thisone].name,thisone,false,true);
	objlist.options[objlist.options.length-1].selected = true;
	
	numberofobjects[thetype]++;
}

ObjectInterface.retrievevariable = function(objinterface) {
	var objlist = document.getElementById('createdobjectlist');
	var thedisplayname = objinterface.parentscorrespondingprop.displayname.toLowerCase(), predicate = 'a ';
	if (thedisplayname.substr(0,1).match(/[aeiou]/)) predicate = 'an ';
	
	var name = '';
	if (objlist.selectedIndex != -1 && creatableobjecttypenames[preliminaryobjects[parseInt(objlist.options[objlist.selectedIndex].value)].type] == objinterface.parentscorrespondingprop.objecttypename)
		name = preliminaryobjects[parseInt(objlist.options[objlist.selectedIndex].value)].name;
	name = prompt('Copy from what object? (Must be ' + predicate + thedisplayname + ')',name);
	if (name == null) return;
	var thisone = preliminaryobjects.length;
	for (var a=0; a < preliminaryobjects.length; a++)
	{
		if (preliminaryobjects[a].name == name)
		{
			if (editingobjnum == a) {
				alert('You cannot copy that object because it is currently being edited.');
				return;
			}
			else if (creatableobjecttypenames[preliminaryobjects[a].type] != objinterface.parentscorrespondingprop.objecttypename) {
				alert('You cannot copy that object because it is not ' + predicate + thedisplayname + '.');
				return;
			}
			objinterface.setcurrentinstance(preliminaryobjects[a].obj);
			break;
		}
	}
	if (a == preliminaryobjects.length)
		alert('That object does not exist in the Created Objects list.');
}

function createobject(_editing)
{
	var neweditingobjnum;
	if (_editing) {
		neweditingobjnum = document.controls.createdobjectlist.selectedIndex;
		if (neweditingobjnum == -1) {
			alert("Please first select an object to edit from the Created Objects list.");
			return;
		}
	}

	if (creatingobject) {
		var msg = "Proceeding will cancel the object you are already creating.";
		if (editing)
			msg = "Proceeding will cancel any changes you have made to the current object.";
		if (confirm(msg))
			currentiface.cancel();
		else
			return;
	}

	if (_editing) editing=true;
	else editing = false;

	if (editing)
	{
		editingobjnum = document.controls.createdobjectlist.options[neweditingobjnum].value;
		objtype = preliminaryobjects[editingobjnum].type;
	}
	else
	{
		objtype = document.controls.availableobjectlist.selectedIndex;
		if (objtype == -1) {
			alert("Please first select a type of object to create from the Available Objects list.");
			return;
		}
	}
	
	creatingobject = true;

	var defaultname = creatableobjecttypenames[objtype] + numberofobjects[objtype];
	if (editing) defaultname = preliminaryobjects[editingobjnum].name;

	if (!editing)
	{
		editingobjnum = preliminaryobjects.length;
		preliminaryobjects[editingobjnum] = new Object();
		preliminaryobjects[editingobjnum].active = false;
		preliminaryobjects[editingobjnum].type = objtype;
		if (ObjectInterface.objecttypes[creatableobjecttypenames[objtype]].type == ObjectType.TYPE_OBJECT)
			preliminaryobjects[editingobjnum].obj = new Object();
		else if (ObjectInterface.objecttypes[creatableobjecttypenames[objtype]].type == ObjectType.TYPE_ARRAY)
			preliminaryobjects[editingobjnum].obj = new Array();
	}

	preliminaryobjects[editingobjnum].obj.___name = defaultname;
	currentiface = new ObjectInterface(new ObjectProperty(creatableobjecttypenames[objtype], '', creatableobjectdisplaynames[objtype]), preliminaryobjects[editingobjnum].obj, '_0', true);
	document.getElementById('objectsfieldset').appendChild(currentiface.fieldset);

	if (currentiface.tofocus)
		currentiface.tofocus.focus();
	currentiface.oldcancel = currentiface.cancel;
	currentiface.cancel = function() {
		if (!editing) {
			removepreliminaryobject(editingobjnum);
		}
		this.oldcancel();
		creatingobject = false;
		editing = false;
		document.getElementById('availableobjectlist').focus();
	}
	currentiface.oldcomplete = currentiface.complete;
	currentiface.complete = function() {
		var thename = new Object();
		this.retrieveProperty(new ObjectProperty('string','___name','Name',''), thename);
		thename = thename.___name;
		for (a=0; a < preliminaryobjects.length; a++)
		{
			if (a != editingobjnum && preliminaryobjects[a].name == thename)
			{
				alert("An object already exists with the same name.");
				return;
			}
		}
		this.oldcomplete();
		preliminaryobjects[editingobjnum].name = thename;
		if (!editing)
		{
			var objlist = document.getElementById('createdobjectlist');
			objlist.options[objlist.options.length] = new Option(preliminaryobjects[editingobjnum].name,editingobjnum,false,true);
			objlist.focus();
		}
		else
		{
			var objlist = document.getElementById('createdobjectlist');
			for (var a=0; a < objlist.options.length; a++)
			{
				if (parseInt(objlist.options[a].value) == editingobjnum)
				{
					objlist.options[a].text = thename;
				}
			}
		}
		numberofobjects[preliminaryobjects[editingobjnum].type]++;
		creatingobject = false;
		editing = false;
		document.getElementById('availableobjectlist').focus();
	}

	ObjectInterface.fixieformbug();
}

function removeobject()
{
	var objlist = document.getElementById('createdobjectlist'), activeobjlist = document.getElementById('activeobjectlist');
	var optionnum = objlist.selectedIndex;
	if (optionnum == -1) {
		alert("Please first select an object to remove from the Created Objects list.\nIf you want to delete an object in the scene, you must first remove it from the scene with the <- button.");
		return;
	}
	var objnum = parseInt(objlist.options[optionnum].value);
	
	if (!confirm("Permanently delete " + preliminaryobjects[objnum].name + "?")) return;
	
	//numberofobjects[preliminaryobjects[objnum].type]--;

	if (editing && editingobjnum == objnum)
		currentiface.cancel();
	
	objlist.options[optionnum] = null;
	removepreliminaryobject(objnum);
	
	if (objlist.options.length > optionnum)
		objlist.options[optionnum].selected = true;
	else if (objlist.options.length > 0)
		objlist.options[optionnum-1].selected = true;

}

function removepreliminaryobject(which) {
	var objlist = document.getElementById('createdobjectlist'), activeobjlist = document.getElementById('activeobjectlist');
	for (var a=which; a < preliminaryobjects.length-1; a++)
		preliminaryobjects[a] = preliminaryobjects[a+1];
	if (editingobjnum > which) editingobjnum--;
	preliminaryobjects.length--;
	for (var a=0; a < objlist.options.length; a++)
		if (parseInt(objlist.options[a].value) > which)
			objlist.options[a].value--;
	for (var a=0; a < activeobjlist.options.length; a++)
		if (parseInt(activeobjlist.options[a].value) > which)
			activeobjlist.options[a].value--;
}

function duplicateobject()
{
	var objlist = document.getElementById('createdobjectlist');
	var optionnum = objlist.selectedIndex;
	if (optionnum == -1) {
		alert("Please first select an object to duplicate from the Created Objects list.\nIf you want to duplicate an object in the scene, you must first remove it from the scene with the <- button.");
		return;
	}
	var objnum = parseInt(objlist.options[optionnum].value);
	
	var thisone = preliminaryobjects.length;
	preliminaryobjects[thisone] = new Object();
	preliminaryobjects[thisone].name = prompt("Name for the duplicate object?", preliminaryobjects[objnum].name + '_copy');
	if (preliminaryobjects[thisone].name == null)
	{
		preliminaryobjects.length--;
		return;
	}
	for (a=0; a < preliminaryobjects.length; a++)
	{
		if (a != thisone && preliminaryobjects[a].name == preliminaryobjects[thisone].name)
		{
			alert("An object already exists with the same name.");
			preliminaryobjects.length--;
			return;
		}
	}
	preliminaryobjects[thisone].active = false;
	preliminaryobjects[thisone].type = preliminaryobjects[objnum].type;
	
	preliminaryobjects[thisone].obj = ObjectInterface.copyobj(preliminaryobjects[objnum].obj, creatableobjecttypenames[preliminaryobjects[thisone].type]);
	
	objlist.options[objlist.options.length] = new Option(preliminaryobjects[thisone].name,thisone,false,true);
	objlist.options[objlist.options.length-1].selected = true;
}

function activateobject()
{
	var objlist = document.getElementById('createdobjectlist'), activeobjlist = document.getElementById('activeobjectlist');
	var optionnum = objlist.selectedIndex;
	if (optionnum == -1) {
		alert("Please first select an object from the Created Objects list to insert into the scene.");
		return;
	}
	var objnum = parseInt(objlist.options[optionnum].value);

	if (editing && editingobjnum == objnum)
	{
		alert('You cannot add that object to the scene because it is currently being edited.');
		return;
	}

	if (preliminaryobjects[objnum].type > scenelevelobjectindexcutoff)
	{
		var msg = 'That is not a scene-level object. Only the following objects can be put into a scene:\n';
		for (var a=0; a <= scenelevelobjectindexcutoff; a++)
			msg += ' \t' + creatableobjectdisplaynames[a] + '\n';
		alert(msg);
		return;
	}

	if (creatableobjecttypenames[preliminaryobjects[objnum].type] == 'camera')
		++numcameras;
		
	preliminaryobjects[objnum].active = true;
	
	objlist.options[optionnum] = null;

	if (objlist.options.length > optionnum)
		objlist.options[optionnum].selected = true;
	else if (objlist.options.length > 0)
		objlist.options[optionnum-1].selected = true;

	activeobjlist.options[activeobjlist.options.length] = new Option(preliminaryobjects[objnum].name,objnum,false,true);
}
function deactivateobject()
{
	var objlist = document.getElementById('createdobjectlist'), activeobjlist = document.getElementById('activeobjectlist');
	var optionnum = activeobjlist.selectedIndex;
	if (optionnum == -1) {
		alert("Please first select an object from the Objects In Scene list to remove from the scene.");
		return;
	}
	var objnum = parseInt(activeobjlist.options[optionnum].value);

	if (creatableobjecttypenames[preliminaryobjects[objnum].type] == 'camera')
		--numcameras;
	
	preliminaryobjects[objnum].active = false;
	
	activeobjlist.options[optionnum] = null;

	if (activeobjlist.options.length > optionnum)
		activeobjlist.options[optionnum].selected = true;
	else if (activeobjlist.options.length > 0)
		activeobjlist.options[optionnum-1].selected = true;

	objlist.options[objlist.options.length] = new Option(preliminaryobjects[objnum].name,objnum,false,true);
}

//================= workspace option functions ======================

var loadingworkspace = false, thetextarea;
function saveworkspace()
{
	if (creatingobject)
	{
		alert('You cannot save a workspace while an object is being created or edited.');
		return;
	}
	if (preliminaryobjects.length == 0)
	{
		alert('No objects have been created.');
		return;
	}
	var outputstring = '[';
	for (var a=0; a < preliminaryobjects.length; a++)
	{
		outputstring += '{name:"' + ObjectInterface.escapestring(preliminaryobjects[a].name) + '",active:' + preliminaryobjects[a].active;
		outputstring += ',type:' + preliminaryobjects[a].type + ',obj:';
		outputstring += ObjectInterface.getcode(preliminaryobjects[a].obj, creatableobjecttypenames[preliminaryobjects[a].type]);
		outputstring += '}';
		if (a != preliminaryobjects.length-1)
			outputstring += ',';
	}
	outputstring += ']';
	
	loadingworkspace = false;
	document.getElementById('output').style.display='block';
	while(document.getElementById('outputdata').childNodes.length > 0)
		document.getElementById('outputdata').removeChild(document.getElementById('outputdata').lastChild);
	document.getElementById('outputdata').appendChild(document.createTextNode('Save this text. Do not insert any line breaks. When you want to load the workspace, cut and paste this into the box given by the "Load Workspace" button.'));
	thetextarea = document.createElement('textarea');
	thetextarea.value = outputstring;
	document.getElementById('outputdata').appendChild(thetextarea);
	document.getElementById('outputdata').style.display='block';
	thetextarea.focus();
}

function closeworkspace(noalerts)
{
	if (preliminaryobjects.length == 0) {
		alert('No objects have been created.');
		return;
	}
	if (noalerts){}
	else if (!confirm('Are you sure you want to destroy all created objects?'))
		return;
	var objlist = document.getElementById('createdobjectlist'), activeobjlist = document.getElementById('activeobjectlist');
	if (creatingobject)
		currentiface.cancel();
	objlist.options.length = activeobjlist.options.length = 0;
	preliminaryobjects.length = 0;
	numcameras = 0;
}

function loadworkspace()
{
	loadingworkspace = true;
	document.getElementById('output').style.display='block';
	while(document.getElementById('outputdata').childNodes.length > 0)
		document.getElementById('outputdata').removeChild(document.getElementById('outputdata').lastChild);
	document.getElementById('outputdata').appendChild(document.createTextNode('Enter the previously saved text into this text box, and then click "OK" to load the workspace.'));
	thetextarea = document.createElement('textarea');
	thetextarea.value = '';
	document.getElementById('outputdata').appendChild(thetextarea);
	document.getElementById('outputdata').style.display='block';
	thetextarea.focus();
}
function loadworkspacetext(inputstring)
{
	var objlist = document.getElementById('createdobjectlist'), activeobjlist = document.getElementById('activeobjectlist');
	var newpreliminaryobjects;
	try {
		eval('newpreliminaryobjects = '+inputstring);
	}
	catch (err) {
		alert('The workspace could not be loaded:\n' + err.message);
		return;
	}
	var oldlength = preliminaryobjects.length;
	for (a=0; a < newpreliminaryobjects.length; a++)
	{
		var newindex = a+oldlength;
		preliminaryobjects[newindex] = newpreliminaryobjects[a];
		var thelist;
		if (preliminaryobjects[newindex].active)
		{
			if (creatableobjecttypenames[preliminaryobjects[newindex].type] == 'camera')
				numcameras++;
			thelist = activeobjlist;
		}
		else
			thelist = objlist;
		thelist.options[thelist.options.length] = new Option(preliminaryobjects[newindex].name,a,a==0,a==0);
	}
}

function hideinputoutputbox()
{
	if (loadingworkspace)
	{
		if (thetextarea.value != '')
			loadworkspacetext(thetextarea.value);
		loadingworkspace = false;
	}
	document.getElementById('output').style.display='none';
}

function loadpreset()
{
	if (preliminaryobjects.length != 0)
	{
		if (!confirm('Loading a preset workspace will close the current workspace and destroy all created objects. Are you sure you want to continue?'))
			return;
		closeworkspace(true);
	}
	loadworkspacetext(presets[document.getElementById('presetlist').selectedIndex].text);
}

// some predefined text to cut down on file size here...
var _na = ":new Vector(";
var _nc = ":new Color(";
var _x = ",parentsprop:ObjectInterface.objecttypes.transformationlist.propertylist[";
var _y = ",parentsprop:ObjectInterface.objecttypes.objectlist.propertylist[";
var _z = ",inversed:false,castshadows:true";
var _w = "0,1,0),zoomfactor:1,aspectratio:1.3333333333333332,handedness:0}";
presets = new Array();
presets[0] = new Object();
presets[0].name = 'Simple Camera and Light';
presets[0].text = "[{name:\"Basic Camera\",active:true,type:0,obj:{cameratypechoice:0,simplecamera:{pos"+_na+"0,0,-5),lookat"+_na+"0,0,0),sky"+_na+"0,1,0),zoomfactor:1,aspectratio:1.33333333333333,handedness:0}}},{name:\"Upper Left Light Source\",active:true,type:1,obj:{pos"+_na+"-10,10,-10),color"+_nc+"1,1,1)}}]";
presets[1] = new Object();
presets[1].name = 'Texture Workshop';
presets[1].text = "[{name:\"Head-On Camera\",active:true,type:0,obj:{cameratypechoice:0,simplecamera:{pos"+_na+"0,5,0),lookat"+_na+"0,0,0),sky"+_na+"0,0,1),zoomfactor:1,aspectratio:1.33333333333333,handedness:0}}},{name:\"Perspective Camera\",active:false,type:0,obj:{cameratypechoice:0,simplecamera:{pos"+_na+"0,5,-5),lookat"+_na+"0,0,0),sky"+_na+"0,1,0),zoomfactor:1,aspectratio:1.33333333333333,handedness:0}}},{name:\"Textured Plane\",active:true,type:4,obj:{norm"+_na+"0,1,0),dist:0"+_z+",texture:{colorpigmentchoice:1,finish:{specular:0,glossiness:10,reflection:0,diffuse:0,ambient:1},overridesparent:false,pigment:{func:\"/* Work on your texture here */\\n\\nvar noiseval = noise(pos);\\nreturn new Color(noiseval,noiseval,noiseval); /*red,green,blue values from 0 to 1*/\"}}}}]";
presets[2] = new Object();
presets[2].name = 'Original JS Raytracer Scene';
presets[2].text = "[{name:\"Head-On Camera\",active:true,type:0,obj:{cameratypechoice:0,simplecamera:{pos"+_na+"0,0.3,-4),lookat"+_na+"0,0,0),sky"+_na+_w+"}},{name:\"Red Chrome Sphere\",active:true,type:2,obj:{pos"+_na+"0,0,0),rad:1"+_z+",texture:{colorpigmentchoice:0,finish:{specular:0.6,reflection:0.5,diffuse:0.8,ambient:0.2},color"+_nc+"0.5,0.25,0.25)}}},{name:\"Checkered Plane\",active:true,type:4,obj:{norm"+_na+"0,1,0),dist:-1"+_z+",texture:{colorpigmentchoice:1,pigment:{func:\"var checkerval = checker(Vector.add(pos,new Vector(0,0.5,0)));\\n/* (added 0.5 to avoid sampling checker pattern on X-Z plane) */\\nreturn new Color(checkerval,checkerval,checkerval);\"}}}},{name:\"Main Light Source\",active:true,type:1,obj:{pos"+_na+"-7,10,-10),color"+_nc+"1,1,1)}},{name:\"Angled Camera\",active:false,type:0,obj:{cameratypechoice:0,simplecamera:{pos"+_na+"2.2,1.6,-3.1),lookat"+_na+"0,0,0),sky"+_na+"0,1,0),zoomfactor:1,aspectratio:1.33333333333333,handedness:0}}}]";
presets[3] = new Object();
presets[3].name = '"Primitive Convention"';
presets[3].text = "[{name:\"Camera\",active:true,type:0,obj:{cameratypechoice:0,transformation:{components:[{object:{amnt"+_na+"0.5,0,-2)}"+_x+"1]},{object:{axis:1,amount:-10}"+_x+"0]}],inversed:false},simplecamera:{pos"+_na+"0,4,-5),lookat"+_na+"0,0,4),sky"+_na+_w+"}},{name:\"Ground Plane\",active:true,type:4,obj:{norm"+_na+"0,1,0),dist:0,inversed:false,castshadows:false,texture:{colorpigmentchoice:0,finish:{specular:0,glossiness:10,reflection:0,diffuse:0.8,ambient:0},overridesparent:false,color"+_nc+"0.4,1,0.9)}}},{name:\"Cone\",active:true,type:6,obj:{end1"+_na+"0,0,0),rad1:1,end2"+_na+"0,3,0),rad2:0,open:false"+_z+",transformation:{components:[{object:{amnt"+_na+"-1.5,0,-2.5)}"+_x+"1]}],inversed:false},texture:{colorpigmentchoice:0,finish:{specular:1,glossiness:10,reflection:0.4,diffuse:0.8,ambient:0},overridesparent:false,color"+_nc+"0.1,0.2,0.8)}}},{name:\"Sphere\",active:true,type:2,obj:{pos"+_na+"-1.7,1.5,1),rad:1.5"+_z+",texture:{colorpigmentchoice:0,finish:{specular:1,glossiness:20,reflection:0.7,diffuse:0.8,ambient:0},overridesparent:false,color"+_nc+"0.2,0.9,0.3)}}},{name:\"Box\",active:true,type:3,obj:{v1"+_na+"-0.5,0,-0.5),v2"+_na+"0.5,1,0.5)"+_z+",transformation:{components:[{object:{amnt"+_na+"1.5,1.5,1)}"+_x+"2]},{object:{axis:1,amount:-20}"+_x+"0]},{object:{amnt"+_na+"2.5,0,-2.3)}"+_x+"1]}],inversed:false},texture:{colorpigmentchoice:0,finish:{specular:0.5,glossiness:5,reflection:0.6,diffuse:0.8,ambient:0},overridesparent:false,color"+_nc+"0.5,0.2,1)}}},{name:\"Cylinder\",active:true,type:5,obj:{end1"+_na+"0.5,1,5),end2"+_na+"4,1,0.5),rad:1,open:false"+_z+",texture:{colorpigmentchoice:0,finish:{specular:1,glossiness:15,reflection:0.6,diffuse:0.8,ambient:0},overridesparent:false,color"+_nc+"0.15,0.7,0.8)}}},{name:\"High Qual Light 1\",active:false,type:1,obj:{pos"+_na+"10,10,-5),color"+_nc+"0.3,1,1),arealight:{use:true,dim1"+_na+"0,0,3),dim2"+_na+"0,3,0),minsampledepth:2,maxsampledepth:3,jitteramnt:1.5},spotlight:{use:true,pointat"+_na+"0,0,1),outerangle:25,innerangle:18},transformation:{inversed:false}}},{name:\"High Qual Light 2\",active:false,type:1,obj:{pos"+_na+"-5,15,-10),color"+_nc+"1,0.3,0.3),arealight:{use:true,dim1"+_na+"3,0,0),dim2"+_na+"0,0,3),minsampledepth:2,maxsampledepth:3,jitteramnt:1.5},spotlight:{use:true,pointat"+_na+"0,0,1),outerangle:25,innerangle:18},transformation:{inversed:false}}},{name:\"Low Qual Light 2\",active:true,type:1,obj:{pos"+_na+"-5,15,-10),color"+_nc+"1,0.3,0.3),arealight:{use:false,dim1"+_na+"3,0,0),dim2"+_na+"0,0,3),minsampledepth:2,maxsampledepth:3,jitteramnt:1.5},spotlight:{use:false,pointat"+_na+"0,0,1),outerangle:25,innerangle:18},transformation:{inversed:false}}},{name:\"Low Qual Light 1\",active:true,type:1,obj:{pos"+_na+"10,10,-5),color"+_nc+"0.3,1,1),arealight:{use:false,dim1"+_na+"0,0,3),dim2"+_na+"0,3,0),minsampledepth:2,maxsampledepth:3,jitteramnt:1.5},spotlight:{use:false,pointat"+_na+"0,0,1),outerangle:25,innerangle:18},transformation:{inversed:false}}}]";
presets[4] = new Object();
presets[4].name = 'Flower';
var _a="{object:{posobjects:[{object:{pos"+_na+"0,0,0),rad:1"+_z+"}"+_y+"0]}],negobjects:[{object:{pos"+_na+"0.02,-0.02,0),rad:1"+_z+",transformation:{components:[{object:{amnt"+_na+"1.02,1,1.2)}"+_x+"2]}],inversed:false}}"+_y+"0]}]"+_z+",transformation:{components:[{object:{amnt"+_na+"1.5,0,0)}"+_x+"1]},{object:{amnt"+_na+"1.2,1,1)}"+_x+"2]}";
var _b=",{object:{axis:1,amount:";
var _c="}"+_x+"0]}],inversed:false}}"+_y+"7]},";
var _d=",{object:{amnt"+_na+"-0.3,0,0)}"+_x+"1]},{object:{axis:2,amount:-10}"+_x+"0]},{object:{amnt"+_na+"0.3,0,0)}"+_x+"1]}";
presets[4].text = "[{name:\"High Qual Light\",active:false,type:1,obj:{pos"+_na+"70,100,-100),color"+_nc+"1,1,1),arealight:{use:true,dim1"+_na+"0,0,10),dim2"+_na+"0,10,0),minsampledepth:0,maxsampledepth:2,jitteramnt:1},spotlight:{use:false,pointat"+_na+"0,6,0),outerangle:35,innerangle:30}}},{name:\"Flower\",active:true,type:7,obj:{objects:[{object:{objects:["+_a+"],inversed:false}}"+_y+"7]},"+_a+_b+"45"+_c+_a+_b+"90"+_c+_a+_b+"135"+_c+_a+_b+"180"+_c+_a+_b+"225"+_c+_a+_b+"270"+_c+_a+_b+"315}"+_x+"0]}],inversed:false}}"+_y+"7]}],testboundingbox:true"+_z+",texture:{colorpigmentchoice:0,finish:{specular:0.3,glossiness:30,reflection:0,diffuse:0.8,ambient:0.2},overridesparent:true,color"+_nc+"1,0.75,0)}}"+_y+"5]},{object:{objects:["+_a+_d+"],inversed:false}}"+_y+"7]},"+_a+_d+_b+"45"+_c+_a+_d+_b+"90"+_c+_a+_d+_b+"135"+_c+_a+_d+_b+"180"+_c+_a+_d+_b+"225"+_c+_a+_d+_b+"270"+_c+_a+_d+_b+"315"+_c+_a+_d+_b+"135}"+_x+"0]}],inversed:false}}"+_y+"7]}],testboundingbox:true"+_z+",transformation:{components:[{object:{axis:1,amount:22.5}"+_x+"0]}],inversed:false},texture:{colorpigmentchoice:0,finish:{specular:0.3,glossiness:30,reflection:0,diffuse:0.8,ambient:0.2},overridesparent:true,color"+_nc+"1.2,0.5,0)}}"+_y+"5]},{object:{pos"+_na+"0,0,0),rad:0.6"+_z+",texture:{colorpigmentchoice:1,overridesparent:true,pigment:{func:\"var numrepstheta = 8; \\nvar numrepsphi = 30; \\nvar val = (Math.sin(Math.atan2(pos.z,pos.x)*numrepstheta)*Math.sin(Math.atan2(pos.y,Math.sqrt(pos.x*pos.x+pos.z*pos.z))*numrepsphi))*.5+.5; \\nreturn Color.scalar(new Color(1,1,.2),Math.sqrt(val)*.8+.3); /*red,green,blue values from 0 to 1*/\"}}}"+_y+"0]},{object:{objects:[{object:{posobjects:[{object:{pos"+_na+"0,0,0),rad:0.7"+_z+",transformation:{components:[{object:{amnt"+_na+"1,1.7,1)}"+_x+"2]}],inversed:false}}"+_y+"0]}],negobjects:[{object:{pos"+_na+"0,4.1,0),rad:4"+_z+"}"+_y+"0]}]"+_z+"}"+_y+"7]},{object:{end1"+_na+"0,0,0),end2"+_na+"0,-1.5,0),rad:0.1,open:true"+_z+"}"+_y+"3]},{object:{end1"+_na+"0,-1.5,0),end2"+_na+"0.29,-3,0),rad:0.1,open:true"+_z+"}"+_y+"3]},{object:{end1"+_na+"0.29,-3,0),end2"+_na+"0.7,-4.5,0),rad:0.1,open:true"+_z+"}"+_y+"3]},{object:{pos"+_na+"0,-1.5,0),rad:0.1"+_z+"}"+_y+"0]},{object:{pos"+_na+"0.29,-3,0),rad:0.1"+_z+"}"+_y+"0]}],testboundingbox:true"+_z+",texture:{colorpigmentchoice:0,overridesparent:true,color"+_nc+"0.2,0.4,0.1)}}"+_y+"5]}],testboundingbox:false"+_z+",transformation:{components:[{object:{amnt"+_na+"0,7,0)}"+_x+"1]},{object:{axis:2,amount:-19}"+_x+"0]},{object:{amnt"+_na+"-1.4,1,0)}"+_x+"1]}],inversed:false},texture:{colorpigmentchoice:0,finish:{specular:0,glossiness:10,reflection:0,diffuse:1.1,ambient:0.3},overridesparent:false,color"+_nc+"1,1,1)}}},{name:\"Pot\",active:true,type:9,obj:{posobjects:[{object:{end1"+_na+"0,0,0),rad1:1,end2"+_na+"0,4,0),rad2:1.7,open:false"+_z+"}"+_y+"4]}],negobjects:[{object:{end1"+_na+"0,0.1,0),rad1:0.95,end2"+_na+"0,4.1,0),rad2:1.65,open:true,inversed:false,castshadows:false}"+_y+"4]}]"+_z+",texture:{colorpigmentchoice:1,finish:{specular:0,glossiness:10,reflection:0,diffuse:0.8,ambient:0.2},overridesparent:true,pigment:{func:\"var xpos = pos.x + noise(Vector.mult(pos,new Vector(1,1/2,1/2)))*3;\\nvar val = Math.cos((xpos-Math.floor(xpos))*2*Math.PI)*.5+.5;\\nval = .5+val*.5;\\nvar col1 = new Color(.7,.5,.15), col2 = new Color(.72,.72,.85);\\nreturn Color.add(col1, Color.scalar(Color.add(col2, col1.neg()), val));\\n\",transformation:{components:[{object:{amnt"+_na+"0.45,0.45,0.45)}"+_x+"2]}],inversed:false}}}}},{name:\"Dirt\",active:true,type:6,obj:{end1"+_na+"0,0.05,0),rad1:0.975,end2"+_na+"0,3.8,0),rad2:1.66,open:false,inversed:false,castshadows:false,texture:{colorpigmentchoice:1,finish:{specular:0,glossiness:0,reflection:0,diffuse:0.8,ambient:0.2},overridesparent:true,pigment:{func:\"var val = noise(Vector.scalar(pos,15));\\nreturn Color.scalar(new Color(.7,.5,.3),val*.8+.2);\"}}}},{name:\"Low Qual Light\",active:true,type:1,obj:{pos"+_na+"70,100,-100),color"+_nc+"1,1,1),arealight:{use:false,dim1"+_na+"0,0,2),dim2"+_na+"0,2,0),minsampledepth:0,maxsampledepth:2,jitteramnt:1},spotlight:{use:false,pointat"+_na+"0,6,0),outerangle:35,innerangle:30}}},{name:\"WoodenFlatThing\",active:true,type:4,obj:{norm"+_na+"0,1,0),dist:0,inversed:false,castshadows:false,texture:{colorpigmentchoice:1,finish:{specular:0.9,glossiness:10,reflection:0,diffuse:0.8,ambient:0.2},overridesparent:true,pigment:{func:\"var n=noise(new Vector(pos.x/.06 + noise(Vector.scalar(pos,.3))*30,pos.y/.06,pos.z/3));\\nreturn new Color(.3+.3*n,.1+.4*n,0);\",transformation:{components:[{object:{axis:1,amount:23}"+_x+"0]}],inversed:false}}}}},{name:\"Camera\",active:true,type:0,obj:{cameratypechoice:0,simplecamera:{pos"+_na+"0,11,-6),lookat"+_na+"0,6,0),sky"+_na+_w+"}}]";

onload = function() {
	for (var a=0; a < presets.length; a++)
	{
		var opt = document.createElement('option');
		opt.appendChild(document.createTextNode(presets[a].name));
		document.getElementById('presetlist').appendChild(opt);
	}
	document.getElementById('presetlist').style.visibility = 'hidden';
	document.getElementById('presetlist').style.visibility = 'visible'; // fix ie 5 rendering error
}

/*

var numrepstheta = 8;
var numrepsphi = 30;
var val = (Math.sin(Math.atan2(pos.z,pos.x)*numrepstheta)*Math.sin(Math.atan2(pos.y,Math.sqrt(pos.x*pos.x+pos.z*pos.z))*numrepsphi))*.5+.5;
return Color.scalar(new Color(1,1,.2),val*.9+.2); red,green,blue values from 0 to 1

var xpos = pos.x + noise(Vector.mult(pos,new Vector(1,1/2,1/2)))*3;
var val = Math.cos((xpos-Math.floor(xpos))*2*Math.PI)*.5+.5;
val = .5+val*.5;
var col1 = new Color(.7,.5,.15), col2 = new Color(.77,.77,.9);
return Color.add(col1, Color.scalar(Color.add(col2, col1.neg()), val));

*/