Index: /issm/trunk-jpl/src/m/plot/applyoptions.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/applyoptions.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/applyoptions.js	(revision 21231)
@@ -35,11 +35,8 @@
 			} //}}}
 			//Initialize colorbar canvas {{{
-			
 			ccanvasid = options.getfieldvalue('colorbarid',options.getfieldvalue('canvasid').replace('canvas','colorbar-canvas'));			
 			ccanvas = $('#'+ccanvasid)[0];
 			cwidth = ccanvas.width*options.getfieldvalue('colorbarwidth',1);
 			cheight = ccanvas.height*options.getfieldvalue('colorbarheight',1);
-			//ccanvas.width = cwidth;
-			//ccanvas.height = cheight;
 			ccontext = ccanvas.getContext('2d');
 			ccontext.clearRect(0,0, cwidth, cheight);
@@ -68,6 +65,4 @@
 			clabelsid = options.getfieldvalue('colorbarid', ccanvasid).replace('canvas','labels');
 			clabels = $('#'+clabelsid);
-			//clabels.height(cheight);
-			//clabels.css({'color':options.getfieldvalue('colorbarfontcolor','black'), 'font-size':options.getfieldvalue('colorbarfontsize',18)+'px'});
 			if (colorbarinnerlabels=='on') {
 				clabels.removeClass('sim-colorbar-labels-outer');
@@ -96,24 +91,17 @@
 			ctitleid = options.getfieldvalue('colorbarid', ccanvasid).replace('canvas','heading');
 			ctitle = $('#'+ctitleid);
-			//ctitle.width(cwidth);
-			if (options.exist('colorbartitle')) {
-				ctitle.html(options.getfieldvalue('colorbartitle'));
-				//ctitle.css({'color':options.getfieldvalue('colorbarfontcolor','black'), 'font-size':options.getfieldvalue('colorbarfontsize',18)+'px'});
-			} //}}}
+			if (options.exist('colorbartitle')) { ctitle.html(options.getfieldvalue('colorbartitle')); }
+			//}}}
 		} 
 	} //}}}
 	//texture canvas //{{{
-	var tcontext,tcanvas,tcanvasid,tcanvashtml,tURL,tgradient;
+	var tcontext,tcanvas,tcanvasid,tURL,tgradient;
 	tcanvasid = 'texturecanvas';
