Index: /issm/trunk-jpl/src/m/plot/plot_unit.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/plot_unit.js	(revision 19727)
+++ /issm/trunk-jpl/src/m/plot/plot_unit.js	(revision 19728)
@@ -1,3 +1,3 @@
-function plot_unit(x,y,z,elements,data,is2d,isplanet,datatype,options){
+function plot_unit(x,y,z,elements,data,is2d,isplanet,datatype,options) {
 	//PLOT_UNIT - unit plot, display data
 	//
@@ -13,5 +13,5 @@
 	var canvas=document.getElementById(options.getfieldvalue('canvasid'));
 
-	//Initialize the GL context: 
+	// Initialize the GL context: 
 	var gl=initWebGL(canvas);
 
@@ -19,12 +19,7 @@
 
 	if (gl) {
-		// Set clear color to black, fully opaque
-		gl.clearColor(0.0, 0.0, 0.0, 1.0);
-		// Enable depth testing
-		gl.enable(gl.DEPTH_TEST);
-		// Near things obscure far things
-		gl.depthFunc(gl.LEQUAL);
-		// Clear the color as well as the depth buffer.
-		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+		loadShaders(gl);
+		var node=loadModel(gl,x,y,z,elements,data);
+		drawSceneGraphNode(gl,node);
 	}
 
Index: /issm/trunk-jpl/src/m/plot/webgl.js
===================================================================
--- /issm/trunk-jpl/src/m/plot/webgl.js	(revision 19727)
+++ /issm/trunk-jpl/src/m/plot/webgl.js	(revision 19728)
@@ -1,3 +1,5 @@
 /*This is where we have all our webgl relevant functionality for the plotting routines: */
+var shaders = {};
+//{{{ GL Initialization
 function initWebGL(canvas) { //{{{
 	gl = null;
@@ -15,4 +17,336 @@
 	}
 
+
+	// Set clear color to black, fully opaque
+	gl.clearColor(0.0, 0.0, 0.0, 1.0);
+	// Enable depth testing
+	gl.enable(gl.DEPTH_TEST);
+	// Near things obscure far things
+	gl.depthFunc(gl.LEQUAL);
+	// Clear the color as well as the depth buffer.
+	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+	// Allocate arrays equal to maximium number of attributes used by any one shader
+	gl.enableVertexAttribArray(0);
+	gl.enableVertexAttribArray(1);
+
+	// Add context state variables
+	//gl.zoomFactor = 1.0;
+	//gl.cameraMatrix = mat4.create();
+
+	// Add event listeners for canvas
+	if (canvas.addEventListener) {
+		// IE9, Chrome, Safari, Opera
+		canvas.addEventListener("mousewheel", function (e) {MouseWheelHandler(e,gl)}, false);
+		// Firefox
+		canvas.addEventListener("DOMMouseScroll", function (e) {MouseWheelHandler(e,gl)}, false);
+		// Mobile
+		//canvas.addEventListener("gesturechange", MousePinchHandler, false);
+	}
+
 	return gl;
 } //}}}
+function loadModel(gl,x,y,z,elements,data){ //{{{
+	colorbar=colorbars["rainbow"];
+	model={"x":x,"y":y,"z":z,"elements":elements};	
+	sceneGraph={};
+	sceneGraph["model"] = sceneGraphNode(gl);
+	sceneGraph["model"]["shaderName"] = "colored";
+	sceneGraph["model"]["shader"] = shaders["colored"]["program"];
+	sceneGraph["model"]["useIndexBuffer"] = true;
+	
+	var meshVertices = [];
+	var meshColors = [];
+	var meshNormals = [];
+	var color = [];
+
+	var qmin = Math.min.apply(null,data);
+	var qmax = Math.max.apply(null,data);
+	var xmin = Math.min.apply(null,model["x"]);
+	var xmax = Math.max.apply(null,model["x"]);
+	var ymin = Math.min.apply(null,model["y"]);
+	var ymax = Math.max.apply(null,model["y"]);
+	var zmin = Math.min.apply(null,[0]);
+	var zmax = Math.max.apply(null,[0]);
+	
+	var scale = 1 / (xmax - xmin);
+	sceneGraph["model"]["scale"] = [scale, scale, scale];
+	sceneGraph["model"]["translation"] = [(xmin + xmax) / (-2 / scale), (ymin + ymax) / (-2 / scale), (zmin + zmax) / (-2 / scale)];
+	sceneGraph["model"]["modelMatrix"] = recalculateModelMatrix(sceneGraph["model"]);
+
+	for(var i = 0; i < model["x"].length; i++){
+		meshVertices[meshVertices.length] = model["x"][i];
+		meshVertices[meshVertices.length] = model["y"][i];
+		meshVertices[meshVertices.length] = 0.0;
+		
+		//initialize vertex normals
+		meshNormals[meshNormals.length] = 0.0;
+		meshNormals[meshNormals.length] = 0.0;
+		meshNormals[meshNormals.length] = 0.0;
+		
+		//handle mesh/qinterest size mismatch
+		color = rgb(data[i], qmin, qmax);
+		meshColors[meshColors.length] = color[0];
+		meshColors[meshColors.length] = color[1];
+		meshColors[meshColors.length] = color[2];
+		meshColors[meshColors.length] = 1.0;
+	}
+
+	var indices = [];
+	indices = indices.concat.apply(indices, model["elements"]);
+
+	for (var i = 0; i < indices.length; i += 3){
+		indices[i] -= 1;
+		indices[i + 1] -= 1;
+		indices[i + 2] -= 1;
+	}
+
+	meshVertices.itemSize = 3;
+	meshColors.itemSize = 4;
+	indices.itemSize = 1;
+
+	sceneGraph["model"]["buffers"] = initBuffers(gl,[meshVertices, meshColors, indices]);
+	return sceneGraph["model"];
+} //}}}
+function initBuffers(gl,arrays) { //{{{	
+	var bufferArray = [];
+	for (var i = 0; i < arrays.length; i++) {
+		bufferArray[i] = gl.createBuffer();	
+		bufferArray[i].itemSize = arrays[i].itemSize;
+		bufferArray[i].numItems = arrays[i].length/bufferArray[i].itemSize;
+		
+		if (bufferArray[i].itemSize > 1) {
+			gl.bindBuffer(gl.ARRAY_BUFFER, bufferArray[i]);
+			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(arrays[i]), gl.STATIC_DRAW);
+		}
+		else {
+			//TODO: identify index buffers uniquely (by name)
+			gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferArray[i]);
+			gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(arrays[i]), gl.STATIC_DRAW);
+		}
+	}	
+	return bufferArray;
+} //}}}
+function sceneGraphNode(gl) { //{{{
+	//Primary object for storing common rendering information.
+	return {buffers:[],
+		shader:null,
+		draw:null,
+		hideOcean:false,
+		level:0,
+		useIndexBuffer:false,
+		useOrthographic:false,
+	       	transparency:1.0,
+		disableDepthTest:false, 
+		enableCullFace:false,
+		cullFace:gl.FRONT,
+		drawMode:gl.TRIANGLES,
+		texture:null,
+		translation:vec3.create(),
+		rotation:vec3.create(),
+		scale:vec3.fromValues(1, 1, 1),
+		modelMatrix:mat4.create()};
+} //}}}
+function recalculateModelMatrix(node) { //{{{
+	var modelMatrix = mat4.create();
+
+	var scaleMatrix = mat4.create();
+	mat4.scale(scaleMatrix, scaleMatrix, node["scale"]);
+	mat4.multiply(modelMatrix, scaleMatrix, modelMatrix);
+
+	var zRotationMatrix = mat4.create();	
+	mat4.rotate(zRotationMatrix, zRotationMatrix, node["rotation"][2] * Math.PI/180.0, [0.0, 0.0, 1.0]);
+	mat4.multiply(modelMatrix, zRotationMatrix, modelMatrix);
+	var yRotationMatrix = mat4.create();	
+	mat4.rotate(yRotationMatrix, yRotationMatrix, node["rotation"][1] * Math.PI/180.0, [0.0, 1.0, 0.0]);
+	mat4.multiply(modelMatrix, yRotationMatrix, modelMatrix);
+	var xRotationMatrix = mat4.create();	
+	mat4.rotate(xRotationMatrix, xRotationMatrix, node["rotation"][0] * Math.PI/180.0, [1.0, 0.0, 0.0]);
+	mat4.multiply(modelMatrix, xRotationMatrix, modelMatrix);
+
+	var translationMatrix = mat4.create();
+	mat4.translate(translationMatrix, translationMatrix, node["translation"]); //relative translation
+	mat4.multiply(modelMatrix, translationMatrix, modelMatrix);
+
+	return modelMatrix;
+} //}}}
+function rgb(value, min, max) { //{{{
+	value = clamp(value, min + 1.0, max);
+	var use_log_scale = true;
+	if (use_log_scale == true) {
+		var normalizedValue = Math.log(value - min) / Math.log(max - min);
+	}
+	else {
+		var normalizedValue = (value - min) / (max - min);
+	}
+	var index = clamp(Math.round(normalizedValue * colorbar.length), 0, colorbar.length - 1);
+	return colorbar[index];
+} //}}}
+function clamp(value, min, max) { //{{{
+	return Math.max(min, Math.min(value, max));
+} //}}}
+//}}}
+//{{{ Shader Loading
+function loadShaders(gl) { //{{{
+	var shader_name_array = ["colored"];
+	//var shaders = {};
+	for (var i = 0; i < shader_name_array.length; i++) {
+		loadShader(gl,shader_name_array[i]);
+	}
+	//return shaders; 
+} //}}}
+function loadShader(gl,shaderName) { //{{{
+	//var shader = {loaded:false, vsh:{}, fsh:{}};
+	shaders[shaderName] = {loaded:false, vsh:{}, fsh:{}};
+	$.ajax({
+		url: "webgl/shaders/" + shaderName + ".vsh",
+		async: false,
+		dataType: "script"
+	});
+	$.ajax({
+		url: "webgl/shaders/" + shaderName + ".fsh",
+		async: false,
+		dataType: "script"
+	});
+
+	shaders[shaderName]["vsh"]["shader"] = getShaderByString(gl, shaders[shaderName]["vsh"]["string"], "vsh");
+	shaders[shaderName]["fsh"]["shader"] = getShaderByString(gl, shaders[shaderName]["fsh"]["string"], "fsh");
+
+	shaders[shaderName]["program"] = gl.createProgram();
+	gl.attachShader(shaders[shaderName]["program"], shaders[shaderName]["vsh"]["shader"]);
+	gl.attachShader(shaders[shaderName]["program"], shaders[shaderName]["fsh"]["shader"]);
+	gl.linkProgram(shaders[shaderName]["program"]);
+
+	if (!gl.getProgramParameter(shaders[shaderName]["program"], gl.LINK_STATUS)) {
+		alert("Could not initialise shaders");
+	}
+
+	var vshStringArray = shaders[shaderName]["vsh"]["string"].split("\n");
+	var fshStringArray = shaders[shaderName]["fsh"]["string"].split("\n");
+	var line = "";
+	var property = "";
+	for (var i = 0; i < vshStringArray.length; i++) {
+		line = vshStringArray[i];
+		if (line.search("attribute") != -1) {
+			property = nameFromLine(line);
+			shaders[shaderName]["program"][property] = gl.getAttribLocation(shaders[shaderName]["program"], property);
+		}
+		else if (line.search("uniform") != -1) {
+			property = nameFromLine(line);
+			shaders[shaderName]["program"][property] = gl.getUniformLocation(shaders[shaderName]["program"], property);
+		}
+		else if (line.search("void main") != -1) {
+			break;
+		}
+	}
+	for (var i = 0; i < fshStringArray.length; i++) {
+		line = fshStringArray[i];
+		if (line.search("uniform") != -1) {
+			property = nameFromLine(line);
+			shaders[shaderName]["program"][property] = gl.getUniformLocation(shaders[shaderName]["program"], property);
+		}
+		else if (line.search("void main") != -1) {
+			break;
+		}
+	}
+	shaders[shaderName]["loaded"] = true;
+	//return shader;
+} //}}}
+function getShaderByString(gl,str,type) { //{{{
+	var shader;
+	if (type == "fsh") {
+		shader = gl.createShader(gl.FRAGMENT_SHADER);
+	}
+	else if (type == "vsh") {
+		shader = gl.createShader(gl.VERTEX_SHADER);
+	}
+	else {
+		return null;
+	}
+	
+	gl.shaderSource(shader, str);
+	gl.compileShader(shader);
+
+	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {	
+		alert(gl.getShaderInfoLog(shader));
+		return null;
+	}
+
+	return shader;
+} //}}}
+function functionToString(functionVariable) { //{{{
+	//Workaround for loading text files - store as multiline comment in function, then strip string from function once loaded
+	return functionVariable.toString().replace(/^[^\/]+\/\*!?/, '').replace(/\*\/[^\/]+$/, '');
+} //}}}
+function nameFromLine(line) { //{{{
+	//returns lowerCamelCase property name from shader line
+	var fullName = line.split(" ")[2];
+	return fullName.slice(0, fullName.search(";"));
+} //}}}
+//}}}
+//{{{ Interface Functions
+function MouseWheelHandler(e,gl) { //{{{
+	// prevent scrolling when over canvas
+	e.preventDefault();
+	var delta = clamp((e.wheelDelta || -e.detail), -1, 1);
+	//gl.zoomFactor = gl.zoomFactor + delta;
+	updateCameraMatrix(gl);
+} //}}}
+//}}}
+//{{{ Drawing Functions
+function updateCameraMatrix(gl) { //{{{
+       	//Update view matrix and multiply with projection matrix to get the view-projection (camera) matrix.
+	var vMatrix = mat4.create();
+	var pMatrix = mat4.create();
+
+	mat4.perspective(pMatrix, 45 * Math.PI / 180, gl.canvas.width / gl.canvas.height, 0.001, 10000.0);
+
+	var scaleMatrix= mat4.create();
+	mat4.scale(scaleMatrix, scaleMatrix, [1/100000000000000, 1/100000000000000, 1/100000000000000]);	
+	mat4.multiply(vMatrix, scaleMatrix, vMatrix);
+
+	//Apply screenspace relative translation
+	var translateMatrix = mat4.create();
+	mat4.translate(translateMatrix, translateMatrix, [0.0, 0.0, gl.zoomFactor]);
+	mat4.multiply(vMatrix, translateMatrix, vMatrix);
+
+	//Apply projection matrix to get camera matrix
+	mat4.multiply(gl.cameraMatrix, pMatrix, vMatrix);
+}//}}}
+function drawSceneGraphNode(gl,node) { //{{{
+	bindAttributes(gl, node["shader"], node["buffers"]);
+	var mvpMatrix = mat4.create();
+	var cameraMatrix = mat4.create();
+	//var cameraMatrix = gl.cameraMatrix;
+	if (node["useOrthographic"] == true) {
+		mat4.ortho(mvpMatrix, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
+	}
+	else {
+		mat4.multiply(mvpMatrix, cameraMatrix, node["modelMatrix"]);
+	}
+	gl.uniformMatrix4fv(node["shader"]["uMVPMatrix"], false, mvpMatrix);
+	gl.uniform1f(node["shader"]["uAlpha"], node["transparency"]);
+	if  (node["useIndexBuffer"]) {
+		gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, node["buffers"][node["buffers"].length - 1]);
+		gl.drawElements(node["drawMode"], node["buffers"][node["buffers"].length - 1].numItems, gl.UNSIGNED_SHORT, 0);
+	}
+	else {
+		gl.drawArrays(node["drawMode"], 0, node["buffers"][0].numItems);
+	}	
+	gl.enable(gl.DEPTH_TEST);
+	gl.disable(gl.CULL_FACE);
+} //}}}
+function bindAttributes(gl,shaderProgram,bufferArray) { //{{{
+	gl.useProgram(shaderProgram);
+	var arrayNumber = 0;
+	for (var propertyName in shaderProgram) {
+		if (propertyName[0] == "a") {
+			if (bufferArray[arrayNumber].itemSize > 1) {
+				gl.bindBuffer(gl.ARRAY_BUFFER, bufferArray[arrayNumber]);
+				gl.vertexAttribPointer(shaderProgram[propertyName], bufferArray[arrayNumber].itemSize, gl.FLOAT, false, 0, 0);
+				arrayNumber++;
+			}
+		}
+	}
+} //}}}
+//}}}
Index: /issm/trunk-jpl/test/NightlyRun/test101.html
===================================================================
--- /issm/trunk-jpl/test/NightlyRun/test101.html	(revision 19727)
+++ /issm/trunk-jpl/test/NightlyRun/test101.html	(revision 19728)
@@ -4,4 +4,9 @@
 <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
 <!-- Includes {{{-->
+<script type="text/javascript" src="webgl/jquery-1.11.1.js"></script>
+<script type="text/javascript" src="webgl/gl-matrix-2.2.0.js"></script>
+<script type="text/javascript" src="webgl/colorbars.js"></script>
+<script type="text/javascript" src="./IssmModule.js"></script>
+<script type="text/javascript" src="./sprintf.js"></script>
 <script type="text/javascript" src="../Exp/Square.js"></script>
 <script type="text/javascript" src="../../src/m/miscellaneous/fielddisplay.js"></script>
@@ -27,6 +32,4 @@
 <script type="text/javascript" src="../../src/wrappers/NodeConnectivity/NodeConnectivity.js"></script>
 <script type="text/javascript" src="../../src/wrappers/ElementConnectivity/ElementConnectivity.js"></script>
-<script type="text/javascript" src="../../build-js/src/wrappers/javascript/IssmModule.js"></script>
-<script type="text/javascript" src="../../externalpackages/javascript/src/sprintf.js"></script>
 <!-- Includes }}}-->
 </head>
@@ -36,5 +39,4 @@
 	<canvas id="figure2" width="640" height="480">
 	</canvas>
-
 
 	<script type="text/javascript" async><!--}}}-->
@@ -47,4 +49,5 @@
 	
 </script> <!--{{{-->
+<canvas id="ISSM-canvas"></canvas>
 </body> 
 </html><!--}}}-->
