Index: issm/trunk-jpl/src/m/plot/webgl.js
===================================================================
--- issm/trunk-jpl/src/m/plot/webgl.js	(revision 21205)
+++ 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); });
 } //}}}
 //}}}