-	var tcanvashtml = document.getElementById(tcanvasid);
-	if (tcanvashtml == null) {
-		tcanvas = $('<canvas id="texturecanvas" width="256" height="256"></canvas>').insertAfter('#'+String(options.getfieldvalue('canvasid')));
-		tcanvas.css({'display':'none'});
-		tcanvashtml = document.getElementById(tcanvasid);
+	var tcanvas = document.getElementById(tcanvasid);
+	if (tcanvas == null) {
+		$('<canvas id="texturecanvas" width="256" height="256" style="display: none;"></canvas>').insertAfter('#'+String(options.getfieldvalue('canvasid')));
+		tcanvas = document.getElementById(tcanvasid);
 	}
-	else {
-		tcanvas = $('#' + tcanvasid);
-	}
-	tcontext = tcanvashtml.getContext('2d');
+	tcontext = tcanvas.getContext('2d');
 	tgradient = tcontext.createLinearGradient(0,0,0,256);
 		
@@ -128,11 +116,13 @@
 	tcontext.fillStyle = tgradient;
 	tcontext.fillRect(0,0,256,256);
-	tURL = tcanvashtml.toDataURL();
-	node['texture'] = initTexture(gl,tURL);
+	tURL = tcanvas.toDataURL();
+	node.texture = initTexture(gl,tURL);
+	node.textureCanvas = tcanvas;
+	node.caxis = options.getfieldvalue('caxis',[ArrayMin(data),ArrayMax(data)]);
 	//}}}
 	//expdisp contours {{{
 	if (options.exist('expdisp')) {
-		canvas.nodes['expdisp'] = Node(gl,options);
-		var node = canvas.nodes['expdisp'];
+		canvas.nodes.expdisp = Node(gl,options);
+		var node = canvas.nodes.expdisp;
 		
 		//declare variables:  {{{
@@ -147,10 +137,10 @@
 		
 		//Process data and model
-		var x = options.getfieldvalue('expdisp')['x'];
-		var y = options.getfieldvalue('expdisp')['y'];
+		var x = options.getfieldvalue('expdisp').x;
+		var y = options.getfieldvalue('expdisp').y;
 		var z = Array.apply(null, Array(x.length)).map(Number.prototype.valueOf,0);
 		
-		if (options.getfieldvalue('expdisp')['z']) {
-			z = options.getfieldvalue('expdisp')['z'];
+		if (options.getfieldvalue('expdisp').z) {
+			z = options.getfieldvalue('expdisp').z;
 		}
 		//}}}
@@ -173,13 +163,13 @@
 		//Compute scaling: //{{{
 		var scale = 1 / (xmax - xmin);
-		node['shaderName'] = 'colored';
-		node['shader'] = gl['shaders'][node['shaderName']]['program'];
-		node['scale'] = [scale, scale, scale*options.getfieldvalue('heightscale',1)];
-		node['translation'] = [(xmin + xmax) / (-2 / scale), (ymin + ymax) / (-2 / scale), (zmin + zmax) / (-2 / scale)];
-		node['modelMatrix'] = recalculateModelMatrix(node);
-		node['drawMode'] = gl.LINE_LOOP;
-		node['drawOrder'] = 0;
-		node['useIndexBuffer'] = false;
-		node['disableDepthTest'] = true;
+		node.shaderName = 'colored';
+		node.shader = gl.shaders[node.shaderName].program;
+		node.scale = [scale, scale, scale*options.getfieldvalue('heightscale',1)];
+		node.translation = [(xmin + xmax) / (-2 / scale), (ymin + ymax) / (-2 / scale), (zmin + zmax) / (-2 / scale)];
+		node.modelMatrix = updateModelMatrix(node);
+		node.drawMode = gl.LINE_LOOP;
+		node.drawOrder = 0;
+		node.useIndexBuffer = false;
+		node.disableDepthTest = true;
 		//}}}
 
@@ -206,11 +196,11 @@
 
 		//Initalize buffers:
-		node['arrays'] = [vertices, colors];
-		node['buffers'] = initBuffers(gl, node['arrays']);
+		node.arrays = [vertices, colors];
+		node.buffers = initBuffers(gl, node.arrays);
 	} //}}}
 	//cloud of points {{{
 	if (options.exist('cloud')) {
-		canvas.nodes['cloud'] = Node(gl,options);
-		var node = canvas.nodes['cloud'];
+		canvas.nodes.cloud = Node(gl,options);
+		var node = canvas.nodes.cloud;
 
 		//declare variables:  {{{
@@ -225,10 +215,10 @@
 		
 		//Process data and model
-		var x = options.getfieldvalue('cloud')['x'];
-		var y = options.getfieldvalue('cloud')['y'];
+		var x = options.getfieldvalue('cloud').x;
+		var y = options.getfieldvalue('cloud').y;
 		var z = Array.apply(null, Array(x.length)).map(Number.prototype.valueOf,0);
 		
-		if (options.getfieldvalue('cloud')['z']) {
-			z = options.getfieldvalue('cloud')['z'];
+		if (options.getfieldvalue('cloud').z) {
+			z = options.getfieldvalue('cloud').z;
 		}
 		//}}}
@@ -251,13 +241,13 @@
 		//Compute scaling: //{{{
 		var scale = 1 / (xmax - xmin);
-		node['shaderName'] = 'colored';
-		node['shader'] = gl['shaders'][node['shaderName']]['program'];
-		node['scale'] = [scale, scale, scale*options.getfieldvalue('heightscale',1)];
-		node['translation'] = [(xmin + xmax) / (-2 / scale), (ymin + ymax) / (-2 / scale), (zmin + zmax) / (-2 / scale)];
-		node['modelMatrix'] = recalculateModelMatrix(node);
-		node['drawMode'] = gl.POINTS;
-		node['drawOrder'] = 0;
-		node['useIndexBuffer'] = false;
-		node['disableDepthTest'] = true;
+		node.shaderName = 'colored';
+		node.shader = gl.shaders[node.shaderName].program;
+		node.scale = [scale, scale, scale*options.getfieldvalue('heightscale',1)];
+		node.translation = [(xmin + xmax) / (-2 / scale), (ymin + ymax) / (-2 / scale), (zmin + zmax) / (-2 / scale)];
+		node.modelMatrix = updateModelMatrix(node);
+		node.drawMode = gl.POINTS;
+		node.drawOrder = 0;
+		node.useIndexBuffer = false;
+		node.disableDepthTest = true;
 		//}}}
 
@@ -284,6 +274,6 @@
 
 		//Initalize buffers:
-		node['arrays'] = [vertices, colors];
-		node['buffers'] = initBuffers(gl, node['arrays']);
+		node.arrays = [vertices, colors];
+		node.buffers = initBuffers(gl, node.arrays);
 	} //}}}
 	
@@ -314,6 +304,6 @@
 			for (text in textlabels) {
 				textlabel = textlabels[text];
-				mat4.multiply(mvpMatrix, canvas.cameraMatrix, canvas.nodes['overlay']['modelMatrix']);
-				textposition = vec3.transformMat4(textposition, textlabel['pos'], mvpMatrix);
+				mat4.multiply(mvpMatrix, canvas.cameraMatrix, canvas.nodes.overlay.modelMatrix);
+				textposition = vec3.transformMat4(textposition, textlabel.pos, mvpMatrix);
 				if (textposition[2] > 1) { //clip coordinates with z > 1
 					continue;
@@ -325,6 +315,6 @@
 				textcontext.textAlign = 'center';
 				textcontext.textBaseline = 'middle';
-				textcontext.fillText(textlabel['text'], textcoordinates[0], textcoordinates[1]);
-				textcontext.strokeText(textlabel['text'], textcoordinates[0], textcoordinates[1]);
+				textcontext.fillText(textlabel.text, textcoordinates[0], textcoordinates[1]);
+				textcontext.strokeText(textlabel.text, textcoordinates[0], textcoordinates[1]);
 			}
 		}
@@ -362,15 +352,15 @@
 			var node = Node(gl);
 			canvas.nodes[canvas.nodes.length] = node;
-			node["name"] = "atmosphere";
-			node["shaderName"] = "SkyFromSpace";
-			node["shader"] = gl["shaders"][node["shaderName"]];
-			node["drawOrder"] = 1;
-			node["enableCullFace"] = true;
-			node["mesh"] = GL.Mesh.icosahedron({size:6371000*atmosphereScale,subdivisions:6});
-			node["useIndexBuffer"] = false;
-			node["rotation"] = [0, 0, 0];
-			node["translation"] = translation;
-			node["center"] = [0, 0, 0];
-			node["modelMatrix"] = recalculateModelMatrix(node);
+			node.name = "atmosphere";
+			node.shaderName = "SkyFromSpace";
+			node.shader = gl.shaders[node.shaderName];
+			node.drawOrder = 1;
+			node.enableCullFace = true;
+			node.mesh = GL.Mesh.icosahedron({size:6371000*atmosphereScale,subdivisions:6});
+			node.useIndexBuffer = false;
+			node.rotation = [0, 0, 0];
+			node.translation = translation;
+			node.center = [0, 0, 0];
+			updateModelMatrix(node);
 		}
 		if (options.getfieldvalue('render',[]).indexOf('space')!=-1) {	
@@ -378,16 +368,16 @@
 			node = Node(gl);
 			canvas.nodes[canvas.nodes.length] = node;
-			node["name"] = "skysphere";
-			node["shaderName"] = "Textured";
-			node["shader"] = gl["shaders"][node["shaderName"]];
-			node["drawOrder"] = 2;
-			node["enableCullFace"] = true;
-			node["mesh"] = GL.Mesh.sphere({size:6371000*10});
-			node["texture"] = initTexture(gl,'../../../js/textures/TychoSkymapII_t4_2k.jpg');
-			node["useIndexBuffer"] = false;
-			node["rotation"] = [0, 0, 0];
-			node["translation"] = translation;
-			node["center"] = [0, 0, 0];
-			node["modelMatrix"] = recalculateModelMatrix(node);
+			node.name = "skysphere";
+			node.shaderName = "Textured";
+			node.shader = gl.shaders[node.shaderName];
+			node.drawOrder = 2;
+			node.enableCullFace = true;
+			node.mesh = GL.Mesh.sphere({size:6371000*10});
+			node.texture = initTexture(gl,'../../../js/textures/TychoSkymapII_t4_2k.jpg');
+			node.useIndexBuffer = false;
+			node.rotation = [0, 0, 0];
+			node.translation = translation;
+			node.center = [0, 0, 0];
+			updateModelMatrix(node);
 		}
 	} //}}}
Index: /issm/trunk-jpl/src/m/plot/plot_mesh.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_mesh.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/plot_mesh.js	(revision 21231)
@@ -62,18 +62,18 @@
 	var node = Node(gl);
 	canvas.nodes[canvas.nodes.length] = node;
-	node["name"] = "mesh";
-	node["shaderName"] = "Colored";
-	node["shader"] = gl["shaders"][node["shaderName"]];
-	node["lineWidth"] = options.getfieldvalue('linewidth',1);
-	node["scale"] = [1, 1, matrixscale];
-	node["rotation"] = [-90, 0, 0];
-	node["translation"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["center"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["modelMatrix"] = recalculateModelMatrix(node);
-	node["drawMode"] = gl.LINES;
-	node["drawOrder"] = 0;
-	node["maskEnabled"] = options.getfieldvalue('innermask','off') == 'on';
-	node["maskHeight"] = options.getfieldvalue('innermaskheight',150.0)*options.getfieldvalue('heightscale',1);
-	node["maskColor"] = options.getfieldvalue('innermaskcolor',[0.0,0.0,1.0,1.0]);
+	node.name = "mesh";
+	node.shaderName = "Colored";
+	node.shader = gl.shaders[node.shaderName];
+	node.lineWidth = options.getfieldvalue('linewidth',1);
+	node.scale = [1, 1, matrixscale];
+	node.rotation = [-90, 0, 0];
+	node.translation = [0, 0, 0];
+	node.center = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
+	node.drawMode = gl.LINES;
+	node.drawOrder = 0;
+	node.maskEnabled = options.getfieldvalue('innermask','off') == 'on';
+	node.maskHeight = options.getfieldvalue('innermaskheight',150.0)*options.getfieldvalue('heightscale',1);
+	node.maskColor = options.getfieldvalue('innermaskcolor',[0.0,0.0,1.0,1.0]);
+	updateModelMatrix(node);
 
 	//retrieve some options
@@ -136,4 +136,4 @@
 	}
 	//}}}
-	node["mesh"] = GL.Mesh.load({vertices:vertices, colors:colors, indices:indices}, null, null, gl);
+	node.mesh = GL.Mesh.load({vertices:vertices, colors:colors, triangles:indices}, null, null, gl);
 }
Index: /issm/trunk-jpl/src/m/plot/plot_overlay.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_overlay.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/plot_overlay.js	(revision 21231)
@@ -61,18 +61,18 @@
 	var node = Node(gl);
 	canvas.nodes[canvas.nodes.length] = node;
-	node["name"] = "overlay";
-	node["shaderName"] = (options.getfieldvalue('render',[]).indexOf('ground')!=-1) ? "GroundFromSpace" : "Textured";
-	node["shader"] = gl["shaders"][node["shaderName"]];
-	node["scale"] = [1, 1, matrixscale];
-	node["rotation"] = [-90, 0, 0];
-	node["translation"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["center"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["modelMatrix"] = recalculateModelMatrix(node);
-	node["texture"] = initTexture(gl,options.getfieldvalue('overlay_image'));
-	node["alpha"] = options.getfieldvalue('outeralpha',1.0);
-	node["drawOrder"] = 1;
-	node["maskEnabled"] = options.getfieldvalue('outermask','off') == 'on';
-	node["maskHeight"] = options.getfieldvalue('outermaskheight',150.0);
-	node["maskColor"] = options.getfieldvalue('outermaskcolor',[0.0,0.0,1.0,1.0]);
+	node.name = "overlay";
+	node.shaderName = (options.getfieldvalue('render',[]).indexOf('ground')!=-1) ? "GroundFromSpace" : "Textured";
+	node.shader = gl.shaders[node.shaderName];
+	node.scale = [1, 1, matrixscale];
+	node.rotation = [-90, 0, 0];
+	node.translation = [0, 0, 0];
+	node.center = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
+	node.texture = initTexture(gl,options.getfieldvalue('overlay_image'));
+	node.alpha = options.getfieldvalue('outeralpha',1.0);
+	node.drawOrder = 1;
+	node.maskEnabled = options.getfieldvalue('outermask','off') == 'on';
+	node.maskHeight = options.getfieldvalue('outermaskheight',150.0);
+	node.maskColor = options.getfieldvalue('outermaskcolor',[0.0,0.0,1.0,1.0]);
+	updateModelMatrix(node);
 	
 	//Handle outer radaroverlay
@@ -98,5 +98,5 @@
 		zmax = zlim[1];
 		
-		node["center"] = [node["center"][0], node["center"][1], -zmax];
+		node.center = [node.center[0], node.center[1], -zmax];
 	}
 	
@@ -133,6 +133,6 @@
 			vertices[vertices.length] = vertex[2];
 			
-			texcoords[texcoords.length] = degrees(Math.atan2(vertex[1], vertex[0])) / 360 + 0.5;
-			texcoords[texcoords.length] = degrees(Math.asin(vertex[2] / magnitude)) / 180 + 0.5;
+			texcoords[texcoords.length] = Math.atan2(vertex[1], vertex[0]) / (2 * Math.PI) + 0.5;
+			texcoords[texcoords.length] = Math.asin(vertex[2] / magnitude) / Math.PI + 0.5;
 		}
 		else {
@@ -159,4 +159,4 @@
 		indices[indices.length] = element[2];
 	}
-	node["mesh"] = GL.Mesh.load({vertices:vertices, coords:texcoords, indices:indices}, null, null, gl);
+	node.mesh = GL.Mesh.load({vertices:vertices, coords:texcoords, triangles:indices}, null, null, gl);
 }
