Index: sm/trunk-jpl/src/m/plot/gauge.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/gauge.js	(revision 22911)
+++ 	(revision )
@@ -1,52 +1,0 @@
-function gaugeInit(){ //{{{
-	/*
-		References:	https://bernii.github.io/gauge.js/
-	*/
-	// Convert arguments to options.
-	var args 		= Array.prototype.slice.call(arguments);
-	var options 	= new pairoptions(args);
-
-	// Recover option values.
-	var canvasId	= options.getfieldvalue('canvasId', 'sim-gauge-canvas');
-	var textFieldId	= options.getfieldvalue('textFieldId', 'gauge-text');
-	var	value 		= options.getfieldvalue('value', 0);
-	var max 		= options.getfieldvalue('max', 22);
-	var colors 		= options.getfieldvalue('colors', [[0.0, "#FFFF00"],
-													   [0.50, "#FF8000"],
-													   [1.0, "#FF0000"]]);
-	
-	// Construct associative array of options.
-	var opts = {
-		lines: 				12, 	// The number of lines to draw
-		angle: 				0,		// The length of each line
-		lineWidth: 			0.39,	// The line thickness
-		
-		pointer: {
-			length: 			0, 			// The radius of the inner circle
-			strokeWidth: 		0, 			// The rotation offset
-			color: 				'#000000' 	// Fill color
-		},
-		
-		limitMax: 			'false',	// If true, the pointer will not go past the end of the gauge
-		percentColors: 		colors,
-		strokeColor: 		'#E0E0E0',	// to see which ones work best for you
-		generateGradient: 	true
-	};
-	
-	// Create new gauge.
-	var newGauge = new Gauge(document.getElementById(canvasId)).setOptions(opts);
-	
-	// Bind text field to gauge.
-	newGauge.setTextField(document.getElementById(textFieldId));
-	
-	newGauge.textField.render = function(newGauge){
-    	return this.el.innerHTML = newGauge.displayedValue.toFixed(0);
-    };
-    
-	newGauge.maxValue 			= max;
-	newGauge.animationSpeed 	= 1;
-	newGauge.set(1);		// Somehow this prevents pointer from rendering on initialization.
-	newGauge.set(value);	// set actual value
-	
-	return newGauge;
-} //}}}
Index: sm/trunk-jpl/src/m/plot/webgl.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/webgl.js	(revision 22911)
+++ 	(revision )
@@ -1,654 +1,0 @@
-/*This is where we have all our webgl relevant functionality for the plotting routines: */
-//{{{ Canvas Initialization
-function initCanvas(options) {
-	//Initialize open Gl for each canvas and clear any previous animation handlers, once per plotmodel call:
-	canvas = document.getElementById(options.getfieldvalue('canvasid'));
-	
-	if (!canvas.initialized) {
-		if (!vesl.helpers.isEmptyOrUndefined(canvas.draw) && canvas.draw.handler !== 0)	{ window.cancelAnimationFrame(canvas.draw.handler); }
-		if (!vesl.helpers.isEmptyOrUndefined(canvas.animation) && canvas.animation.handler !== 0) { clearInterval(canvas.animation.handler); }
-		initWebGL(canvas, options);
-		drawCanvas(canvas);
-		
-		canvas.initialized = true;
-		
-		triggerStartEvent(canvas);
-	}
-	
-	return canvas;
-}
-function initWebGL(canvas, options) { //{{{
-	//Initialize canvas.gl on page load, reusing gl context on additional runs
-	var gl = canvas.gl;
-	
-	if (vesl.helpers.isEmptyOrUndefined(gl)) {
-		gl = GL.create({canvas: canvas});
-		gl.enable(gl.DEPTH_TEST); // Enable depth testing
-		gl.depthFunc(gl.LEQUAL); // Near things obscure far things
-		gl.enable(gl.BLEND); // Enable color blending/overlay
-		gl.enable(gl.CULL_FACE); // Enable face culling
-		gl.cullFace(gl.FRONT);
-		gl.shaders = loadShaders(gl, options.getfieldvalue('rootpath', '/canvas')); // Load shaders and store them in gl object.
-		gl.textures = {};
-		
-		// Add event listeners for canvas
-		var displayview = options.getfieldvalue('displayview', 'off') === 'on';
-		var displayzoom = options.getfieldvalue('displayzoom', 'off') === 'on';
-		var mc = new Hammer(canvas);	
-		mc.add(new Hammer.Pan({threshold: 0, pointers: 0}));
-		mc.add(new Hammer.Pinch({threshold: 0})).recognizeWith(mc.get('pan'));
-		mc.on('tap', function(ev) {onTap(ev, canvas);});
-		mc.on('panstart panmove', function(ev) {onPan(ev, canvas, displayview);});
-		mc.on('pinchstart pinchmove', function(ev) {onPinch(ev, canvas, displayview);});
-		canvas.addEventListener('mousewheel', function(ev) {onZoom(ev, canvas, displayzoom)}, false);
-		canvas.addEventListener('DOMMouseScroll', function(ev) {onZoom(ev, canvas, displayzoom)}, false);
-		
-		//Add persistent state variables
-		canvas.nodes = {};
-		canvas.octrees = {};
-		canvas.unitNode = {};
-		canvas.unitData = {};
-		canvas.unitMovieData = {};
-		
-		canvas.gl = gl;
-		canvas.assetsPath = options.getfieldvalue('rootpath', '/canvas');
-		canvas.id = options.getfieldvalue('canvasid', '.sim-canvas');
-		canvas.selector = $('#' + canvas.id);
-		canvas.textcanvas = null;
-		canvas.overlaycanvas = null;
-		canvas.permalinkUsed = false;
-		
-		typedArraySliceSupport();
-	}
-	
-	if (options.getfieldvalue('clf', 'on') === 'on') {
-		// Add context state variables
-		canvas.render 				= options.getfieldvalue('render', {});
-		canvas.controlSensitivity 	= options.getfieldvalue('controlsensitivity', 1);
-		canvas.overlayHandlers 		= {};
-		
-		var backgroundcolor = new RGBColor(options.getfieldvalue('backgroundcolor', 'lightcyan'));
-		
-		if (backgroundcolor.ok) { 
-			canvas.backgroundcolor = 	[
-											backgroundcolor.r / 255.0, 
-											backgroundcolor.g / 255.0, 
-											backgroundcolor.b / 255.0, 
-											1.0
-										]; 
-		} else { 
-			throw Error(sprintf('s%s%s\n','initWebGL error message: cound not find out background color for current canvas ', canvas)); 
-		}
-		
-		//Property intiialization, using values from options first, then from default values.
-		var atmosphere = options.getfieldvalue('atmosphere', {});
-		canvas.atmosphere = { 														//Default Values
-			wavelength_r: defaultFor(atmosphere.wavelength_r, 			0.65), 		//0.65		Red wavelength (micrometers)
-			wavelength_g: defaultFor(atmosphere.wavelength_g, 			0.57),		//0.57		Green wavelength (micrometers)
-			wavelength_b: defaultFor(atmosphere.wavelength_b, 			0.475),		//0.475		Green wavelength (micrometers)
-			eSun: 	defaultFor(atmosphere.eSun, 						100.0),		//20.0		Sun intensity	
-			kRayleigh: defaultFor(atmosphere.kRayleigh, 				0.0025),	//0.0025	Rayleigh scattering amount
-			kMie: defaultFor(atmosphere.kMie, 							0.000), 	//0.01		Mie scattering amount
-			g: defaultFor(atmosphere.g, 								-0.99),		//-0.99		Mie phase asymmetry/direction factor
-			hdr_exposure: defaultFor(atmosphere.hdr_exposure, 			0.8),		//0.8		High Dynamic Range Exposure
-			scaleHeight: defaultFor(atmosphere.scaleHeight, 			1.25), 		//1.025		Scale of height of atmosphere to earth radius.
-			scaleDepth: defaultFor(atmosphere.scaleDepth, 				0.25), 		//0.25		Percentage altitude at which the atmosphere's average density is found
-			a: defaultFor(atmosphere.a, 								-0.00287),	//-0.00287	Scaling constant a
-			b: defaultFor(atmosphere.b, 								0.459),		//0.459		Scaling constant b
-			c: defaultFor(atmosphere.c, 								3.83),		//3.83		Scaling constant c
-			d: defaultFor(atmosphere.d, 								-6.80),		//-6.80		Scaling constant d
-			e: defaultFor(atmosphere.e, 								3.6),		//5.25		Scaling constant e. Lower when increasing atmosphere scale.
-			attenuation: defaultFor(atmosphere.attenuation, 			0.5)		//0.5		Strength of atmospheric scattering on ground shading.
-		};
-		updateAtmosphereParameters(canvas);
-			
-		var animation = options.getfieldvalue('movies', {});
-		canvas.animation = {
-			frame: defaultFor(animation.frame, 							0),
-			play: defaultFor(animation.play, 							true),
-			increment: defaultFor(animation.increment, 					true),
-			fps: defaultFor(animation.fps, 								4),
-			interval: defaultFor(animation.interval, 					1000 / defaultFor(animation.fps, 4)),
-			loop: defaultFor(animation.loop, 							true),
-			handler: defaultFor(animation.handler, 						0)
-		}
-		var brush = options.getfieldvalue('brush', {});
-		canvas.brush = {
-			enabled: defaultFor(brush.enabled, 							false),
-			strength: defaultFor(brush.strength, 						0.075),
-			falloff: defaultFor(brush.falloff, 							0.5),
-			hit: defaultFor(brush.hit, 									{})
-		};
-		var camera = options.getfieldvalue('camera', {});
-		canvas.camera = {
-			position: defaultFor(camera.position, 						vec3.create()),
-			rotation: defaultFor(camera.rotation, 						quat.create()),
-			relativePosition: defaultFor(camera.relativePosition, 		vec3.create()),
-			direction: defaultFor(camera.direction, 					vec3.create()),
-			near: defaultFor(camera.near, 								1e3),
-			far: defaultFor(camera.far, 								1e10),
-			fov: defaultFor(camera.fov, 								45),
-			vMatrix: defaultFor(camera.vMatrix, 						mat4.create()),
-			pMatrix: defaultFor(camera.pMatrix, 						mat4.create()),
-			vpMatrix: defaultFor(camera.vpMatrix, 						mat4.create()),
-			vInverseMatrix: defaultFor(camera.vInverseMatrix, 			mat4.create()),
-			pInverseMatrix: defaultFor(camera.pInverseMatrix, 			mat4.create()),
-			vpInverseMatrix: defaultFor(camera.vpInverseMatrix, 		mat4.create()),
-			ready: defaultFor(camera.ready, 							false)
-		};
-		if (vesl.helpers.isEmptyOrUndefined(canvas.dataMarker)) {
-			var dataMarker = options.getfieldvalue('dataMarker', {});
-			
-			canvas.dataMarker = {
-				enabled: defaultFor(dataMarker.enabled, 				false),
-				element: defaultFor(dataMarker.element, 				$('#sim-data-marker-' + canvas.id)),
-				isTooltip: defaultFor(dataMarker.isTooltip,				false),
-				handlersReady: defaultFor(dataMarker.handlersReady,		false),
-				initialized: defaultFor(dataMarker.initialized,			false),
-				visible: defaultFor(dataMarker.visible,					false),
-				image: defaultFor(dataMarker.image, 					canvas.assetsPath + '/data-markers/data-marker.svg'),
-				size: defaultFor(dataMarker.size, 						[32, 32]),
-				font: defaultFor(dataMarker.font, 						''),
-				reposition: defaultFor(dataMarker.reposition, 			true)
-			};
-		}
-		var graph = options.getfieldvalue('graph', {});
-		canvas.graph = {
-			enabled: defaultFor(graph.enabled,							false),
-			id: defaultFor(graph.id,									'sim-graph'),
-			animated: defaultFor(graph.animated, 						false),
-			data: defaultFor(graph.data,								[]),
-			otherData: defaultFor(graph.otherData, 						{})
-		};
-		var draw = options.getfieldvalue('draw', {});
-		canvas.draw = {
-			ready: defaultFor(draw.ready, 								false),
-			handler: defaultFor(draw.handler, 							null)
-		};
-		var view = options.getfieldvalue('view', {});
-		canvas.view = {
-			position: defaultFor(view.position, 						[0.0, 0.0, 0.0]),
-			rotation: defaultFor(view.rotation, 						[0, 90]),
-			zoom: defaultFor(view.zoom, 								1.0),
-			zoomLimits: defaultFor(view.zoomLimits, 					[0.001, 100.0]),
-			lastZoom: defaultFor(view.lastZoom, 						1.0),
-			lightingBias: defaultFor(view.lightingBias, 				0.75),
-			azimuthLimits: defaultFor(view.azimuthLimits, 				[0, 360]),
-			elevationLimits: defaultFor(view.elevationLimits, 			[-180, 180]),
-			panningEnabled: defaultFor(view.panningEnabled, 			false),
-			preventDefaultOnPan: defaultFor(view.preventDefaultOnPan, 	false),
-			twod: defaultFor(view.twod, 								false)
-		};
-
-		// Override with parameters from URL, if any
-		vesl.ui.parsePermalinkCanvas(canvas);
-	}
-} //}}}
-function loadShaders(gl, assetsPath) { //{{{
-	var shaders = {};
-	// NOTE: Consider changing fromURL() to XMLHttpRequest with responseType = 'text' to avoid XML Parsing Error (shader files are not encoded as XML).
-	shaders.Colored = new GL.Shader.fromURL(assetsPath + '/shaders/Colored.vsh', assetsPath + '/shaders/Colored.fsh', null, gl);
-	shaders.ColoredDiffuse = new GL.Shader.fromURL(assetsPath + '/shaders/ColoredDiffuse.vsh', assetsPath + '/shaders/ColoredDiffuse.fsh', null, gl);
-	shaders.Textured = new GL.Shader.fromURL(assetsPath + '/shaders/Textured.vsh', assetsPath + '/shaders/Textured.fsh', null, gl);
-	shaders.TexturedDiffuse = new GL.Shader.fromURL(assetsPath + '/shaders/TexturedDiffuse.vsh', assetsPath + '/shaders/TexturedDiffuse.fsh', null, gl);
-	shaders.SkyFromSpace = new GL.Shader.fromURL(assetsPath + '/shaders/SkyFromSpace.vert', assetsPath + '/shaders/SkyFromSpace.frag', null, gl);
-	shaders.GroundFromSpace = new GL.Shader.fromURL(assetsPath + '/shaders/GroundFromSpace.vert', assetsPath + '/shaders/GroundFromSpace.frag', null, gl);
-	return shaders;
-} //}}}
-function initTexture(gl, imageSource) { //{{{
-	//Initialize textures, or load from memory if they already exist.
-	if (vesl.helpers.isEmptyOrUndefined(gl.textures[imageSource])) {
-		gl.textures[imageSource] = GL.Texture.fromURL(imageSource, {minFilter: gl.LINEAR_MIPMAP_LINEAR, magFilter: gl.LINEAR}, null, gl);
-	}
-	return gl.textures[imageSource];
-} //}}}
-function updateAtmosphereParameters(canvas) {
-	//Precalculate derived atmosphere shader parameters
-	//TODO: Find a better way to structure this
-	var atm = canvas.atmosphere;
-	atm.inv_wavelength4 = [1.0 / Math.pow(atm.wavelength_r, 4), 1.0 / Math.pow(atm.wavelength_g, 4), 1.0 / Math.pow(atm.wavelength_b, 4)];
-	atm.innerRadius = 6.371e6;
-	atm.innerRadius2 = atm.innerRadius * atm.innerRadius;
-	atm.outerRadius = atm.innerRadius * atm.scaleHeight;
-	atm.outerRadius2 = atm.outerRadius * atm.outerRadius;
-	atm.krESun = atm.kRayleigh * atm.eSun;
-	atm.kmESun = atm.kMie * atm.eSun;
-	atm.kr4PI = atm.kRayleigh * 4 * Math.PI;
-	atm.km4PI = atm.kMie * 4 * Math.PI;
-	atm.scale = 1.0 / (atm.outerRadius - atm.innerRadius);
-	atm.scaleOverScaleDepth = atm.scale / atm.scaleDepth;
-	atm.g2 = atm.g * atm.g;
-	canvas.atmosphere = atm;
-}
-function clamp(value, min, max) { //{{{
-	return Math.max(min, Math.min(value, max));
-} //}}}
-function defaultFor(name, value) { //{{{
-	return typeof name !== 'undefined' ? name : value;
-} //}}}
-function recover(canvasid, name, value) { //{{{
-	//Traverse canvas object tree for property defined by dot delimited string, returning it, or a default value if it is not found.
-	var object = document.getElementById(canvasid);
-	var properties = name.split('.');
-	for (var i = 0; i < properties.length; i++) {
-		object = object[properties[i]];
-		if (vesl.helpers.isEmptyOrUndefined(object)) { break; }
-    }
-	return defaultFor(object, value);
-} //}}}
-function triggerStartEvent(canvas) { //{{{
-	// If data markers are enabled for this canvas, wait for their handlers to be registered before triggering onStart event
-	if(!vesl.helpers.isEmptyOrUndefined(canvas.dataMarker) && canvas.dataMarker.enabled) {
-		vesl.dataMarker.areHandlersReady(
-			canvas, 
-			function() {
-				// The onStart event triggers once per plotmodel call load after WebGL and canvas initialization are complete
-				canvas.selector.trigger('onStart', [canvas]);
-			}
-		);
-	}
-} //}}}
-//}}}
-//{{{ Interaction Functions
-function onTap(ev, canvas) { //{{{
-	ev.preventDefault();
-	
-	var hit = raycastXY(canvas, ev.srcEvent.layerX, ev.srcEvent.layerY, canvas.unitNode);
-
-	//Trigger any handlers attatched to this canvas event.
-	canvas.selector.trigger('onTap', [ev, canvas, hit]);
-} //}}}
-function onPan(ev, canvas, displaylog) { //{{{
-	ev.preventDefault();
-	
-	if (ev.type === 'panstart') {
-		canvas.lastDeltaX = 0;
-		canvas.lastDeltaY = 0;
-	}
-	
-	//Trigger any handlers attatched to this canvas event.
-	canvas.selector.trigger('onPan', [ev, canvas]);
-	
-	//If any onPan handler sets preventDefaultOnPan to true, skips default onPan camera behavior
-	if (!canvas.view.preventDefaultOnPan) {
-		//If panning with two fingers or shift key, translate camera center
-		if (ev.srcEvent.shiftKey || ev.pointers.length === 2) {
-			if (canvas.view.panningEnabled) {
-				var deltaX = (canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth / canvas.view.zoom * 2 * canvas.controlSensitivity * 6.371e6;
-				var deltaY = (canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight / canvas.view.zoom * 2 * canvas.controlSensitivity * 6.371e6;
-				
-				//TODO: convert canvas.view.rotation from az/el euler to quaternion
-				if (canvas.view.twod) {
-					canvas.view.position[0] += Math.cos(vesl.RATIO_DEG_TO_RAD * canvas.view.rotation[0]) * deltaX - Math.sin(vesl.RATIO_DEG_TO_RAD * 0) * deltaY;
-					canvas.view.position[2] += Math.sin(vesl.RATIO_DEG_TO_RAD * canvas.view.rotation[0]) * deltaX + Math.cos(vesl.RATIO_DEG_TO_RAD * 0) * deltaY;
-				}
-				else {
-					canvas.view.position[0] += Math.cos(vesl.RATIO_DEG_TO_RAD * canvas.view.rotation[0]) * deltaX - Math.sin(vesl.RATIO_DEG_TO_RAD * canvas.view.rotation[0]) * deltaY;
-					canvas.view.position[2] += Math.sin(vesl.RATIO_DEG_TO_RAD * canvas.view.rotation[0]) * deltaX + Math.cos(vesl.RATIO_DEG_TO_RAD * canvas.view.rotation[0]) * deltaY;
-				}
-			}
-		}
-		//Else, rotate around camera center
-		else {
-			canvas.view.rotation[0] += (canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth * 2 * canvas.controlSensitivity * vesl.RATIO_RAD_TO_DEG;
-			canvas.view.rotation[1] += (canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight * -2 * canvas.controlSensitivity * vesl.RATIO_RAD_TO_DEG;
-			
-			if (canvas.view.rotation[0] > 360) { canvas.view.rotation[0] -= 360; };
-			if (canvas.view.rotation[0] < -360) { canvas.view.rotation[0] += 360; };
-			if (canvas.view.rotation[1] > 180) { canvas.view.rotation[1] -= 360; };
-			if (canvas.view.rotation[1] < -180) { canvas.view.rotation[1] += 360; };
-			
-			canvas.view.rotation[0] = clamp(canvas.view.rotation[0], canvas.view.azimuthLimits[0], canvas.view.azimuthLimits[1]);
-			canvas.view.rotation[1] = clamp(canvas.view.rotation[1], canvas.view.elevationLimits[0], canvas.view.elevationLimits[1]);
-			
-			if (displaylog) { console.log(canvas.view.rotation); }
-		}
-	}	
-	
-	canvas.view.preventDefaultOnPan = false;
-	canvas.lastDeltaX = ev.deltaX;
-	canvas.lastDeltaY = ev.deltaY;
-} //}}}
-function onPinch(ev, canvas, displaylog) { //{{{
-	ev.preventDefault();
-	
-	if (ev.type === 'pinchstart') { 
-		canvas.view.lastZoom = canvas.view.zoom; 
-	} else { 
-		canvas.view.zoom = ev.scale * canvas.view.lastZoom;
-		
-		if (displaylog) { 
-			console.log(canvas.view.zoom); 
-		}
-	}
-} //}}}
-function onZoom(ev, canvas, displaylog) { //{{{
-	ev.preventDefault();
-	
-	var delta = clamp(ev.scale || ev.wheelDelta || -ev.detail, -1, 1) * canvas.controlSensitivity * canvas.view.zoom / 4;
-	
-	modifyZoom(canvas.view.zoom + delta, canvas, displaylog, ev, 0);
-} //}}}
-function modifyZoom(value, canvas, displaylog, ev, duration) { //{{{
-	if (vesl.helpers.isEmptyOrUndefined(duration)) {
-		duration = 200;
-	}
-	
-	var targetZoom 	= clamp(value, canvas.view.zoomLimits[0], canvas.view.zoomLimits[1]);
-	var currentZoom = canvas.view.zoom;
-	
-	animateValue(
-		0,
-		1.0,
-		duration,
-		'swing',
-		function(value, info) {
-			canvas.view.zoom = currentZoom * (1 - value) + targetZoom * value;
-			
-			if (displaylog) { 
-				console.log(canvas.view.zoom); 
-			}
-			
-			//Trigger any handlers attatched to this canvas event.
-			canvas.selector.trigger('onZoom', [ev, canvas]);
-		}
-	);
-} //}}}
-function toggleMoviePlay(canvas) { //{{{
-	canvas.animation.play = !canvas.animation.play;
-} //}}}
-function screenToWorldPoint(canvas, x, y) { //{{{
-	var viewportX 	= (x - canvas.width / 2) / (canvas.width / 2);
-	var viewportY 	= (canvas.height / 2 - y) / (canvas.height / 2);
-	
-	return vec3.transformMat4(vec3.create(), [viewportX, viewportY, 0], canvas.camera.vpInverseMatrix);
-} //}}}
-function screenToModelRay(canvas, x, y, node) { //{{{
-	var inverseMVPMatrix 	= mat4.invert(mat4.create(), mat4.multiply(mat4.create(), canvas.camera.vpMatrix, node.modelMatrix));
-	var viewportX 			= (x - canvas.width / 2) / (canvas.width / 2);
-	var viewportY 			= (canvas.height / 2 - y) / (canvas.height / 2);
-	var origin 				= vec3.transformMat4(vec3.create(), [viewportX, viewportY, 0], inverseMVPMatrix);
-	var far 				= vec3.transformMat4(vec3.create(), [viewportX, viewportY, 1.0], inverseMVPMatrix);
-	var direction 			= vec3.normalize(vec3.create(), vec3.subtract(vec3.create(), far, origin));
-	
-	return 	{
-				'origin'	: origin, 
-				'direction'	: direction
-			};
-} //}}}
-function raycast(canvas, origin, direction, node) { //{{{
-	//Performs raycast on given node using ray origin and direction vectors.
-	//Returns hit objects with hit position, normals, barycentric coordinates, element number, and indices of ray-triangle intersection.
-	//TODO: Diagnose marker issues with orthographic views and slr-eustatic updates when switching between basins.
-	if (!node.octree) { 
-		node.octree = new GL.Octree(node.mesh); 
-	}
-	
-	var hit = node.octree.testRay(origin, direction, 1e3, 1e10);
-	
-	if (!hit) { 
-		return; 
-	}
-
-	hit.modelPos = vec3.copy(vec3.create(), hit.pos);
-	
-	vec3.transformMat4(hit.pos, hit.pos, node.modelMatrix);
-	
-	return hit;
-} //}}}
-function raycastXY(canvas, x, y, node, faceWinding) { //{{{
-	//Performs raycast on given node using x and y screenspace coordinates.
-	//Returns hit objects with hit position, normals, barycentric coordinates, element number, and indices of ray-triangle intersection.
-	//TODO: Diagnose marker issues with orthographic views and slr-eustatic updates when switching between basins.
-	var ray = screenToModelRay(canvas, x, y, node);
-	
-	return raycast(canvas, ray.origin, ray.direction, node);
-} //}}}
-function animateValue(current, target, duration, easing, stepCallback, doneCallback) { //{{{
-	//Animates scalar value for length duration, calling callback each step. Specify smooth easing as a string ('swing', 'linear').
-	$({'value':current}).animate({'value' : target}, {
-		duration	: duration,
-		easing		: easing,
-		step		: stepCallback,
-		done		: doneCallback
-	});
-} //}}}
-//}}}
-//{{{ Drawing Functions
-function updateCameraMatrix(canvas) { //{{{
-    //Update view matrix and multiply with projection matrix to get the view-projection matrix.
-	var vMatrix 					= mat4.create();
-	var pMatrix 					= mat4.create();
-	var translateMatrix 			= mat4.create();
-	var rotationMatrix 				= mat4.create();
-	var azimuthRotationMatrix 		= mat4.create();
-	var elevationRotationMatrix 	= mat4.create();
-	var aspectRatio 				= canvas.clientWidth / canvas.clientHeight;
-	var camera 						= canvas.camera;
-	var view 						= canvas.view;
-
-	if (view.twod) { 
-		mat4.ortho(
-			pMatrix, -aspectRatio * 6.371e6 / view.zoom, 
-			aspectRatio * 6.371e6 / view.zoom, 
-			-6.371e6 / view.zoom, 
-			6.371e6 / view.zoom, 
-			camera.near, 
-			camera.far
-		); 
-	} else { 
-		mat4.perspective(
-			pMatrix, 
-			camera.fov * vesl.RATIO_DEG_TO_RAD,
-			aspectRatio, 
-			camera.near, 
-			camera.far
-		); 
-	}
-	
-	//Apply worldspace translation
-	mat4.translate(vMatrix, translateMatrix, vec3.negate(vec3.create(), view.position));
-	
-	//Calculate rotation around camera focal point about worldspace origin
-	if (view.twod) {
-		mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, -vesl.RATIO_DEG_TO_RAD * 0, [0, 1, 0]);
-		mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, vesl.RATIO_DEG_TO_RAD * 90, [1, 0, 0]);
-		mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
-	} else {
-		mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, -vesl.RATIO_DEG_TO_RAD * (view.rotation[0] + 90), [0, 1, 0]);
-		mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, vesl.RATIO_DEG_TO_RAD * view.rotation[1], [1, 0, 0]);
-		mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
-		//var quaternionWorldX = Node.prototype.eulerToQuaternion(0, 0, vesl.RATIO_DEG_TO_RAD * (view.rotation[0]));
-		//var quaternionWorldY = Node.prototype.eulerToQuaternion(0, vesl.RATIO_DEG_TO_RAD * (view.rotation[1]), 0);
-		//var quaternionWorldZ = Node.prototype.eulerToQuaternion(vesl.RATIO_DEG_TO_RAD * (view.rotation[2]), 0, 0);
-		//var quaternionTemp = quat.multiply(quat.create(), quaternionWorldY, quaternionWorldX);
-		//quat.multiply(camera.rotation, quaternionWorldZ, quaternionTemp);
-		//mat4.fromQuat(rotationMatrix, camera.rotation);	
-	}
-
-	//Apply rotation transform
-	mat4.multiply(vMatrix, rotationMatrix, vMatrix);
-	
-	//Apply screenspace translation to emulate rotation around point
-	mat4.identity(translateMatrix);
-	mat4.translate(translateMatrix, translateMatrix, [0.0, 0.0, -6.371e6 / view.zoom]);
-	mat4.multiply(vMatrix, translateMatrix, vMatrix);
-	
-	//Apply projection matrix to get camera matrix
-	mat4.copy(camera.vMatrix, vMatrix);
-	mat4.multiply(camera.vpMatrix, pMatrix, vMatrix);
-	
-	//Calculate inverse view matrix fields for lighting and raycasts
-	mat4.invert(camera.vInverseMatrix, camera.vMatrix);
-	mat4.invert(camera.vpInverseMatrix, camera.vpMatrix);
-	
-	vec3.transformMat4(camera.position, vec3.create(), camera.vInverseMatrix);
-	vec3.sub(camera.relativePosition, camera.position, view.position);
-	vec3.normalize(camera.direction, camera.relativePosition);
-	
-	camera.ready = true;
-}//}}}
-function drawSceneGraphNode(canvas, node) { //{{{
-	if (!node.enabled) { 
-		return;
-	}
-
-	var gl = canvas.gl;
-	
-	gl.makeCurrent();
-	
-	var mvpMatrix = mat4.create();
-	
-	mat4.multiply(mvpMatrix, canvas.camera.vpMatrix, node.modelMatrix);
-	
-	var normalMatrix 	= mat3.create();
-	var tempMatrix 		= mat4.create();
-	
-	mat4.invert(tempMatrix, node.modelMatrix);
-	mat4.transpose(tempMatrix, tempMatrix);
-	mat3.fromMat4(normalMatrix, tempMatrix);
-	
-	if (node.texture) { 
-		node.texture.bind(0); 
-	}
-	
-	if (node.disableDepthTest) { 
-		gl.disable(gl.DEPTH_TEST); 
-	}
-	
-	if (node.enableCullFace) { 
-		gl.enable(gl.CULL_FACE); 
-	}
-
-	gl.cullFace(node.cullFace);
-	gl.lineWidth(node.lineWidth);
-	gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
-	
-	//Setup for light that originates from camera
-	var atm 		= canvas.atmosphere;
-	var lightOrigin = vec3.create();
-	
-	node.shader.uniforms({
-		m4MVP: 					mvpMatrix,
-		m3Normal: 				normalMatrix,
-		m4Model: 				node.modelMatrix,
-		u_alpha: 				node.alpha,
-		u_ambientColor: 		node.ambientColor,
-		u_cameraPosition: 		canvas.camera.position,
-		u_diffuseColor: 		node.diffuseColor,
-		u_lightDirection: 		canvas.camera.direction,
-		u_lightingBias: 		node.lightingBias,
-		u_maskZerosColor: 		node.maskZerosColor,
-		u_maskZerosEnabled: 	node.maskZerosEnabled,
-		u_maskZerosTolerance: 	node.maskZerosTolerance,
-		u_maskZerosZeroValue: 	node.maskZerosZeroValue,
-		u_maskEnabled: 			node.maskEnabled,
-		u_maskHeight: 			node.maskHeight,
-		u_maskColor: 			node.maskColor,
-		u_pointSize: 			node.pointSize,
-		u_specularColor: 		node.specularColor,
-		u_specularPower: 		node.specularPower,
-		u_specularStrength: 	node.specularStrength,
-		u_texture: 				0,
-		v3CameraPosition: 		canvas.camera.position,
-		v3Translate: 			node.translation,
-		v3LightPos: 			lightOrigin,
-		v3InvWavelength: 		atm.inv_wavelength4,
-		fOuterRadius: 			atm.outerRadius,
-		fOuterRadius2: 			atm.outerRadius2,
-		fInnerRadius: 			atm.innerRadius,
-		fInnerRadius2: 			atm.innerRadius2,
-		fKrESun: 				atm.krESun,
-		fKmESun: 				atm.kmESun,
-		fKr4PI: 				atm.kr4PI,
-		fKm4PI: 				atm.km4PI,
-		fScale: 				atm.scale, 
-		fScaleDepth: 			atm.scaleDepth,
-		fScaleOverScaleDepth: 	atm.scaleOverScaleDepth, 
-		v3LightPosFrag: 		lightOrigin,
-		fHdrExposure: 			atm.hdr_exposure,	
-		g: 						atm.g,			
-		g2: 					atm.g2,
-		a: 						atm.a,
-		b: 						atm.b,
-		c: 						atm.c,
-		d: 						atm.d,		
-		e: 						atm.e,
-		attenuation: 			atm.attenuation
-	}).draw(node.mesh, node.drawMode, 'triangles');
-	
-	gl.enable(gl.DEPTH_TEST);
-	gl.disable(gl.CULL_FACE);
-} //}}}
-function canvasResize(canvas) {
-	var rect = canvas.getBoundingClientRect();
-	
-	canvas.width  = rect.width;
-	canvas.height = rect.height;
-	
-	canvas.gl.viewport(0, 0, canvas.width, canvas.height);
-	
-	var overlaycanvas = canvas.overlaycanvas;
-	
-	if (!vesl.helpers.isEmptyOrUndefined(overlaycanvas)) {
-		rect = overlaycanvas.getBoundingClientRect();
-		overlaycanvas.width  = rect.width;
-		overlaycanvas.height = rect.height;
-		overlaycanvas.getContext('2d').clearRect(0, 0, overlaycanvas.width, overlaycanvas.height);
-	}
-}
-function drawCanvas(canvas) { //{{{
-	//Ensure all nodes are ready to render
-	//TODO: Come up with better way to check if shaders are ready, or move outside of main draw function
-	var nodes = canvas.nodes;
-	
-	if (!canvas.draw.ready) {
-		if (Object.keys(nodes).length !== 0) {
-			canvas.draw.ready = true;
-			
-			for (var node in nodes) {
-				if (nodes[node].shader.ready === false) {
-					canvas.draw.ready = false;
-					
-					break;
-				}
-			}
-		}
-	}
-	
-	//Begin rendering nodes
-	if (canvas.draw.ready) {
-		//Handle canvas resizing and viewport/screenspace coordinate synchronization
-		canvasResize(canvas);	
-		
-		var gl = canvas.gl;
-		
-		gl.makeCurrent(); //litegl function to handle switching between multiple canvases
-		gl.clearColor(canvas.backgroundcolor[0], canvas.backgroundcolor[1], canvas.backgroundcolor[2], canvas.backgroundcolor[3]);
-		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-		
-		updateCameraMatrix(canvas);
-		
-		//Trigger any handlers attatched to this canvas event.
-		canvas.selector.trigger('onPreRender', [canvas]);
-		
-		for (var handler in canvas.overlayHandlers) { 
-			canvas.overlayHandlers[handler](canvas);
-		}
-		
-		var drawPassNumber = 3;
-		
-		// NOTE: For large value of drawPassNumber/nodes.length, could copy nodes to a new array and pop elements as each drawOrder is found
-		for (var i = drawPassNumber - 1; i >= 0; --i) {
-			for (var node in nodes) {
-				if (nodes[node].drawOrder === i) { 
-					drawSceneGraphNode(canvas, nodes[node]); 
-				}
-			}
-		}
-	}
-	
-	//Regardless of ready state, schedule next frame to check for ready state and render
-	canvas.draw.handler = window.requestAnimationFrame(function(time) { drawCanvas(canvas); });
-} //}}}
-//}}}
Index: sm/trunk-jpl/src/m/plot/webgl_node.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/webgl_node.js	(revision 22911)
+++ 	(revision )
@@ -1,652 +1,0 @@
-//NODE class definition //{{{
-//	Description:
-//		Contains all information for rendering an object in WebGL, including mesh, materials, shaders, and other attributes.	
-//	Usage:
-//		node=new Node('canvas', canvas);
-
-function Node() { //{{{
-	//properties
-	// {{{
-	var args = Array.prototype.slice.call(arguments);
-	var options = new pairoptions(args.slice(0,args.length));
-
-	this.canvas = options.getfieldvalue('canvas', 								null),
-	this.options = options.getfieldvalue('options', 							null),
-	this.gl = canvas.gl;
-	
-	this.alpha = options.getfieldvalue('alpha', 								1.0);									//Shading transparency.
-	this.ambientColor = options.getfieldvalue('ambientColor', 					[0.0, 0.0, 0.0, 1.0]);					//Ambient color used in lighting.
-	this.animation = options.getfieldvalue('animation', 						{});									//Animtation parameters.
-	this.arrays = options.getfieldvalue('arrays', 								{});									//Storage for webgl arrays.
-	this.caxis = options.getfieldvalue('caxis', 								[0.0, 1.0]);							//Color axis for texturing.
-	this.center = options.getfieldvalue('center', 								vec3.create());							//Center of rotation/scaling.
-	this.cullFace = options.getfieldvalue('cullFace', 							this.gl.BACK);							//GL enum for face culling back/front faces.
-	this.computeIndices = options.getfieldvalue('computeIndices', 				true);									//Specifies whether or not indices need to be computed during patching.
-	this.disableDepthTest = options.getfieldvalue('disableDepthTest', 			false);									//GL enum for enabling/disabling depth testing.
-	this.diffuseColor = options.getfieldvalue('diffuseColor', 					[0.0, 0.0, 0.0, 1.0]);					//Diffuse color in vec4 rgba format.
-	this.drawMode = options.getfieldvalue('drawMode', 							this.gl.TRIANGLES);						//GL enum for draw mode.
-	this.drawOrder = options.getfieldvalue('drawOrder', 						1);										//Drawing order for non-depth tested/transparent objects. Higher numbers are drawn. first.
-	this.enabled = options.getfieldvalue('enabled', 							true);									//Toggles display of this nodde.
-	this.enableCullFace = options.getfieldvalue('enableCullFace', 				true);									//Toggles use of face culling.
-	this.hideOcean = options.getfieldvalue('hideOcean', 						false);									//ISSM shader uniform controlling ocean masking
-	this.lineWidth = options.getfieldvalue('lineWidth', 						1.0);									//Controls width of gl lines. No reliable support across windows platforms.
-	this.lightingBias = options.getfieldvalue('lightingBias', 					0.0);									//Controls width of gl lines. No reliable support across windows platforms.
-	this.log = options.getfieldvalue('log', 									false);									//Controls logarithmic color axis scaling for texturing.
-	this.maskAll = options.getfieldvalue('maskAll', 							[]);									//Masking array used for marking all region elements.
-	this.maskBoundary = options.getfieldvalue('maskBoundary', 					[]);									//Masking array used for marking region boundary edges.
-	this.maskColor = options.getfieldvalue('maskColor', 						vec4.fromValues(0.0, 0.0, 1.0, 1.0));	//ISSM shader uniform controlling ocean masking color.
-	this.maskEnabled = options.getfieldvalue('maskEnabled', 					false);									//ISSM shader uniform toggling ocean masking.
-	this.maskHeight = options.getfieldvalue('maskHeight', 						150.0);									//ISSM shader uniform controlling height at which ocean masking is cut off.
-	this.maskObject = options.getfieldvalue('maskObject', 						{'enabled':false});						//Masking array options object.
-	this.maskZerosColor = options.getfieldvalue('maskZerosColor', 				[1.0, 1.0, 1.0, 1.0]);					//ISSM shader uniform controlling value masking color.
-	this.maskZerosEnabled = options.getfieldvalue('maskZerosEnabled', 			false);									//ISSM shader uniform toggling value masking.
-	this.maskZerosTolerance = options.getfieldvalue('maskZerosTolerance', 		1e-6);									//ISSM shader uniform controlling tolerance of value to zero value when masking.
-	this.maskZerosZeroValue = options.getfieldvalue('maskZerosZeroValue', 		0.5);									//ISSM shader uniform controlling zero value masking offset.
-	this.mesh = options.getfieldvalue('mesh', 									undefined);								//Litegl GL.Mesh class used for drawing.
-	this.name = options.getfieldvalue('name', 									'node');								//Name of node.
-	this.nanIndices = options.getfieldvalue('nanIndices', 						new Set());								//Set of NaN indices to mask out of mesh.
-	this.octree = options.getfieldvalue('octree', 								undefined);								//Fast raytracing octree object.
-	this.pointSize = options.getfieldvalue('pointSize', 						15.0);									//Size of points when displaying GL.POINTS.
-	this.shaderName = options.getfieldvalue('shaderName', 						'Colored');								//Name of shader to use.
-	this.shader = options.getfieldvalue('shader', 								this.gl.shaders[this.shaderName]);		//Compiled shader object.
-	this.specularColor = options.getfieldvalue('specularColor', 				[1.0, 1.0, 1.0, 1.0]);					//Specular reflection color used in lighting.
-	this.specularStrength = options.getfieldvalue('specularStrength', 			1.0);									//Specular reflection power - represents specular highlight sharpness.
-	this.specularPower = options.getfieldvalue('specularPower', 				5);										//Specular reflection power - represents specular highlight sharpness.
-	this.texture = options.getfieldvalue('texture', 							undefined);								//GL texture object.
-	this.scale = options.getfieldvalue('scale', 								vec3.fromValues(1, 1, 1));				//XYZ scaling of the node.
-	this.renderObject = options.getfieldvalue('renderObject', 					{});									//Field for additional render object state information not necessarily related to node display.
-	this.rotation = options.getfieldvalue('rotation', 							vec3.create());							//XYZ rotation of the node.
-	this.rotationQuaternion = options.getfieldvalue('rotationQuaternion',		quat.create());							//Quaternion rotation of the node. Not currently in use.
-	this.translation = options.getfieldvalue('translation', 					vec3.create());							//XYZ translation of the node.
-	this.modelMatrix = options.getfieldvalue('modelMatrix', 					mat4.create());							//Model matrix computed from updateModelMatrix function.
-	this.translationMatrix = options.getfieldvalue('translationMatrix', 		mat4.create());							//Intermediate translation matrix computed from updateModelMatrix function.
-	this.rotationMatrix = options.getfieldvalue('rotationMatrix', 				mat4.create());							//Intermediate rotation matrix computed from updateModelMatrix function.
-	this.scaleMatrix = options.getfieldvalue('scaleMatrix', 					mat4.create());							//Intermediate scale matrix computed from updateModelMatrix function.
-	this.inverseModelMatrix = options.getfieldvalue('inverseModelMatrix', 		mat4.create());							//Inverse model matrix.
-	this.inverseRotationMatrix = options.getfieldvalue('inverseRotationMatrix', mat4.create());							//Inverse rotation matrix.
-	//}}}
-	//initialize {{{
-	//if (this.name in canvas.nodes) abort?
-	this.updateModelMatrix();
-	this.updateDiffuseColor();
-	canvas.nodes[this.name] = this;
-	//}}}
-} //}}}
-Node.prototype.eulerToQuaternion = function(pitch, roll, yaw) { //{{{
-	var t0 = Math.cos(yaw * 0.5);
-	var t1 = Math.sin(yaw * 0.5);
-	var t2 = Math.cos(roll * 0.5);
-	var t3 = Math.sin(roll * 0.5);
-	var t4 = Math.cos(pitch * 0.5);
-	var t5 = Math.sin(pitch * 0.5);
-
-	var w = t0 * t2 * t4 + t1 * t3 * t5;
-	var x = t0 * t3 * t4 - t1 * t2 * t5;
-	var y = t0 * t2 * t5 + t1 * t3 * t4;
-	var z = t1 * t2 * t4 - t0 * t3 * t5;
-	return quat.fromValues(x, y, z, w);
-} //}}}
-Node.prototype.quaternionToEuler = function(q) { //{{{
-	var ysqr = q[1] * q[1];
-
-	// roll (x-axis rotation)
-	var t0 = +2.0 * (q[3] * q[0] + q[1] * q[2]);
-	var t1 = +1.0 - 2.0 * (q[0] * q[0] + ysqr);
-	var roll = Math.atan2(t0, t1);
-
-	// pitch (y-axis rotation)
-	var t2 = +2.0 * (q[3] * q[1] - q[2] * q[0]);
-	t2 = t2 > 1.0 ? 1.0 : t2;
-	t2 = t2 < -1.0 ? -1.0 : t2;
-	var pitch = Math.asin(t2);
-
-	// yaw (z-axis rotation)
-	var t3 = +2.0 * (q[3] * q[2] + q[0] * q[1]);
-	var t4 = +1.0 - 2.0 * (ysqr + q[2] * q[2]);
-	var yaw = Math.atan2(t3, t4);
-	
-	return [pitch * vesl.RATIO_RAD_TO_DEG, roll * vesl.RATIO_RAD_TO_DEG, yaw * vesl.RATIO_RAD_TO_DEG];
-} //}}}
-Node.prototype.updateModelMatrix = function() { //{{{
-	//Update the model matrix if rotation, scale, or translation have been manually modified.
-	var modelMatrix = mat4.create();
-
-	var translationMatrix = mat4.create();
-	mat4.translate(translationMatrix, translationMatrix, vec3.negate(vec3.create(), this.center)); //scale/rotation centering
-	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
-	
-	var scaleMatrix = mat4.create();
-	mat4.scale(scaleMatrix, scaleMatrix, this.scale);
-	mat4.multiply(modelMatrix, scaleMatrix, modelMatrix);
-	
-	var rotationMatrix = mat4.create();
-	var zRotationMatrix = mat4.create();	
-	mat4.rotate(zRotationMatrix, zRotationMatrix, vesl.RATIO_DEG_TO_RAD * this.rotation[2], [0.0, 0.0, 1.0]);
-	mat4.multiply(rotationMatrix, zRotationMatrix, rotationMatrix);
-	var yRotationMatrix = mat4.create();	
-	mat4.rotate(yRotationMatrix, yRotationMatrix, vesl.RATIO_DEG_TO_RAD * this.rotation[1], [0.0, 1.0, 0.0]);
-	mat4.multiply(rotationMatrix, yRotationMatrix, rotationMatrix);
-	var xRotationMatrix = mat4.create();	
-	mat4.rotate(xRotationMatrix, xRotationMatrix, vesl.RATIO_DEG_TO_RAD * this.rotation[0], [1.0, 0.0, 0.0]);
-	mat4.multiply(rotationMatrix, xRotationMatrix, rotationMatrix);
-	mat4.multiply(modelMatrix, rotationMatrix, modelMatrix);
-
-	//var rotationQuaternionX = this.eulerToQuaternion(0, -vesl.RATIO_DEG_TO_RAD * this.rotation[0], 0);
-	//var rotationQuaternionY = this.eulerToQuaternion(vesl.RATIO_DEG_TO_RAD * this.rotation[1], 0, 0);
-	//mat4.fromQuat(this.rotationMatrix, quat.multiply(quat.create(), rotationQuaternionY, rotationQuaternionX));
-
-	//mat4.multiply(this.modelMatrix, this.rotationMatrix, this.modelMatrix);	
-		
-	
-	mat4.identity(translationMatrix);
-	mat4.translate(translationMatrix, translationMatrix, this.center); //relative translation
-	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
-	
-	mat4.identity(translationMatrix);
-	mat4.translate(translationMatrix, translationMatrix, this.translation); //absolute translation
-	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
-	
-	this.modelMatrix = modelMatrix;
-	this.inverseModelMatrix = mat4.invert(mat4.create(), modelMatrix);
-	this.rotationMatrix = rotationMatrix;
-	this.inverseRotationMatrix = mat4.invert(mat4.create(), rotationMatrix);
-} //}}}
-Node.prototype.updateDiffuseColor = function() { //{{{
-	//Update the diffuse color with an RGB color name or vec4 containing r, g, b, and alpha values from 0.0 to 1.0
-	var color = this.diffuseColor;
-	if (typeof color === 'string') {
-		color = new RGBColor(color);
-		if (color.ok) color = [color.r/255.0, color.g/255.0, color.b/255.0, 1.0];
-		else throw Error(sprintf("s%s%s\n","initWebGL error message: cound not find out edgecolor color for curent canvas ", canvas));
-	}
-	this.diffuseColor = color;
-} //}}}
-Node.prototype.transform = function() { //{{{
-	//Transforms the translation, rotation, or scale of the node and updates the model matrix.
-	var args = Array.prototype.slice.call(arguments);
-	var options = new pairoptions(args.slice(0,args.length));
-	
-	var translation = options.getfieldvalue('translation', undefined);
-	var rotation = options.getfieldvalue('rotation', undefined);
-	var scale = options.getfieldvalue('scale', undefined);
-	
-	if (!vesl.helpers.isEmptyOrUndefined(translation)) this.translation = translation;
-	if (!vesl.helpers.isEmptyOrUndefined(rotation)) this.rotation = rotation;
-	if (!vesl.helpers.isEmptyOrUndefined(scale)) this.scale = scale;
-	this.updateModelMatrix();
-} //}}}
-Node.prototype.patch = function() { //{{{
-	//Emulates the behavior of MATLAB patch function by constructing a mesh from arguments.
-	//Limitations:
-	//	-Expects pair labeled arguments ('FaceColor','none',...).
-	//	-Only handles Face/Vertices/FaceVertexCData element/node plots.
-	//	-Only supports FaceColor 'interp' and 'none'.
-	
-	var args = Array.prototype.slice.call(arguments);
-	var options = new pairoptions(args.slice(0,args.length));
-
-	var faces = options.getfieldvalue('Faces', undefined);
-	var vertices = options.getfieldvalue('Vertices', undefined);
-	var faceVertexCData = options.getfieldvalue('FaceVertexCData', undefined);
-	var faceColor = options.getfieldvalue('FaceColor', 'interp');
-	var edgeColor = options.getfieldvalue('EdgeColor', this.diffuseColor);
-	var lineWidth = options.getfieldvalue('linewidth', 1);
-	
-	this.faces = faces;
-	this.vertices = vertices;
-	this.diffuseColor = edgeColor;
-	this.updateDiffuseColor();
-	this.computeMasks();
-	
-	this.patchVertices(faceVertexCData, faces, vertices);
-	this.patchCoords(faceVertexCData, faces, vertices);
-	this.patchIndices(faces, faceColor);
-
-	this.mesh = GL.Mesh.load(this.arrays, null, null, this.gl);
-	this.mesh.computeNormals();
-	this.computeOctree();
-} //}}}
-Node.prototype.patchVertices = function(faceVertexCData, faces, vertices) { //{{{
-	//Patch subfunction for processing xyz vertices.
-	var vertexArray;
-	var face;
-	
-	if (vesl.helpers.isEmptyOrUndefined(faceVertexCData)) {
-		vertexArray = new Float32Array(vertices[0].length * 3);
-		for(var i = 0, v = 0; i < vertices[0].length; i++) {	
-			vertexArray[v++] = vertices[0][i];
-			vertexArray[v++] = vertices[1][i];
-			vertexArray[v++] = vertices[2][i];
-		}
-	}
-	else if (!Array.isArray(faceVertexCData[0])) { //indexed plot - faceVertexCData = [0, 2.32, 231.1, ...]
-		if (faceVertexCData.length === faces.length) { //element plot
-			vertexArray = new Float32Array(faces.length * 3 * 3);
-			for(var i = 0, v = 0, t = 0; i < faces.length; i++) {
-				face = [faces[i][0] - 1, faces[i][1] - 1, faces[i][2] - 1];
-				for(var j = 0; j < face.length; j++) {
-					if (isNaN(faceVertexCData[i])) {
-						this.nanIndices.add(i);
-						vertexArray[v++] = 0.0;
-						vertexArray[v++] = 0.0;
-						vertexArray[v++] = 0.0;
-					}
-					else {
-						vertexArray[v++] = vertices[0][face[j]];
-						vertexArray[v++] = vertices[1][face[j]];
-						vertexArray[v++] = vertices[2][face[j]];
-					}
-				}
-			}
-			this.computeIndices = false;
-		}
-		else if (faceVertexCData.length === vertices[0].length) { //node plot
-			vertexArray = new Float32Array(vertices[0].length * 3);
-			for(var i = 0, v = 0, t = 0; i < vertices[0].length; i++) {
-				if (isNaN(faceVertexCData[i])) {
-					this.nanIndices.add(i);
-					vertexArray[v++] = 0.0;
-					vertexArray[v++] = 0.0;
-					vertexArray[v++] = 0.0;
-				}
-				else {
-					vertexArray[v++] = vertices[0][i];
-					vertexArray[v++] = vertices[1][i];
-					vertexArray[v++] = vertices[2][i];
-				}
-			}
-		}
-	}
-	else if (Array.isArray(faceVertexCData[0])) { //JS specific: precomputed UV coord plot - faceVertexCData = [[0, 0.99, 0.4, ...],[0, 0.99, 0.4, ...]]
-		if (faceVertexCData[0].length === faces.length) { //element plot
-			vertexArray = new Float32Array(faces.length * 3 * 3);
-			for(var i = 0, v = 0, t = 0; i < faces.length; i++) {
-				face = [faces[i][0] - 1, faces[i][1] - 1, faces[i][2] - 1];
-				for(var j = 0; j < face.length; j++) {					
-					vertexArray[v++] = vertices[0][face[j]];
-					vertexArray[v++] = vertices[1][face[j]];
-					vertexArray[v++] = vertices[2][face[j]];
-				}
-			}
-			this.computeIndices = false;
-		}
-		else if (faceVertexCData[0].length === vertices[0].length) { //node plot
-			vertexArray = new Float32Array(vertices[0].length * 3);
-			for(var i = 0, v = 0, t = 0; i < vertices[0].length; i++) {
-				vertexArray[v++] = vertices[0][i];
-				vertexArray[v++] = vertices[1][i];
-				vertexArray[v++] = vertices[2][i];
-			}
-		}
-	}
-	this.arrays.vertices = vertexArray;
-} //}}}
-Node.prototype.patchCoords = function(faceVertexCData, faces, vertices) { //{{{
-	//Patch subfunction for processing texture coords/UVs.
-	var coordArray;
-	var cramge;
-	var caxis = this.caxis;
-	var face;
-	
-	if (vesl.helpers.isEmptyOrUndefined(faceVertexCData)) { return; }
-	
-	//Use logarithmic scaling if it is valid
-	if (this.log !== false && this.log !== 'off') {	
-		caxis = [
-			Math.log10(caxis[0]) / Math.log10(this.log),
-			Math.log10(caxis[1]) / Math.log10(this.log)
-		];
-	}
-	
-	if (!Array.isArray(faceVertexCData[0])) { //indexed plot - faceVertexCData = [0, 2.32, 231.1, ...]
-		if (faceVertexCData.length === faces.length) { //element plot
-			coordArray = new Float32Array(faces.length * 3 * 2);
-			crange = caxis[1] - caxis[0];
-			for(var i = 0, v = 0, t = 0; i < faces.length; i++) {
-				face = [faces[i][0] - 1, faces[i][1] - 1, faces[i][2] - 1];
-				for(var j = 0; j < face.length; j++) {
-					if (isNaN(faceVertexCData[i])) {
-						this.nanIndices.add(i);
-						coordArray[t++] = 0.0;
-						coordArray[t++] = 0.0;
-					}
-					else {
-						coordArray[t++] = this.queryCoordMask(i); //Account for special texturing
-						coordArray[t++] = clamp((faceVertexCData[i] - caxis[0]) / crange, 0.0, 1.0);
-					}
-				}
-			}
-			this.computeIndices = false;
-		}
-		else if (faceVertexCData.length === vertices[0].length) { //node plot
-			coordArray = new Float32Array(vertices[0].length * 2);
-			crange = caxis[1] - caxis[0];
-			for(var i = 0, v = 0, t = 0; i < vertices[0].length; i++) {
-				if (isNaN(faceVertexCData[i])) {
-					this.nanIndices.add(i);
-					coordArray[t++] = 0.0;
-					coordArray[t++] = 0.0;
-				}
-				else {
-					coordArray[t++] = this.queryCoordMask(i); //Account for special texturing
-					coordArray[t++] = clamp((faceVertexCData[i] - caxis[0]) / crange, 0.0, 1.0);
-				}
-			}
-		}
-	}
-	else if (Array.isArray(faceVertexCData[0])) { //JS specific: precomputed UV coord plot - faceVertexCData = [[0, 0.99, 0.4, ...],[0, 0.99, 0.4, ...]]
-		if (faceVertexCData[0].length === faces.length) { //element plot
-			coordArray = new Float32Array(faces.length * 3 * 2);
-			for(var i = 0, v = 0, t = 0; i < faces.length; i++) {
-				face = [faces[i][0] - 1, faces[i][1] - 1, faces[i][2] - 1];
-				for(var j = 0; j < face.length; j++) {					
-					coordArray[t++] = faceVertexCData[0][i];
-					coordArray[t++] = faceVertexCData[1][i];
-				}
-			}
-			this.computeIndices = false;
-		}
-		else if (faceVertexCData[0].length === vertices[0].length) { //node plot
-			coordArray = new Float32Array(vertices[0].length * 2);
-			for(var i = 0, v = 0, t = 0; i < vertices[0].length; i++) {
-				coordArray[t++] = faceVertexCData[0][i];
-				coordArray[t++] = faceVertexCData[1][i];
-			}
-		}
-	}
-	this.arrays.coords = coordArray;
-} //}}}
-Node.prototype.patchIndices = function(faces, faceColor) { //{{{
-	//Patch subfunction for processing faces/elements/triangles/indices.
-	var indexArray;
-	var face;
-	
-	if (faceColor === 'none') { //Check for wireframe mesh rendering
-		if (this.drawMode === this.gl.TRIANGLES) { //NOTE: Stopgap to allow gl.LINE_STRIP nodes render normally. Only use case for faceColor === 'none' is for plot_mesh
-			this.drawMode = this.gl.LINES;
-		}
-	}
-	
-	if (this.computeIndices === true && !vesl.helpers.isEmptyOrUndefined(faces)) {
-		if (!vesl.helpers.isEmptyOrUndefined(faces[0])) { //Check for 2D format and process if needed
-			if (faceColor !== 'none') { //Check for triangle rendering
-				indexArray = new Uint16Array(faces.length * 3);
-				for(var i = 0, f = 0; i < faces.length; i++) {
-					face = [faces[i][0] - 1, faces[i][1] - 1, faces[i][2] - 1];
-					if (this.nanIndices.has(face[0]) || this.nanIndices.has(face[1]) || this.nanIndices.has(face[2])) continue; //Skip triangle if contains NaN value.
-					indexArray[f++] = faces[i][0] - 1;
-					indexArray[f++] = faces[i][1] - 1;
-					indexArray[f++] = faces[i][2] - 1;
-				}
-			}
-			else { //Check for wireframe mesh rendering
-				indexArray = new Uint16Array(faces.length * 6);
-				for(var i = 0, f = 0; i < faces.length; i++) {
-					indexArray[f++] = faces[i][0] - 1;
-					indexArray[f++] = faces[i][1] - 1;
-					indexArray[f++] = faces[i][1] - 1;
-					indexArray[f++] = faces[i][2] - 1;
-					indexArray[f++] = faces[i][2] - 1;
-					indexArray[f++] = faces[i][0] - 1;
-				}
-			}
-		}
-		else { //Else, assume face indices have already been processed
-			indexArray = faces;
-		}
-		this.arrays.triangles = indexArray;
-	}
-} //}}}
-Node.prototype.updateBuffer = function() { //{{{
-	//Updates the mesh buffers provided in place.
-	//NOTE: Only support coord buffers currently.
-	var args = Array.prototype.slice.call(arguments);
-	var options = new pairoptions(args.slice(0,args.length));
-	
-	var coords = options.getfieldvalue('Coords', undefined);
-	if (!vesl.helpers.isEmptyOrUndefined(coords)) {
-		this.patchCoords(coords, this.faces, this.vertices);
-		var buffer = this.mesh.getBuffer("coords");
-		buffer.data = this.arrays.coords;
-		buffer.upload(this.gl.DYNAMIC_DRAW);
-	}
-} //}}}
-Node.prototype.computeOctree = function() { //{{{
-	//Computes and caches octrees for a node.
-	var octree = this.canvas.octrees[this.name];
-	if (vesl.helpers.isEmptyOrUndefined(octree)) {
-		octree = new GL.Octree(this.mesh);
-	}
-	this.canvas.octrees[this.name] = octree;
-	this.octree = octree;
-} //}}}
-Node.prototype.geometryShader = function() { //{{{
-	//Emulates OpenGL geometry shaders by rendering each point as a mesh.
-	//Parameters:
-	//	Mesh - the geometry to duplicate for each point.
-	//	Vertices - the list of points to shade.
-	//	Indices - (optional) ordered list for non-ordered Vertices objects.
-	
-	var args = Array.prototype.slice.call(arguments);
-	var options = new pairoptions(args.slice(0,args.length));
-
-	var mesh = options.getfieldvalue('Mesh', undefined);
-	var vertices = options.getfieldvalue('Vertices', undefined);
-	var indices = options.getfieldvalue('Indices', undefined);
-	
-	//For handling key-value object arrays, like xcity
-	for (var i = 0; i < vertices.length; i++) {
-		if (!Array.isArray(vertices[i])) {
-			var array = [];
-			var coordinateObject = vertices[i];
-			var j = 0;
-			if (vesl.helpers.isEmptyOrUndefined(indices)) {
-				for (var key in coordinateObject) {
-					console.log(key);
-					array[j++] = coordinateObject[key];
-				}
-			}
-			else {
-				for (var k = 0; k < indices.length; k++) {
-					array[j++] = coordinateObject[indices[k]];
-				}
-			}
-			vertices[i] = array;
-		}
-	}
-	
-	var x = vertices[0];
-	var y = vertices[1];
-	var z = vertices[2];
-	var meshVertices = mesh.getBuffer('vertices').data;
-	var meshIndicies = mesh.getIndexBuffer('triangles').data;
-	var indices = new Uint16Array(meshIndicies.length * x.length);
-	var size = meshVertices.length * x.length / 3;
-	newX = new Float32Array(size);
-	newY = new Float32Array(size);
-	newZ = new Float32Array(size);
-	
-	//For each vertex in vertices, instantiate mesh geomtry centered around that point.
-	for(var i = 0, v = 0, e = 0; i < x.length; i++){
-		var vector = [x[i], y[i], z[i]];
-		for (var j = 0; j < meshVertices.length;) {
-			newX[v] = meshVertices[j++] + x[i];
-			newY[v] = meshVertices[j++] + y[i];
-			newZ[v++] = meshVertices[j++] + z[i];
-		}
-		var offset = i * meshVertices.length / 3;
-		for (var j = 0; j < meshIndicies.length;) {
-			indices[e++] = meshIndicies[j++] + offset;
-		}
-	}
-	
-	this.patch('Faces', indices, 'Vertices', [newX, newY, newZ], 'FaceColor', 'interp');
-} //}}}
-Node.prototype.scaleVertices = function(md, x, y, z, elements, scale, maskObject) { //{{{
-	//Scales and returns vertices x, y, and z by factor scale. Uses md.geometry.scale for heightscaling in 3d meshes.
-	var region = maskObject.region;
-	var mask;
-	var maskScale = maskObject.enabled ? maskObject.scale : 1;
-	
-	//The maskScaleField is the height scaling array.
-	var maskScaleField = maskObject.scaleField;
-	if (Array.isArray(md.geometry.surface) && md.geometry.surface[0] === undefined) {
-		md.geometry.surface = NewArrayFill(x.length, 1);
-	}
-	if (vesl.helpers.isNaN(maskScaleField) || maskScaleField === undefined || (Array.isArray(maskScaleField) && maskScaleField[0]  === undefined)) {
-		maskScaleField = md.geometry.surface;
-	}
-		
-	//Scale in 3D if using globe model, or in 2D otherwise.
-	if (md.mesh.classname() === 'mesh3dsurface') {
-		var element;
-		if (x.length === md.mesh.numberofelements) { //element plot
-			mask = maskObject.enabled ? maskObject.heightmask[region] : NewArrayFill(elements.length, 1);
-			for (var i = 0; i < x.length; i++) {
-				if (maskObject.enabled) { //Scale the element if mask is not enabled, or if it is, only if it is also in the mask
-					element = elements[i];
-					for (var j = 0; j < 3; j++) {
-						xyz = vec3.fromValues(x[element[j] - 1], y[element[j] - 1], z[element[j] - 1]);
-						magnitude = 1 + maskScaleField[element[j] - 1] * mask[i] * scale * maskScale / vec3.length(xyz);
-						vec3.scale(xyz, xyz, magnitude);
-						x[element[j] - 1] = xyz[0];
-						y[element[j] - 1] = xyz[1];
-						z[element[j] - 1] = xyz[2];
-					}
-					
-				}
-			}
-		}
-		else if (x.length === md.mesh.numberofvertices) { //node plot
-			mask = maskObject.enabled ? maskObject.heightmask[region] : NewArrayFill(x.length, 1);
-			for (var i = 0; i < x.length; i++) {
-				if (!maskObject.enabled || mask[i] === 1) { //Scale the node if mask is not enabled, or if it is, only if it is also in the mask
-					xyz = vec3.fromValues(x[i], y[i], z[i]);
-					magnitude = 1 + maskScaleField[i] * scale * maskScale / vec3.length(xyz);
-					vec3.scale(xyz, xyz, magnitude);
-					x[i] = xyz[0];
-					y[i] = xyz[1];
-					z[i] = xyz[2];
-				}
-			}
-		}
-	} else {
-		z = z.slice();
-		mask = maskObject.enabled ? maskObject.heightmask[region] : NewArrayFill(z.length, 1);
-		var zMin = ArrayMin(maskScaleField);
-		for(var i = 0; i < z.length; i++) {
-			if (!maskObject.enabled || mask[i] === 1) { //Scale the element if mask is not enabled, or if it is, only if it is also in the mask
-				z[i] = (z[i] - zMin) * scale + zMin;
-			}
-		}
-	}
-	return [x, y, z];
-} //}}}
-Node.prototype.computeMasks = function() { //{{{
-	//NOTE: Since sets are not yet widely supported, use array with binary values to represent if object is in set.
-	//NOTE: Only support element wise masks for now.
-	var maskObject = this.maskObject;
-	if (!maskObject.enabled) { return; }
-	var region = maskObject.region;
-	var mask = maskObject.mask[region];
-	var maskRegion = []; //new Set()
-	var maskAdjacency = []; //new Set()
-	var adjacentElements;
-	var adjacentElements2;
-	var adjacentNodes;
-	var adjacentNodes2;
-	var adjacencyLength; //length of connectivity array - 3 for elements, many for nodes
-	var index;
-	//For each element in the mask elements array,
-	if (mask.length === md.mesh.numberofelements) { //element plot
-		if (md.mesh.elementconnectivity) {
-			var adjacencyLength = 3;
-			for (var i = 0; i < mask.length; i++) {
-				//Determine if element is in the mask
-				if (mask[i] === 1) {
-					//Then add each element to the adjacency set and mask region set
-					adjacentElements = md.mesh.elementconnectivity[i];
-					for (var j = 0; j < adjacencyLength; j++) {
-						index = adjacentElements[j] - 1;
-						maskAdjacency[index] = true;
-						
-						//Get second level of adjacent elemnents to form solid boundary (first layer look like this: /\/\/\/\/\; second layer looks like this \/\/\/\/\/; combined = |       |
-						adjacentElements2 = md.mesh.elementconnectivity[index];
-						for (var k = 0; k < adjacencyLength; k++) {
-							index = adjacentElements2[k] - 1;
-							maskAdjacency[index] = true;
-						}
-					}
-					maskRegion[i] = true;
-				}
-			}
-		}
-	}
-	else if (mask.length === md.mesh.numberofvertices) { //node plot
-		if (md.mesh.nodeconnectivity) {
-			var adjacencyLength = md.mesh.nodeconnectivity[0].length;
-			for (var i = 0; i < mask.length; i++) {
-				//Determine if node is in the mask
-				if (mask[i] === 1) {
-					//Then add each node to the adjacency set and mask region set
-					adjacentNodes = md.mesh.nodeconnectivity[i];
-					for (var j = 0; j < adjacencyLength; j++) {
-						index = adjacentNode[j];
-						maskAdjacency[index] = true;
-						
-						//Get second level of adjacent elemnents to form solid boundary (first layer look like this: /\/\/\/\/\; second layer looks like this \/\/\/\/\/; combined = |       |
-						adjacentNodes2 = md.mesh.nodeconnectivity[index];
-						for (var k = 0; k < adjacencyLength; k++) {
-							index = adjacentNodes2[k];
-							maskAdjacency[index] = true;
-						}
-					}
-					maskRegion[i] = true;
-				}
-			}
-		}
-	}
-	
-	//Then subtract the mask elements array from the mask adjacency array.
-	var maskBoundary = [];
-	for (var i in maskAdjacency) {
-		if (!(i in maskRegion)) {
-			maskBoundary[i] = true;
-		}
-	}
-	this.maskBoundary = maskBoundary;
-	
-	//For each element not in any mask region (default to mask[0] in slr-gfm), change color to mask color.
-	var maskAll = maskObject.mask[0];
-	this.maskAll = maskAll;
-} //}}}
-Node.prototype.queryCoordMask = function(index) { //{{{
-	//Calculate UV coordinate offsets for special color texture lookup.
-	if (!this.maskObject.enabled) {
-		return 1.0 
-	}
-	else {
-		//var sections = 3; //Object.keys(maskObject.colors).length + 1
-		//var boundaryCoord = 0.5 / sections;
-		//var maskCoord = 1.5 / sections;
-		//var textureCoord = 2.5 / sections;
-		if (index in this.maskBoundary) {
-			return 0.166666667; //boundaryCoord - edge color
-		}
-		else if (this.maskAll[index] === 0) {
-			return 0.5; //maskCoord - masked color
-		}
-		else {
-			return 0.833333333; //textureCoord - default colormap
-		}
-	}
-} //}}}
-Node.prototype.mergeVertices = function(x1, y1, z1, elements1, x2, y2, z2, elements2) { //{{{
-	//Merges and returns two sets of indexed xyz vertices.
-	elements2 = elements2.slice();
-	for (var i = 0, offset = x1.length; i < elements2.length; i++) {
-		elements2[i] = [elements2[i][0] + offset, elements2[i][1] + offset, elements2[i][2] + offset];
-	}
-	return {x:x1.concat(x2), y:y1.concat(y2), z:z1.concat(z2), elements:elements1.concat(elements2)};
-} //}}}
-//}}}