Index: /issm/trunk-jpl/src/m/plot/plot_quiver.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_quiver.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/plot_quiver.js	(revision 21231)
@@ -63,19 +63,19 @@
 	var node = Node(gl);
 	canvas.nodes[canvas.nodes.length] = node;
-	node["name"] = "quiver";
-	node["shaderName"] = "Colored";
-	node["shader"] = gl["shaders"][node["shaderName"]];
-	node["lineWidth"] = options.getfieldvalue('linewidth',1);
-	node["scale"] = [1, 1, matrixscale];
-	node["rotation"] = [-90, 0, 0];
-	node["translation"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["center"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["modelMatrix"] = recalculateModelMatrix(node);
-	node["drawMode"] = gl.LINES;
-	node["useIndexBuffer"] = false;
-	node["drawOrder"] = 0;
-	node["maskEnabled"] = options.getfieldvalue('innermask','off') == 'on';
-	node["maskHeight"] = options.getfieldvalue('innermaskheight',150.0)*options.getfieldvalue('heightscale',1);
-	node["maskColor"] = options.getfieldvalue('innermaskcolor',[0.0,0.0,1.0,1.0]);
+	node.name = "quiver";
+	node.shaderName = "Colored";
+	node.shader = gl.shaders[node.shaderName];
+	node.lineWidth = options.getfieldvalue('linewidth',1);
+	node.scale = [1, 1, matrixscale];
+	node.rotation = [-90, 0, 0];
+	node.translation = [0, 0, 0];
+	node.center = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
+	node.drawMode = gl.LINES;
+	node.useIndexBuffer = false;
+	node.drawOrder = 0;
+	node.maskEnabled = options.getfieldvalue('innermask','off') == 'on';
+	node.maskHeight = options.getfieldvalue('innermaskheight',150.0)*options.getfieldvalue('heightscale',1);
+	node.maskColor = options.getfieldvalue('innermaskcolor',[0.0,0.0,1.0,1.0]);
+	updateModelMatrix(node);
 
 	//retrieve some options
@@ -134,4 +134,4 @@
 	}
 	//}}}
-	node["mesh"] = GL.Mesh.load({vertices:vertices, colors:colors}, null, null, gl);
+	node.mesh = GL.Mesh.load({vertices:vertices, colors:colors}, null, null, gl);
 }
Index: /issm/trunk-jpl/src/m/plot/plot_unit.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_unit.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/plot_unit.js	(revision 21231)
@@ -63,20 +63,20 @@
 	var node = Node(gl);
 	canvas.nodes[canvas.nodes.length] = node;
-	node["name"] = "unit";
-	node["shaderName"] = "Textured";
-	node["shader"] = gl["shaders"][node["shaderName"]];
-	node["scale"] = [1, 1, matrixscale];
-	node["rotation"] = [-90, 0, 0];
-	node["translation"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["center"] = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
-	node["modelMatrix"] = recalculateModelMatrix(node);
-	node["alpha"] = options.getfieldvalue('alpha',1.0);
-	node["drawOrder"] = 0;
-	node["maskEnabled"] = options.getfieldvalue('innermask','off') == 'on';
-	node["maskHeight"] = options.getfieldvalue('innermaskheight',150.0);
-	node["maskColor"] = options.getfieldvalue('innermaskcolor',[0.0,0.0,1.0,1.0]);
-	node["enabled"] = options.getfieldvalue('nodata','off') == 'off';
-	vec3.add(canvas.translation, canvas.translation, node["center"]);
-	
+	canvas.unitNode = node;
+	node.name = "unit";
+	node.shaderName = "Textured";
+	node.shader = gl.shaders[node.shaderName];
+	node.scale = [1, 1, matrixscale];
+	node.rotation = [-90, 0, 0];
+	node.translation = [0, 0, 0];
+	node.center = [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2];
+	node.alpha = options.getfieldvalue('alpha',1.0);
+	node.drawOrder = 1;
+	node.maskEnabled = options.getfieldvalue('innermask','off') == 'on';
+	node.maskHeight = options.getfieldvalue('innermaskheight',150.0);
+	node.maskColor = options.getfieldvalue('innermaskcolor',[0.0,0.0,1.0,1.0]);
+	node.enabled = options.getfieldvalue('nodata','off') == 'off';
+	updateModelMatrix(node);
+
 	switch(datatype){
 		//element plot {{{
@@ -133,5 +133,4 @@
 					texcoords[tindex++] = 0.5;
 					texcoords[tindex++] = clamp((data[i] - datamin) / datadelta, 0.0, 1.0);
-					
 				}
 
@@ -146,5 +145,6 @@
 				}
 			}
-			node["mesh"] = GL.Mesh.load({vertices:vertices, coords:texcoords, indices:indices});
+			node.mesh = GL.Mesh.load({vertices:vertices, coords:texcoords, triangles:indices}, null, null, gl);
+			node.mesh.octree = new GL.Octree(node.mesh);
 			break;
 		//}}}
@@ -222,33 +222,38 @@
 			
 				//Initialize movie loop
-				node["movieInterval"] = 1000 / canvas.moviefps;
-				node["movieTimestamps"] = timestamps;
-				node["movieLength"] = timestamps.length;
-				node["movieFrame"] = 0;
-				if (canvas["movieHandler"])	clearInterval(canvas["movieHandler"]);
-				canvas["movieHandler"] = setInterval(function () {
-						node["movieFrame"] = canvas["movieFrame"];
-						if (canvas["moviePlay"] && canvas["movieIncrement"]) {
-							if (canvas["movieReverse"]) node["movieFrame"] = (((node["movieFrame"] - 1) % node["movieLength"]) + node["movieLength"]) % node["movieLength"]; //Handle negative modulus
-							else node["movieFrame"] = (((node["movieFrame"] + 1) % node["movieLength"]) + node["movieLength"]) % node["movieLength"]; //Handle negative modulus
+				node.movieInterval = 1000 / canvas.moviefps;
+				node.movieTimestamps = timestamps;
+				node.movieLength = timestamps.length;
+				node.movieFrame = 0;
+				if (canvas.movieHandler) { clearInterval(canvas.movieHandler); }
+				canvas.movieHandler = setInterval(function () {
+						node.movieFrame = canvas.movieFrame;
+						if (canvas.moviePlay && canvas.movieIncrement) {
+							if (canvas.movieReverse) {node.movieFrame = (((node.movieFrame - 1) % node.movieLength) + node.movieLength) % node.movieLength; }
+							else { node.movieFrame = (((node.movieFrame + 1) % node.movieLength) + node.movieLength) % node.movieLength; }
 						}
-						if (canvas["timeLabel"]) canvas["timeLabel"].html(node["movieTimestamps"][node["movieFrame"]].toFixed(0) + " " + options.getfieldvalue("movietimeunit","yr"));
-						if (canvas["progressBar"]) {
-							canvas["progressBar"].val(node["movieFrame"]);
-							canvas["progressBar"].slider('refresh');
+						if (canvas.progressBar) {
+							canvas.progressBar.val(node.movieFrame);
+							canvas.progressBar.slider('refresh');
 						}
-						canvas["movieFrame"] = node["movieFrame"];
-						var buffer = node["mesh"].getBuffer("coords");
-						buffer.data = texcoords[node["movieFrame"]];
+						if (canvas.timeLabel) { canvas.timeLabel.html(node.movieTimestamps[node.movieFrame].toFixed(0) + " " + options.getfieldvalue("movietimeunit","yr")); }
+
+						var buffer = node.mesh.getBuffer("coords");
+						buffer.data = texcoords[node.movieFrame];
 						buffer.upload(canvas.gl.DYNAMIC_DRAW);
-					}, node["movieInterval"]);
-				if (canvas["progressBar"]) {
-					canvas["movieFrame"] = 0;
-					canvas["progressBar"].val(0);
-					canvas["progressBar"].attr('max', node["movieLength"]-1);
-					canvas["progressBar"].slider('refresh');
-				}
-			}
-			node["mesh"] = GL.Mesh.load({vertices:vertices, coords:texcoords[0], indices:indices}, null, null, gl);
+						node.mesh.octree = new GL.Octree(node.mesh);
+
+						canvas.movieFrame = node.movieFrame;
+					}, node.movieInterval);
+				if (canvas.progressBar) {
+					canvas.movieFrame = 0;
+					canvas.progressBar.val(0);
+					canvas.progressBar.attr('max', node.movieLength-1);
+					canvas.progressBar.slider('refresh');
+				}
+				
+			}
+			node.mesh = GL.Mesh.load({vertices:vertices, coords:texcoords[0], triangles:indices}, null, null, gl);
+			node.mesh.octree = new GL.Octree(node.mesh);
 			break;
 		//}}}
Index: /issm/trunk-jpl/src/m/plot/plotdoc.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plotdoc.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/plotdoc.js	(revision 21231)
@@ -31,4 +31,5 @@
 	console.log('       "colormap": same as standard matlab option (default "jet", ex: "hsv","cool","spring","gray","Ala","Rignot",...)');
 	console.log('       "controlsensitivity": sensitivty of view/zoom changes as a percentage of default (default 1, ex: 0.5, 2.75)');
+	console.log('       "datamarkers": toggle data marker displays (default "on", ex: "on", "off")');
 	console.log('       "displayview": print view value to console (default "off", ex: "on", "off")');
 	console.log('       "displayzoom": print zoom value to console (default "off", ex: "on", "off")');
Index: /issm/trunk-jpl/src/m/plot/webgl.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/webgl.js	(revision 21230)
+++ /issm/trunk-jpl/src/m/plot/webgl.js	(revision 21231)
@@ -10,5 +10,5 @@
 		canvas.gl = initWebGL(canvas,options);
 		canvas.nodes = [];
-		if (canvas.drawHandler)	window.cancelAnimationFrame(canvas.drawHandler);
+		if (canvas.drawHandler)	{ window.cancelAnimationFrame(canvas.drawHandler); }
 		draw(canvas,options);
 		canvas.initialized = true;
@@ -21,4 +21,31 @@
 		if (!canvas.gl) {
 			gl = GL.create({canvas:canvas});
+			// Enable depth testing
+			gl.enable(gl.DEPTH_TEST);
+			// Near things obscure far things
+			gl.depthFunc(gl.LEQUAL);
+			// Enable color blending/overlay
+			gl.enable(gl.BLEND);
+			// Enable face culling
+			gl.enable(gl.CULL_FACE); 
+			gl.cullFace(gl.FRONT);
+			// Load shaders and store them in gl object
+			gl.shaders = loadShaders(gl);
+			
+			// Add event listeners for canvas
+			var displayview = options.getfieldvalue('displayview','off') == 'on';
+			var displayzoom = options.getfieldvalue('displayzoom','off') == 'on';
+			var mc = new Hammer.Manager(canvas);
+			
+			mc.add( new Hammer.Tap({ event: 'singletap' }) );
+			mc.add(new Hammer.Pan({threshold:0, pointers:0}));
+			mc.add(new Hammer.Pinch({threshold:0})).recognizeWith(mc.get('pan'));
+			mc.on('singletap', 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('mousemove', function (ev) {onTap(ev,canvas);}, false);
+			canvas.addEventListener('mousewheel', function (ev) {onZoom(ev,canvas,displayzoom)}, false);
+			canvas.addEventListener('DOMMouseScroll', function (ev) {onZoom(ev,canvas,displayzoom)}, false);
 		}
 		else {
@@ -31,58 +58,42 @@
 	}
 	
-	// Enable depth testing
-	gl.enable(gl.DEPTH_TEST);
-	// Near things obscure far things
-	gl.depthFunc(gl.LEQUAL);
-	// Enable color blending/overlay
-	gl.enable(gl.BLEND);
-	// Enable face culling
-	gl.enable(gl.CULL_FACE);
-	gl.cullFace(gl.FRONT);
-	
-	// Load shaders and store them in gl object
-	gl.shaders = loadShaders(gl);
-	
 	// Add context state variables
 	//TODO:Group variables in objects for organization and naming
 	canvas.gl = gl;
-	canvas.zoomBounds = options.getfieldvalue('zoomlim',[0.001,100.0]);
-	canvas.zoom = clamp(options.getfieldvalue('zoom',1.0), canvas.zoomBounds[0], canvas.zoomBounds[1]);
-	canvas.zoomLast = canvas.zoom;
 	canvas.cameraPosition = vec3.create();
 	canvas.cameraMatrix = mat4.create();
-	canvas.vInverseMatrix = mat4.create();
-	canvas.translation = options.getfieldvalue('origin',[0,0,0]);
-	canvas.viewPanning = options.getfieldvalue('enablepanning','off') == 'on';
-	canvas.view = options.getfieldvalue('view',[0,90]); //0 azimuth - up is north, 90 elevation - looking straight down
-	canvas.rotation = canvas.view;
-	canvas.rotationAzimuthBounds = options.getfieldvalue('azlim',[0,360]);
-	canvas.rotationElevationBounds = options.getfieldvalue('ellim',[-180,180]);
 	canvas.controlSensitivity = options.getfieldvalue('controlsensitivity',1);
-	canvas.twod = options.getfieldvalue('2d','off') == 'on';
+	canvas.dataMarkersAllowed = options.getfieldvalue('datamarkers','off') == 'on';
+	canvas.dataMarkersEnabled = true; //if data marker feature is on, user can toggle feature on and off
+	canvas.inverseCameraMatrix = mat4.create();
+	canvas.id = options.getfieldvalue('canvasid','.sim-canvas');
 	canvas.moviePlay = true;
 	canvas.movieReverse = false;
 	canvas.movieIncrement = true;
 	canvas.moviefps = options.getfieldvalue('moviefps',5);
+	canvas.rotation = options.getfieldvalue('view',[0,90]); //0 azimuth, 90 elevation
+	canvas.rotationAzimuthBounds = options.getfieldvalue('azlim',[0,360]);
+	canvas.rotationElevationBounds = options.getfieldvalue('ellim',[-180,180]);
+	canvas.translation = options.getfieldvalue('origin',[0,0,0]);
+	canvas.twod = options.getfieldvalue('2d','off') == 'on';
+	canvas.view = options.getfieldvalue('view',[0,90]);
+	canvas.viewPanning = options.getfieldvalue('enablepanning','off') == 'on';
+	canvas.vInverseMatrix = mat4.create();
+	canvas.zoomBounds = options.getfieldvalue('zoomlim',[0.001,100.0]);
+	canvas.zoom = clamp(options.getfieldvalue('zoom',1.0), canvas.zoomBounds[0], canvas.zoomBounds[1]);
+	canvas.zoomLast = canvas.zoom;
 	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 curent canvas ",canvas));
-	
-	// Add event listeners for canvas
-	var displayview = options.getfieldvalue('displayview','off') == 'on';
-	var displayzoom = options.getfieldvalue('displayzoom','off') == 'on';
-	var mc = new Hammer.Manager(canvas);
-	
-	mc.add(new Hammer.Tap());
-    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);
+	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 curent canvas ',canvas)); }
 	
 	return gl;
+} //}}}
+function loadShaders(gl) { //{{{
+	var shaders = {};
+	shaders.Colored = new GL.Shader.fromURL('../../../js/shaders/Colored.vsh', '../../../js/shaders/Colored.fsh', null, gl);
+	shaders.Textured = new GL.Shader.fromURL('../../../js/shaders/Textured.vsh', '../../../js/shaders/Textured.fsh', null, gl);
+	shaders.SkyFromSpace = new GL.Shader.fromURL('../../../js/shaders/SkyFromSpace.vert', '../../../js/shaders/SkyFromSpace.frag', null, gl);
+	shaders.GroundFromSpace = new GL.Shader.fromURL('../../../js/shaders/GroundFromSpace.vert', '../../../js/shaders/GroundFromSpace.frag', null, gl);
+	return shaders;
 } //}}}
 function initTexture(gl,imageSource) { //{{{
@@ -106,7 +117,7 @@
 		maskColor:vec4.fromValues(0.0, 0.0, 1.0, 1.0),
 		mesh:null,
-		name:"node",
-		shaderName:"Colored",
-		shader:gl.shaders["Colored"],
+		name:'node',
+		shaderName:'Colored',
+		shader:gl.shaders.Colored,
 		texture:null,
 		useIndexBuffer:true,
@@ -115,61 +126,54 @@
 		rotation:vec3.create(),
 		translation:vec3.create(),
-		modelMatrix:mat4.create()
+		modelMatrix:mat4.create(),
+		rotationMatrix:mat4.create(),
+		inverseModelMatrix:mat4.create(),
+		inverseRotationMatrix:mat4.create()
 	};
 } //}}}
-function debugNodes(canvasid) {
+function debugNodes(canvasid) { //{{{
+	var canvasid = canvasid || '.sim-canvas';
 	var nodes = $(canvasid)[0].nodes;
-	console.log(canvasid, "Nodes:");
+	console.log(canvasid, 'Nodes:');
 	for (var node in nodes) {
-		console.log("name", nodes[node]["name"], "translation", nodes[node]["translation"], "center", nodes[node]["center"], "rotation", nodes[node]["rotation"]);
+		console.log('name: ', nodes[node].name, ' node: ', nodes[node], ' mesh: ', nodes[node].mesh, ' translation: ', nodes[node].translation, ' center:', nodes[node].center, ' rotation:', nodes[node].rotation);
 	}
 	return nodes;
-}
-function recalculateModelMatrix(node) { //{{{
+} //}}}
+function updateModelMatrix(node) { //{{{
 	var modelMatrix = mat4.create();
 
 	var translationMatrix = mat4.create();
-	mat4.translate(translationMatrix, translationMatrix, [-node["center"][0],-node["center"][1],-node["center"][2]]); //scale/rotation centering
+	mat4.translate(translationMatrix, translationMatrix, [-node.center[0],-node.center[1],-node.center[2]]); //scale/rotation centering
 	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
 	
 	var scaleMatrix = mat4.create();
-	mat4.scale(scaleMatrix, scaleMatrix, node["scale"]);
+	mat4.scale(scaleMatrix, scaleMatrix, node.scale);
 	mat4.multiply(modelMatrix, scaleMatrix, modelMatrix);
 	
+	var rotationMatrix = mat4.create();
 	var zRotationMatrix = mat4.create();	
-	mat4.rotate(zRotationMatrix, zRotationMatrix, radians(node["rotation"][2]), [0.0, 0.0, 1.0]);
-	mat4.multiply(modelMatrix, zRotationMatrix, modelMatrix);
+	mat4.rotate(zRotationMatrix, zRotationMatrix, DEG2RAD * node.rotation[2], [0.0, 0.0, 1.0]);
+	mat4.multiply(rotationMatrix, zRotationMatrix, rotationMatrix);
 	var yRotationMatrix = mat4.create();	
-	mat4.rotate(yRotationMatrix, yRotationMatrix, radians(node["rotation"][1]), [0.0, 1.0, 0.0]);
-	mat4.multiply(modelMatrix, yRotationMatrix, modelMatrix);
+	mat4.rotate(yRotationMatrix, yRotationMatrix, DEG2RAD * node.rotation[1], [0.0, 1.0, 0.0]);
+	mat4.multiply(rotationMatrix, yRotationMatrix, rotationMatrix);
 	var xRotationMatrix = mat4.create();	
-	mat4.rotate(xRotationMatrix, xRotationMatrix, radians(node["rotation"][0]), [1.0, 0.0, 0.0]);
-	mat4.multiply(modelMatrix, xRotationMatrix, modelMatrix);
+	mat4.rotate(xRotationMatrix, xRotationMatrix, DEG2RAD * node.rotation[0], [1.0, 0.0, 0.0]);
+	mat4.multiply(rotationMatrix, xRotationMatrix, rotationMatrix);
+	mat4.multiply(modelMatrix, rotationMatrix, modelMatrix);	
 	
 	mat4.identity(translationMatrix);
-	mat4.translate(translationMatrix, translationMatrix, node["center"]); //relative translation
+	mat4.translate(translationMatrix, translationMatrix, node.center); //relative translation
 	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
 	
 	mat4.identity(translationMatrix);
-	mat4.translate(translationMatrix, translationMatrix, node["translation"]); //absolute translation
+	mat4.translate(translationMatrix, translationMatrix, node.translation); //absolute translation
 	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
-	return modelMatrix;
-} //}}}
-function raycast(canvas,mesh,x,y) { //{{{
-	var raytracer = new GL.Raytracer(canvas.cameraMatrix);
-	var ray = raytracer.getRayForPixel(x, y);
-	if(!mesh || mesh.ready == false) return;
-	if(!mesh.octree) mesh.octree = new GL.Octree(mesh);
-
-	var hit = mesh.octree.testRay(canvas.cameraPosition, ray, 1e2, 1e10);
-	
-	if(!hit) return;
-	return hit.pos;
-}
-function radians (degrees) { //{{{
-  return degrees * Math.PI / 180;
-} //}}}
-function degrees (radians) { //{{{
-  return radians * 180 / Math.PI;
+	
+	node.modelMatrix = modelMatrix;
+	node.inverseModelMatrix = mat4.invert(mat4.create(), modelMatrix);
+	node.rotationMatrix = rotationMatrix;
+	node.inverseRotationMatrix = mat4.invert(mat4.create(), rotationMatrix);;
 } //}}}
 function clamp(value, min, max) { //{{{
@@ -177,8 +181,6 @@
 } //}}}
 function recover(canvasid,name,defaultvalue) { //{{{
-	var canvas  = document.getElementById(canvasid);
-	if (canvas && canvas.hasOwnProperty(name)) {
-		return canvas[name];
-	}
+	var canvas = document.getElementById(canvasid);
+	if (canvas && canvas.hasOwnProperty(name)) { return canvas[name]; }
 	return defaultvalue;
 } //}}}
@@ -186,62 +188,145 @@
 	//TypedArray compatibility for Safari/IE
 	if (typeof Int8Array !== 'undefined') {
-		if (!Int8Array.prototype.fill) Int8Array.prototype.fill = Array.prototype.fill;
-		if (!Int8Array.prototype.slice) Int8Array.prototype.slice = Array.prototype.slice;
+		if (!Int8Array.prototype.fill) { Int8Array.prototype.fill = Array.prototype.fill; }
+		if (!Int8Array.prototype.slice) { Int8Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Uint8Array !== 'undefined') {
-		if (!Uint8Array.prototype.fill) Uint8Array.prototype.fill = Array.prototype.fill;
-		if (!Uint8Array.prototype.slice) Uint8Array.prototype.slice = Array.prototype.slice;
+		if (!Uint8Array.prototype.fill) { Uint8Array.prototype.fill = Array.prototype.fill; }
+		if (!Uint8Array.prototype.slice) { Uint8Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Uint8ClampedArray !== 'undefined') {
-		if (!Uint8ClampedArray.prototype.fill) Uint8ClampedArray.prototype.fill = Array.prototype.fill;
-		if (!Uint8ClampedArray.prototype.slice) Uint8ClampedArray.prototype.slice = Array.prototype.slice;
+		if (!Uint8ClampedArray.prototype.fill) { Uint8ClampedArray.prototype.fill = Array.prototype.fill; }
+		if (!Uint8ClampedArray.prototype.slice) { Uint8ClampedArray.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Int16Array !== 'undefined') {
-		if (!Int16Array.prototype.fill) Int16Array.prototype.fill = Array.prototype.fill;
-		if (!Int16Array.prototype.slice) Int16Array.prototype.slice = Array.prototype.slice;
+		if (!Int16Array.prototype.fill) { Int16Array.prototype.fill = Array.prototype.fill; }
+		if (!Int16Array.prototype.slice) { Int16Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Uint16Array !== 'undefined') {
-		if (!Uint16Array.prototype.fill) Uint16Array.prototype.fill = Array.prototype.fill;
-		if (!Uint16Array.prototype.slice) Uint16Array.prototype.slice = Array.prototype.slice;
+		if (!Uint16Array.prototype.fill) { Uint16Array.prototype.fill = Array.prototype.fill; }
+		if (!Uint16Array.prototype.slice) { Uint16Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Int32Array !== 'undefined') {
-		if (!Int32Array.prototype.fill) Int32Array.prototype.fill = Array.prototype.fill;
-		if (!Int32Array.prototype.slice) Int32Array.prototype.slice = Array.prototype.slice;
+		if (!Int32Array.prototype.fill) { Int32Array.prototype.fill = Array.prototype.fill; }
+		if (!Int32Array.prototype.slice) { Int32Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Uint32Array !== 'undefined') {
-		if (!Uint32Array.prototype.fill) Uint32Array.prototype.fill = Array.prototype.fill;
-		if (!Uint32Array.prototype.slice) Uint32Array.prototype.slice = Array.prototype.slice;
+		if (!Uint32Array.prototype.fill) { Uint32Array.prototype.fill = Array.prototype.fill; }
+		if (!Uint32Array.prototype.slice) { Uint32Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Float32Array !== 'undefined') {
-		if (!Float32Array.prototype.fill) Float32Array.prototype.fill = Array.prototype.fill;
-		if (!Float32Array.prototype.slice) Float32Array.prototype.slice = Array.prototype.slice;
+		if (!Float32Array.prototype.fill) { Float32Array.prototype.fill = Array.prototype.fill; }
+		if (!Float32Array.prototype.slice) { Float32Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof Float64Array !== 'undefined') {
-		if (!Float64Array.prototype.fill) Float64Array.prototype.fill = Array.prototype.fill;
-		if (!Float64Array.prototype.slice) Float64Array.prototype.slice = Array.prototype.slice;
+		if (!Float64Array.prototype.fill) { Float64Array.prototype.fill = Array.prototype.fill; }
+		if (!Float64Array.prototype.slice) { Float64Array.prototype.slice = Array.prototype.slice; }
 	}
 	if (typeof TypedArray !== 'undefined') {
-		if (!TypedArray.prototype.fill) TypedArray.prototype.fill = Array.prototype.fill;
-		if (!TypedArray.prototype.slice) TypedArray.prototype.slice = Array.prototype.slice;
-	}
+		if (!TypedArray.prototype.fill) { TypedArray.prototype.fill = Array.prototype.fill; }
+		if (!TypedArray.prototype.slice) { TypedArray.prototype.slice = Array.prototype.slice; }
+	}
+} //}}}
+function raycast(canvas, origin, ray) { //{{{
+	var mesh = canvas.unitNode.mesh;
+	if (!mesh || mesh.ready == false) { return; }
+	if (!mesh.octree) { mesh.octree = new GL.Octree(mesh); }
+	
+	var hit = mesh.octree.testRay(origin, ray, 1e3, 1e10);
+	
+	if(!hit) { return; }
+	
+	hit.modelPos = vec3.copy(vec3.create(), hit.pos);
+	vec3.transformMat4(hit.pos, hit.pos, canvas.unitNode.modelMatrix);
+	vec3.transformMat4(hit.normal, hit.normal, canvas.unitNode.modelMatrix);
+
+	return hit;
 } //}}}
 //}}}
-//{{{ Shader Loading
-function loadShaders(gl) { //{{{
-	var shaders = {};
-	shaders["Colored"] = new GL.Shader.fromURL("../../../js/shaders/Colored.vsh", "../../../js/shaders/Colored.fsh", null, gl);
-	shaders["Textured"] = new GL.Shader.fromURL("../../../js/shaders/Textured.vsh", "../../../js/shaders/Textured.fsh", null, gl);
-	shaders["SkyFromSpace"] = new GL.Shader.fromURL("../../../js/shaders/SkyFromSpace.vert", "../../../js/shaders/SkyFromSpace.frag", null, gl);
-	shaders["GroundFromSpace"] = new GL.Shader.fromURL("../../../js/shaders/GroundFromSpace.vert", "../../../js/shaders/GroundFromSpace.frag", null, gl);
-	return shaders;
-} //}}}
 //{{{ Interface Functions
-function onTap(ev,canvas) { //{{{
+function onTap(ev, canvas) { //{{{
+	//Sets up a marker on a canvas that will track a point on the mesh. Can be dismissed by closing the display or clicking the marker.
 	ev.preventDefault();
-	if (ev.type == 'panstart') {
-		canvas.lastDeltaX = 0;
-		canvas.lastDeltaY = 0;
-	}
-}
+	if (!(canvas.dataMarkersAllowed && canvas.dataMarkersEnabled)) { return; }
+	
+	var rect = canvas.getBoundingClientRect();
+	var x = ev.srcEvent.clientX - rect.left;
+	var y = ev.srcEvent.clientY - rect.top;
+	
+	updateMarker(canvas, x, y, true);
+} //}}}
+function updateMarker(canvas, x, y, reset, origin, far) { //{{{
+	//Can be called by onTap to create/reuse a marker, or by the marker's update function. Origin and far are optional and only used by the update function for recreating the raycast.
+	if (!canvas.unitNode) { return; }
+	
+	var inverseMVPMatrix = mat4.invert(mat4.create(), mat4.multiply(mat4.create(), canvas.cameraMatrix, canvas.unitNode.modelMatrix));
+	var origin = origin || vec3.transformMat4(vec3.create(), [(x - canvas.width / 2) / (canvas.width / 2), (canvas.height / 2 - y) / (canvas.height / 2), 0], inverseMVPMatrix);
+	var far = far || vec3.transformMat4(vec3.create(), [(x - canvas.width / 2) / (canvas.width / 2), (canvas.height / 2 - y) / (canvas.height / 2), 1.0], inverseMVPMatrix);
+	var ray = vec3.subtract(vec3.create(), far, origin);
+	var hit = raycast(canvas, origin, ray);
+	
+	if (hit) {
+		var u = hit.coords[0][0] * hit.uvw[0] + hit.coords[1][0] * hit.uvw[1] + hit.coords[2][0] * hit.uvw[2];
+		var v = hit.coords[0][1] * hit.uvw[0] + hit.coords[1][1] * hit.uvw[1] + hit.coords[2][1] * hit.uvw[2];
+		var value = canvas.unitNode.caxis[0] * (1.0 - v) + canvas.unitNode.caxis[1] * v;
+		
+		var rect = canvas.getBoundingClientRect();
+		var dataMarkerSize = 32;
+		if (!canvas.marker) {
+			$('#' + canvas.id).after( '<img src="../../../js/textures/data-marker.svg" alt="data marker" width="' + dataMarkerSize + '" height="' + dataMarkerSize + '" id="sim-data-marker-' + canvas.id + '" class="sim-data-marker noselect"></img>' );
+			$('#sim-data-marker-' + canvas.id).css({
+				'position': 'absolute', 
+				'left': (Math.round(x + rect.left) - dataMarkerSize / 2) + 'px', 
+				'top': (Math.round(y + rect.top) - dataMarkerSize) + 'px', 
+				'width': dataMarkerSize + 'px', 
+				'height': dataMarkerSize + 'px',
+				'pointer-events': 'all',
+				'cursor': 'pointer',
+				'display': 'none'
+			});
+			canvas.marker = $('#sim-data-marker-' + canvas.id);
+			canvas.marker.on('click touch', function () {
+				canvas.marker.fadeOut(175);
+				canvas.dataMarkerDisplay.stop();
+			});
+			canvas.marker.fadeIn(175);
+		}
+		
+		
+		canvas.marker.hit = hit;
+		canvas.marker.update = function() {
+			if (!canvas.unitNode) { return; }
+			var screenPoint = vec3.transformMat4(vec3.create(), canvas.marker.hit.pos, canvas.cameraMatrix);
+			var x = screenPoint[0] * (canvas.width / 2) + canvas.width / 2;
+			var y = -screenPoint[1] * (canvas.height / 2) + canvas.height / 2;
+			updateMarker(canvas, Math.round(x), Math.round(y), false, origin, far);
+			canvas.marker.css({
+				'left': (Math.round(x + rect.left) - dataMarkerSize / 2) + 'px', 
+				'top': (Math.round(y + rect.top) - dataMarkerSize) + 'px'
+			});
+		};
+		
+		if (!canvas.dataMarkerDisplay) {
+			var tripToShowData = new Trip([
+			{
+				content : '<div id="sim-data-marker-content-' + canvas.id + '" class="sim-data-marker-content"><p>X: ' + hit.modelPos [0].toPrecision(3) + ' </p><p>Y: ' + hit.modelPos [1].toPrecision(3) + ' </p><p>Z: ' + hit.modelPos [2].toPrecision(3) + '</p><p>Value: ' + (Math.round(value * 1000) / 1000).toPrecision(3) + '</p></div>',
+				position : 'screen-se'
+			}],
+			{
+				onEnd : function(tripIndex) {
+					canvas.marker.fadeOut(175);
+				},
+				tripTheme : 'dark',
+				showCloseBox : true,
+				delay: -1
+			});
+			canvas.dataMarkerDisplay = tripToShowData;
+			canvas.dataMarkerDisplay.start();
+		}
+		$('#sim-data-marker-content-' + canvas.id).html('<p>X: ' + hit.modelPos [0].toPrecision(3) + ' </p><p>Y: ' + hit.modelPos [1].toPrecision(3) + ' </p><p>Z: ' + hit.modelPos [2].toPrecision(3) + '</p><p>Value: ' + (Math.round(value * 1000) / 1000).toPrecision(3) + '</p>');
+				
+		if (reset) { modifyDataMarkersEnabled(true,canvas); }
+	}
+} //}}}
 function onPan(ev,canvas,displaylog) { //{{{
 	ev.preventDefault();
@@ -256,20 +341,20 @@
 		
 		if (canvas.twod) {
-			canvas.translation[0] += Math.cos(radians(canvas.rotation[0])) * deltaX - Math.sin(radians(0)) * deltaY;
-			canvas.translation[2] += Math.sin(radians(canvas.rotation[0])) * deltaX + Math.cos(radians(0)) * deltaY;
+			canvas.translation[0] += Math.cos(DEG2RAD * canvas.rotation[0]) * deltaX - Math.sin(DEG2RAD * 0) * deltaY;
+			canvas.translation[2] += Math.sin(DEG2RAD * canvas.rotation[0]) * deltaX + Math.cos(DEG2RAD * 0) * deltaY;
 		}
 		else {
-			canvas.translation[0] += Math.cos(radians(canvas.rotation[0])) * deltaX - Math.sin(radians(canvas.rotation[0])) * deltaY;
-			canvas.translation[2] += Math.sin(radians(canvas.rotation[0])) * deltaX + Math.cos(radians(canvas.rotation[0])) * deltaY;
+			canvas.translation[0] += Math.cos(DEG2RAD * canvas.rotation[0]) * deltaX - Math.sin(DEG2RAD * canvas.rotation[0]) * deltaY;
+			canvas.translation[2] += Math.sin(DEG2RAD * canvas.rotation[0]) * deltaX + Math.cos(DEG2RAD * canvas.rotation[0]) * deltaY;
 		}
 	}
 	else {
-		canvas.rotation[0] += degrees((canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth * -2 * canvas.controlSensitivity);
-		canvas.rotation[1] += degrees((canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight * -2 * canvas.controlSensitivity);
-		
-		if (canvas.rotation[0] > 360) {canvas.rotation[0] -= 360};
-		if (canvas.rotation[0] < -360) {canvas.rotation[0] += 360};
-		if (canvas.rotation[1] > 180) {canvas.rotation[1] -= 360};
-		if (canvas.rotation[1] < -180) {canvas.rotation[1] += 360};
+		canvas.rotation[0] += (canvas.lastDeltaX - ev.deltaX) / canvas.clientWidth * -2 * canvas.controlSensitivity * RAD2DEG;
+		canvas.rotation[1] += (canvas.lastDeltaY - ev.deltaY) / canvas.clientHeight * -2 * canvas.controlSensitivity * RAD2DEG;
+		
+		if (canvas.rotation[0] > 360) { canvas.rotation[0] -= 360; };
+		if (canvas.rotation[0] < -360) { canvas.rotation[0] += 360; };
+		if (canvas.rotation[1] > 180) { canvas.rotation[1] -= 360; };
+		if (canvas.rotation[1] < -180) { canvas.rotation[1] += 360; };
 		
 		canvas.rotation[0] = clamp(canvas.rotation[0], canvas.rotationAzimuthBounds[0], canvas.rotationAzimuthBounds[1]);
@@ -279,14 +364,10 @@
 	canvas.lastDeltaY = ev.deltaY;
 
-	if (displaylog) console.log(canvas.rotation);
+	if (displaylog) { console.log(canvas.rotation); }
 } //}}}
 function onPinch(ev,canvas,displaylog) { //{{{
 	ev.preventDefault();
-	if (ev.type == 'pinchstart') {
-		canvas.zoomLast = canvas.zoom;
-	}
-	else {
-		modifyZoom(ev.scale * canvas.zoomLast, canvas, displaylog);
-	}
+	if (ev.type == 'pinchstart') { canvas.zoomLast = canvas.zoom; }
+	else { modifyZoom(ev.scale * canvas.zoomLast, canvas, displaylog); }
 } //}}}
 function onZoom(ev,canvas,displaylog) { //{{{
@@ -297,5 +378,16 @@
 function modifyZoom(value,canvas,displaylog) { //{{{
 	canvas.zoom = clamp(value, canvas.zoomBounds[0], canvas.zoomBounds[1]);
-	if (displaylog) console.log(canvas.zoom);
+	if (displaylog) { console.log(canvas.zoom); }
+} //}}}
+function modifyDataMarkersEnabled(value,canvas) { //{{{
+	canvas.dataMarkersEnabled = value;
+	if (!canvas.dataMarkersEnabled && canvas.marker) {
+		canvas.marker.fadeOut(175);
+		canvas.dataMarkerDisplay.stop();
+	}
+	else if (canvas.dataMarkersEnabled && canvas.marker) {
+		canvas.marker.fadeIn(175);
+		canvas.dataMarkerDisplay.start();
+	}
 } //}}}
 //}}}
@@ -312,28 +404,24 @@
 	var cameraPosition = vec3.create();
 
-	if (canvas.twod) {
-		mat4.ortho(pMatrix, -aspectRatio*6.371e6/canvas.zoom, aspectRatio*6.371e6/canvas.zoom, -6.371e6/canvas.zoom, 6.371e6/canvas.zoom, -1.0, 1e10);
-	}
-	else {
-		mat4.perspective(pMatrix, 60 * Math.PI / 180, aspectRatio, 1e2, 1e10);
-	}
+	if (canvas.twod) { mat4.ortho(pMatrix, -aspectRatio*6.371e6/canvas.zoom, aspectRatio*6.371e6/canvas.zoom, -6.371e6/canvas.zoom, 6.371e6/canvas.zoom, -1.0, 1e10); }
+	else { mat4.perspective(pMatrix, 45 * DEG2RAD, aspectRatio, 1e3, 1e10); }
 	
 	//Apply worldspace translation
-	mat4.translate(translateMatrix, translateMatrix, [-canvas["translation"][0],-canvas["translation"][1],-canvas["translation"][2]]);
+	mat4.translate(translateMatrix, translateMatrix, [-canvas.translation[0],-canvas.translation[1],-canvas.translation[2]]);
 	mat4.multiply(vMatrix, translateMatrix, vMatrix);
 	
 	//Calculate rotation around camera focal point about worldspace origin
 	if (canvas.twod) {
-		mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, radians(0), [0, 1, 0]);
-		mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, radians(90), [1, 0, 0]);
+		mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, DEG2RAD * 0, [0, 1, 0]);
+		mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, DEG2RAD * 90, [1, 0, 0]);
 		mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
 	}
 	else {
-		mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, radians(canvas.rotation[0]), [0, 1, 0]);
-		mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, radians(canvas.rotation[1]), [1, 0, 0]);
+		mat4.rotate(azimuthRotationMatrix, azimuthRotationMatrix, DEG2RAD * canvas.rotation[0], [0, 1, 0]);
+		mat4.rotate(elevationRotationMatrix, elevationRotationMatrix, DEG2RAD * canvas.rotation[1], [1, 0, 0]);
 		mat4.multiply(rotationMatrix, elevationRotationMatrix, azimuthRotationMatrix);
 	}
 	
-	//Apply rotation and scaling transform
+	//Apply rotation transform
 	mat4.multiply(vMatrix, rotationMatrix, vMatrix);
 
@@ -345,11 +433,12 @@
 	//Calculate fields for lighting and raycasts
 	mat4.invert(canvas.vInverseMatrix, vMatrix);
-	vec3.transformMat4(canvas.cameraPosition, cameraPosition, canvas.vInverseMatrix);
-
+	
 	//Apply projection matrix to get camera matrix
 	mat4.multiply(canvas.cameraMatrix, pMatrix, vMatrix);
+	mat4.invert(canvas.inverseCameraMatrix, canvas.cameraMatrix);
+	vec3.transformMat4(canvas.cameraPosition, cameraPosition, canvas.inverseCameraMatrix);
 }//}}}
 function drawSceneGraphNode(canvas,node) { //{{{
-	if (!node["enabled"]) return;
+	if (!node.enabled) { return; }
 
 	var gl = canvas.gl;
@@ -357,12 +446,12 @@
 	
 	var mvpMatrix = mat4.create();
-	mat4.multiply(mvpMatrix, canvas.cameraMatrix, node["modelMatrix"]);
-	
-	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"]);
+	mat4.multiply(mvpMatrix, canvas.cameraMatrix, node.modelMatrix);
+	
+	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);
 
@@ -373,5 +462,5 @@
 	vec3.transformMat4(origin, origin, canvas.vInverseMatrix);
 	vec3.normalize(lightOrigin, lightOrigin);
-	vec3.sub(cameraPositionRelative, origin, node["translation"]);
+	vec3.sub(cameraPositionRelative, origin, node.translation);
 	cameraHeight = vec3.length(cameraPositionRelative);
 	
@@ -401,14 +490,14 @@
 	var scaleDepth = atm.scaleDepth;
 	
-	node["shader"].uniforms({
+	node.shader.uniforms({
 		m4MVP: mvpMatrix,
-		m4Model: node["modelMatrix"],
+		m4Model: node.modelMatrix,
 		u_texture: 0,
-		u_alpha: node["alpha"],
-		u_maskEnabled: node["maskEnabled"],
-		u_maskHeight: node["maskHeight"],
-		u_maskColor: node["maskColor"],
+		u_alpha: node.alpha,
+		u_maskEnabled: node.maskEnabled,
+		u_maskHeight: node.maskHeight,
+		u_maskColor: node.maskColor,
 		v3CameraPosition: origin,
-		v3Translate: node["translation"],
+		v3Translate: node.translation,
 		v3LightPos: lightOrigin,
 		v3InvWavelength: inv_wavelength4,
@@ -434,49 +523,46 @@
 		e: atm.e,
 		attenuation: atm.attenuation
-	});
-	if (node["useIndexBuffer"] == true) node["shader"].draw(node["mesh"], node["drawMode"], "indices");
-	else node["shader"].draw(node["mesh"], node["drawMode"]);
-
+	}).draw(node.mesh, node.drawMode, 'triangles');
+	
 	gl.enable(gl.DEPTH_TEST);
 	gl.disable(gl.CULL_FACE);
 } //}}}
 function draw(canvas,options) { //{{{
-
-	// Ensure canvas and gl viewport sizes are the same
-	if (canvas.width  != canvas.clientWidth || canvas.height != canvas.clientHeight) {
-		canvas.width  = canvas.clientWidth;
-		canvas.height = canvas.clientHeight;
-		canvas.gl.viewport(0, 0, canvas.width, canvas.height);
-	}
-	
-	if (canvas.textcanvas) canvas.textcanvas.draw(canvas);
+	if (canvas.textcanvas) { canvas.textcanvas.draw(canvas); }
 
 	var nodes = canvas.nodes;
 	if (nodes.length < 1) {
-		canvas.drawHandler = window.requestAnimationFrame(function(time) {draw(canvas,options)});
+		canvas.drawHandler = window.requestAnimationFrame(function(time) { draw(canvas,options); });
 		return;
 	}
-	for (var node in nodes) {
-		if (nodes[node]["texture"] && nodes[node]["texture"]["ready"] == false || nodes[node]["shader"]["ready"] == false) {
-			canvas.drawHandler = window.requestAnimationFrame(function(time) {draw(canvas,options)});
+	for (var node in nodes) {	
+		if ((nodes[node].texture && nodes[node].texture.ready == false) || nodes[node].shader.ready == false || nodes[node].mesh.ready == false) {
+			canvas.drawHandler = window.requestAnimationFrame(function(time) { draw(canvas,options); });
 			return;
 		}
 	}
 	
+	var rect = canvas.getBoundingClientRect();
+	canvas.width  = rect.width;
+	canvas.height = rect.height;
+	
 	var gl = canvas.gl;
+	gl.makeCurrent(); //litegl function to handle switching between multiple canvases
+	gl.viewport(0, 0, rect.width, rect.height);
 	gl.clearColor(canvas.backgroundcolor[0], canvas.backgroundcolor[1], canvas.backgroundcolor[2], canvas.backgroundcolor[3]);
 	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-	gl.makeCurrent();
 	
 	updateCameraMatrix(canvas);
+	
+	if (canvas.marker) { canvas.marker.update(); }
 	
 	var drawPassNumber = 3;
 	for (var i = drawPassNumber - 1; i >= 0; i--) {
 		for (var node in nodes) {
-			if (nodes[node]["drawOrder"] == i) drawSceneGraphNode(canvas,nodes[node]);
-		}
-	}
-
-	canvas.drawHandler = window.requestAnimationFrame(function(time) {draw(canvas,options)});
+			if (nodes[node].drawOrder == i) { drawSceneGraphNode(canvas,nodes[node]); }
+		}
+	}
+
+	canvas.drawHandler = window.requestAnimationFrame(function(time) { draw(canvas,options); });
 } //}}}
 //}}}
