Add graphics_WebGLManyPlanetsDeep test.

Also add the HTML and JavaScript source files (pulled from the public
khronos WebGL repository) to deps/webgl_mpd.

BUG=chromium:263300
TEST=test_that.py --fast <DUT IP> graphics_WebGLManyPlanetsDeep

Change-Id: Ia46664f89488cef6414c142e727e8e1e52522731
Reviewed-on: https://chromium-review.googlesource.com/57597
Reviewed-by: Ricky Liang <[email protected]>
Commit-Queue: Ricky Liang <[email protected]>
Tested-by: Ricky Liang <[email protected]>
diff --git a/client/deps/webgl_mpd/common.py b/client/deps/webgl_mpd/common.py
new file mode 100644
index 0000000..9c3d751
--- /dev/null
+++ b/client/deps/webgl_mpd/common.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+
+dirname = os.path.dirname(sys.modules[__name__].__file__)
+# Load setup_modules from client_dir (two level up from current dir).
+client_dir = os.path.abspath(os.path.join(dirname, "..", ".."))
+sys.path.insert(0, client_dir)
+import setup_modules
+sys.path.pop(0)
+setup_modules.setup(base_path=client_dir,
+                    root_module_name="autotest_lib.client")
diff --git a/client/deps/webgl_mpd/control b/client/deps/webgl_mpd/control
new file mode 100644
index 0000000..6a78c41
--- /dev/null
+++ b/client/deps/webgl_mpd/control
@@ -0,0 +1,5 @@
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+job.setup_dep(['webgl_mpd'])
diff --git a/client/deps/webgl_mpd/src/ManyPlanetsDeep.html b/client/deps/webgl_mpd/src/ManyPlanetsDeep.html
new file mode 100644
index 0000000..bc37e52
--- /dev/null
+++ b/client/deps/webgl_mpd/src/ManyPlanetsDeep.html
@@ -0,0 +1,280 @@
+<!DOCTYPE html>
+<!--
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ -->
+<html>
+  <head>
+    <title>Many Planets Deep</title>
+    <script type="text/javascript" src="common/webgl-utils.js"></script>
+    <script type="text/javascript" src="debug/webgl-debug.js"></script>
+    <script type="text/javascript" src="cros_fps.js"></script>
+    <script src="resources/J3DI.js"> </script>
+    <script src="resources/J3DIMath.js" type="text/javascript"> </script>
+
+    <script id="vshader" type="x-shader/x-vertex">
+        uniform mat4 u_modelViewProjMatrix;
+        uniform mat4 u_normalMatrix;
+        uniform vec3 lightDir;
+
+        attribute vec3 vNormal;
+        attribute vec4 vTexCoord;
+        attribute vec4 vPosition;
+
+        varying float v_Dot;
+        varying vec2 v_texCoord;
+
+        void main()
+        {
+            gl_Position = u_modelViewProjMatrix * vPosition;
+            v_texCoord = vTexCoord.st;
+            vec4 transNormal = u_normalMatrix * vec4(vNormal,1);
+            v_Dot = max(dot(transNormal.xyz, lightDir), 0.0);
+        }
+    </script>
+
+    <script id="fshader" type="x-shader/x-fragment">
+        precision mediump float;
+
+        uniform sampler2D sampler2d;
+
+        varying float v_Dot;
+        varying vec2 v_texCoord;
+
+        void main()
+        {
+            vec4 color = texture2D(sampler2d,v_texCoord);
+            color += vec4(0.1,0.1,0.1,1);
+            gl_FragColor = vec4(color.xyz * v_Dot, color.a);
+        }
+    </script>
+
+    <script>
+        const numRowCols = 4;
+        const numLayers = 3;
+        const layoutWidth = 10;
+        const layoutHeight = 8;
+        const globeSize = 25;
+        const minIncAngle = 0.2;
+        const maxIncAngle = 2;
+        var g = {};  // globals
+        var g_crosFpsCounter = new crosFpsCounter();
+        var then = 0.0;
+        var lastJSRenderTime = 0.0;
+
+        function init()
+        {
+            var gl = initWebGL("example");
+            if (!gl) {
+                return;
+            }
+            g.program = simpleSetup(gl, "vshader", "fshader",
+                                [ "vPosition", "vTexCoord", "vNormal"],
+                                [ 0, 0, 0, 1 ], 10000);
+            gl.uniform3f(gl.getUniformLocation(g.program, "lightDir"), 0, 0, 1);
+            gl.uniform1i(gl.getUniformLocation(g.program, "sampler2d"), 0);
+
+            if (g.program) {
+                g.u_normalMatrixLoc = gl.getUniformLocation(g.program, "u_normalMatrix");
+                g.u_modelViewProjMatrixLoc = gl.getUniformLocation(g.program, "u_modelViewProjMatrix");
+            }
+
+            g.sphere = makeSphere(gl, 1, 30, 30);
+
+            // get the images
+            earthTexture = loadImageTexture(gl, "resources/earthmap1k.jpg");
+            marsTexture = loadImageTexture(gl, "resources/mars500x250.png");
+
+            return gl;
+        }
+
+        width = -1;
+        height = -1;
+        var requestId;
+
+        function reshape(ctx)
+        {
+            var canvas = document.getElementById('example');
+            if (canvas.width == width && canvas.height == height)
+                return;
+
+            width = canvas.width;
+            height = canvas.height;
+
+            ctx.viewport(0, 0, width, height);
+
+            g.perspectiveMatrix = new J3DIMatrix4();
+            g.perspectiveMatrix.perspective(30, width/height, 1, 10000);
+            g.perspectiveMatrix.lookat(0,0,20, 0, 0, 0, 0, 1, 0);
+        }
+
+        function drawOne(ctx, angle, x, y, z, scale, texture)
+        {
+            // setup VBOs
+            ctx.enableVertexAttribArray(0);
+            ctx.enableVertexAttribArray(1);
+            ctx.enableVertexAttribArray(2);
+
+            ctx.bindBuffer(ctx.ARRAY_BUFFER, g.sphere.vertexObject);
+            ctx.vertexAttribPointer(0, 3, ctx.FLOAT, false, 0, 0);
+
+            ctx.bindBuffer(ctx.ARRAY_BUFFER, g.sphere.normalObject);
+            ctx.vertexAttribPointer(2, 3, ctx.FLOAT, false, 0, 0);
+
+            ctx.bindBuffer(ctx.ARRAY_BUFFER, g.sphere.texCoordObject);
+            ctx.vertexAttribPointer(1, 2, ctx.FLOAT, false, 0, 0);
+
+            ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, g.sphere.indexObject);
+
+            // generate the model-view matrix
+            var mvMatrix = new J3DIMatrix4();
+            mvMatrix.translate(x,y,z);
+            mvMatrix.rotate(30, 1,0,0);
+            mvMatrix.rotate(angle, 0,1,0);
+            mvMatrix.scale(scale, scale, scale);
+
+            // construct the normal matrix from the model-view matrix
+            var normalMatrix = new J3DIMatrix4(mvMatrix);
+            normalMatrix.invert();
+            normalMatrix.transpose();
+            normalMatrix.setUniform(ctx, g.u_normalMatrixLoc, false);
+
+            // construct the model-view * projection matrix
+            var mvpMatrix = new J3DIMatrix4(g.perspectiveMatrix);
+            mvpMatrix.multiply(mvMatrix);
+            mvpMatrix.setUniform(ctx, g.u_modelViewProjMatrixLoc, false);
+
+            ctx.bindTexture(ctx.TEXTURE_2D, texture);
+            ctx.drawElements(ctx.TRIANGLES, g.sphere.numIndices, ctx.UNSIGNED_SHORT, 0);
+        }
+
+        function drawPicture(ctx)
+        {
+            var now = new Date().getTime();
+            if (then != 0.0) {
+                g_crosFpsCounter.update(then, now - then, lastJSRenderTime);
+            }
+            then = now;
+
+            reshape(ctx);
+	        ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT);
+
+            var startX = -layoutWidth/2;
+            var startY = -layoutHeight/2;
+            var startZ = 0;
+            var incX = layoutWidth / (numRowCols-1);
+            var incY = layoutHeight / (numRowCols-1);
+            var incZ = -5;
+
+            for (i = 0; i < numLayers; ++i) {
+                for (j = 0; j < numRowCols; ++j) {
+                    for (k = 0; k < numRowCols; ++k) {
+                        var index = i * numLayers * numRowCols + j * numRowCols + k;
+
+                        drawOne(ctx, currentAngles[index],
+                                startX + incX * k,
+                                startY + incY * j,
+                                startZ + incZ * i,
+                                showEarth[index] ? 1 : 0.6, showEarth[index] ? earthTexture : marsTexture);
+
+                        currentAngles[index] += incAngles[index];
+                        if (currentAngles[index] > 360)
+                            currentAngles[index] -= 360;
+                    }
+                }
+            }
+
+            ctx.bindTexture(ctx.TEXTURE_2D, null);
+
+            framerate.snapshot();
+            lastJSRenderTime = new Date().getTime() - then;
+        }
+
+        function start()
+        {
+            var c = document.getElementById("example");
+            var w = Math.floor(window.innerWidth * 0.9);
+            var h = Math.floor(window.innerHeight * 0.9);
+
+            //c = WebGLDebugUtils.makeLostContextSimulatingCanvas(c);
+            // tell the simulator when to lose context.
+            //c.loseContextInNCalls(1500);
+
+            c.addEventListener('webglcontextlost', handleContextLost, false);
+            c.addEventListener('webglcontextrestored', handleContextRestored, false);
+
+            c.width = w;
+            c.height = h;
+
+            var ctx = init();
+            if (!ctx) {
+                return;
+            }
+
+            currentAngles = [ ];
+            incAngles = [ ];
+            showEarth = [ ];
+
+            for (var i = 0; i < numRowCols * numRowCols * numLayers; ++i) {
+                currentAngles[i] = 0;
+                incAngles[i] = Math.random() * (maxIncAngle - minIncAngle) + minIncAngle;
+                showEarth[i] = Math.random() > 0.5;
+            }
+
+            framerate = new Framerate("framerate");
+            var f = function() {
+                drawPicture(ctx)
+                requestId = window.requestAnimFrame(f, c);
+            };
+            f();
+
+            function handleContextLost(e) {
+                e.preventDefault();
+                clearLoadingImages();
+                if (requestId !== undefined) {
+                    window.cancelAnimFrame(requestId);
+                    requestId = undefined;
+                }
+            }
+
+            function handleContextRestored() {
+                init();
+                f();
+            }
+        }
+    </script>
+    <style type="text/css">
+        canvas {
+            border: 2px solid black;
+        }
+    </style>
+  </head>
+  <body onload="start()">
+    <canvas id="example">
+    There is supposed to be an example drawing here, but it's not important.
+    </canvas>
+    <div id="framerate"></div>
+  </body>
+</html>
diff --git a/client/deps/webgl_mpd/src/common/webgl-utils.js b/client/deps/webgl_mpd/src/common/webgl-utils.js
new file mode 100644
index 0000000..4f14f7f
--- /dev/null
+++ b/client/deps/webgl_mpd/src/common/webgl-utils.js
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2010, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/**
+ * @fileoverview This file contains functions every webgl program will need
+ * a version of one way or another.
+ *
+ * Instead of setting up a context manually it is recommended to
+ * use. This will check for success or failure. On failure it
+ * will attempt to present an approriate message to the user.
+ *
+ *       gl = WebGLUtils.setupWebGL(canvas);
+ *
+ * For animated WebGL apps use of setTimeout or setInterval are
+ * discouraged. It is recommended you structure your rendering
+ * loop like this.
+ *
+ *       function render() {
+ *         window.requestAnimFrame(render, canvas);
+ *
+ *         // do rendering
+ *         ...
+ *       }
+ *       render();
+ *
+ * This will call your rendering function up to the refresh rate
+ * of your display but will stop rendering if your app is not
+ * visible.
+ */
+
+WebGLUtils = function() {
+
+/**
+ * Creates the HTLM for a failure message
+ * @param {string} canvasContainerId id of container of th
+ *        canvas.
+ * @return {string} The html.
+ */
+var makeFailHTML = function(msg) {
+  return '' +
+    '<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' +
+    '<td align="center">' +
+    '<div style="display: table-cell; vertical-align: middle;">' +
+    '<div style="">' + msg + '</div>' +
+    '</div>' +
+    '</td></tr></table>';
+};
+
+/**
+ * Mesasge for getting a webgl browser
+ * @type {string}
+ */
+var GET_A_WEBGL_BROWSER = '' +
+  'This page requires a browser that supports WebGL.<br/>' +
+  '<a href="http://get.webgl.org">Click here to upgrade your browser.</a>';
+
+/**
+ * Mesasge for need better hardware
+ * @type {string}
+ */
+var OTHER_PROBLEM = '' +
+  "It doesn't appear your computer can support WebGL.<br/>" +
+  '<a href="http://get.webgl.org/troubleshooting/">Click here for more information.</a>';
+
+/**
+ * Creates a webgl context. If creation fails it will
+ * change the contents of the container of the <canvas>
+ * tag to an error message with the correct links for WebGL.
+ * @param {Element} canvas. The canvas element to create a
+ *     context from.
+ * @param {WebGLContextCreationAttirbutes} opt_attribs Any
+ *     creation attributes you want to pass in.
+ * @return {WebGLRenderingContext} The created context.
+ */
+var setupWebGL = function(canvas, opt_attribs) {
+  function showLink(str) {
+    var container = canvas.parentNode;
+    if (container) {
+      container.innerHTML = makeFailHTML(str);
+    }
+  };
+
+  if (!window.WebGLRenderingContext) {
+    showLink(GET_A_WEBGL_BROWSER);
+    return null;
+  }
+
+  var context = create3DContext(canvas, opt_attribs);
+  if (!context) {
+    showLink(OTHER_PROBLEM);
+  }
+  return context;
+};
+
+/**
+ * Creates a webgl context.
+ * @param {!Canvas} canvas The canvas tag to get context
+ *     from. If one is not passed in one will be created.
+ * @return {!WebGLContext} The created context.
+ */
+var create3DContext = function(canvas, opt_attribs) {
+  var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
+  var context = null;
+  for (var ii = 0; ii < names.length; ++ii) {
+    try {
+      context = canvas.getContext(names[ii], opt_attribs);
+    } catch(e) {}
+    if (context) {
+      break;
+    }
+  }
+  return context;
+}
+
+return {
+  create3DContext: create3DContext,
+  setupWebGL: setupWebGL
+};
+}();
+
+/**
+ * Provides requestAnimationFrame in a cross browser way.
+ */
+window.requestAnimFrame = (function() {
+  return window.requestAnimationFrame ||
+         window.webkitRequestAnimationFrame ||
+         window.mozRequestAnimationFrame ||
+         window.oRequestAnimationFrame ||
+         window.msRequestAnimationFrame ||
+         function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
+           return window.setTimeout(callback, 1000/60);
+         };
+})();
+
+/**
+ * Provides cancelAnimationFrame in a cross browser way.
+ */
+window.cancelAnimFrame = (function() {
+  return window.cancelAnimationFrame ||
+         window.webkitCancelAnimationFrame ||
+         window.mozCancelAnimationFrame ||
+         window.oCancelAnimationFrame ||
+         window.msCancelAnimationFrame ||
+         window.clearTimeout;
+})();
+
+
diff --git a/client/deps/webgl_mpd/src/cros_fps.js b/client/deps/webgl_mpd/src/cros_fps.js
new file mode 100644
index 0000000..33d50f7
--- /dev/null
+++ b/client/deps/webgl_mpd/src/cros_fps.js
@@ -0,0 +1,42 @@
+crosFrameData = function(seq, startTime, frameElapsedTime, jsElapsedTime) {
+  this.seq = seq;
+  this.startTime = startTime;
+  this.frameElapsedTime = frameElapsedTime;
+  this.jsElapsedTime = jsElapsedTime;
+}
+
+crosFpsCounter = function() {
+  this.totalElapsedTime = 0.0;
+  this.totalRenderTime = 0.0;
+  this.totalFrames = 0;
+  this.buffer_size = 120;
+  this.frameDataBuf = new Array();
+}
+
+crosFpsCounter.prototype.update = function(
+        startTime, frameElapsedTime, jsElapsedTime) {
+  this.totalFrameElapsedTime += frameElapsedTime;
+  this.totalJSElapsedTime += jsElapsedTime;
+  this.frameDataBuf[this.totalFrames % this.buffer_size] = new crosFrameData(
+      this.totalFrames, startTime, frameElapsedTime, jsElapsedTime);
+  this.totalFrames += 1;
+}
+
+crosFpsCounter.prototype.reset = function() {
+  this.totalFrameElapsedTime = 0.0;
+  this.totalJSElapsedTime = 0.0;
+  this.totalFrames = 0;
+  this.frameDataBuf = new Array();
+}
+
+crosFpsCounter.prototype.getAvgFps = function() {
+  return this.totalFrames / this.totalFrameElapsedTime;
+}
+
+crosFpsCounter.prototype.getAvgRenderTime = function() {
+  return this.totalJSElapsedTime / this.totalFrames;
+}
+
+crosFpsCounter.prototype.getFrameData = function() {
+  return this.frameDataBuf;
+}
diff --git a/client/deps/webgl_mpd/src/debug/webgl-debug.js b/client/deps/webgl_mpd/src/debug/webgl-debug.js
new file mode 100644
index 0000000..1e68146
--- /dev/null
+++ b/client/deps/webgl_mpd/src/debug/webgl-debug.js
@@ -0,0 +1,900 @@
+/*
+** Copyright (c) 2012 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a
+** copy of this software and/or associated documentation files (the
+** "Materials"), to deal in the Materials without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Materials, and to
+** permit persons to whom the Materials are furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be included
+** in all copies or substantial portions of the Materials.
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+*/
+
+// Various functions for helping debug WebGL apps.
+
+WebGLDebugUtils = function() {
+
+/**
+ * Wrapped logging function.
+ * @param {string} msg Message to log.
+ */
+var log = function(msg) {
+  if (window.console && window.console.log) {
+    window.console.log(msg);
+  }
+};
+
+/**
+ * Wrapped error logging function.
+ * @param {string} msg Message to log.
+ */
+var error = function(msg) {
+  if (window.console && window.console.error) {
+    window.console.error(msg);
+  } else {
+    log(msg);
+  }
+};
+
+
+/**
+ * Which arguments are enums based on the number of arguments to the function.
+ * So
+ *    'texImage2D': {
+ *       9: { 0:true, 2:true, 6:true, 7:true },
+ *       6: { 0:true, 2:true, 3:true, 4:true },
+ *    },
+ *
+ * means if there are 9 arguments then 6 and 7 are enums, if there are 6
+ * arguments 3 and 4 are enums
+ *
+ * @type {!Object.<number, !Object.<number, string>}}
+ */
+var glValidEnumContexts = {
+  // Generic setters and getters
+
+  'enable': {1: { 0:true }},
+  'disable': {1: { 0:true }},
+  'getParameter': {1: { 0:true }},
+
+  // Rendering
+
+  'drawArrays': {3:{ 0:true }},
+  'drawElements': {4:{ 0:true, 2:true }},
+
+  // Shaders
+
+  'createShader': {1: { 0:true }},
+  'getShaderParameter': {2: { 1:true }},
+  'getProgramParameter': {2: { 1:true }},
+  'getShaderPrecisionFormat': {2: { 0: true, 1:true }},
+
+  // Vertex attributes
+
+  'getVertexAttrib': {2: { 1:true }},
+  'vertexAttribPointer': {6: { 2:true }},
+
+  // Textures
+
+  'bindTexture': {2: { 0:true }},
+  'activeTexture': {1: { 0:true }},
+  'getTexParameter': {2: { 0:true, 1:true }},
+  'texParameterf': {3: { 0:true, 1:true }},
+  'texParameteri': {3: { 0:true, 1:true, 2:true }},
+  'texImage2D': {
+     9: { 0:true, 2:true, 6:true, 7:true },
+     6: { 0:true, 2:true, 3:true, 4:true },
+  },
+  'texSubImage2D': {
+    9: { 0:true, 6:true, 7:true },
+    7: { 0:true, 4:true, 5:true },
+  },
+  'copyTexImage2D': {8: { 0:true, 2:true }},
+  'copyTexSubImage2D': {8: { 0:true }},
+  'generateMipmap': {1: { 0:true }},
+  'compressedTexImage2D': {7: { 0: true, 2:true }},
+  'compressedTexSubImage2D': {8: { 0: true, 6:true }},
+
+  // Buffer objects
+
+  'bindBuffer': {2: { 0:true }},
+  'bufferData': {3: { 0:true, 2:true }},
+  'bufferSubData': {3: { 0:true }},
+  'getBufferParameter': {2: { 0:true, 1:true }},
+
+  // Renderbuffers and framebuffers
+
+  'pixelStorei': {2: { 0:true, 1:true }},
+  'readPixels': {7: { 4:true, 5:true }},
+  'bindRenderbuffer': {2: { 0:true }},
+  'bindFramebuffer': {2: { 0:true }},
+  'checkFramebufferStatus': {1: { 0:true }},
+  'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }},
+  'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }},
+  'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }},
+  'getRenderbufferParameter': {2: { 0:true, 1:true }},
+  'renderbufferStorage': {4: { 0:true, 1:true }},
+
+  // Frame buffer operations (clear, blend, depth test, stencil)
+
+  'clear': {1: { 0:true }},
+  'depthFunc': {1: { 0:true }},
+  'blendFunc': {2: { 0:true, 1:true }},
+  'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
+  'blendEquation': {1: { 0:true }},
+  'blendEquationSeparate': {2: { 0:true, 1:true }},
+  'stencilFunc': {3: { 0:true }},
+  'stencilFuncSeparate': {4: { 0:true, 1:true }},
+  'stencilMaskSeparate': {2: { 0:true }},
+  'stencilOp': {3: { 0:true, 1:true, 2:true }},
+  'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }},
+
+  // Culling
+
+  'cullFace': {1: { 0:true }},
+  'frontFace': {1: { 0:true }},
+};
+
+/**
+ * Map of numbers to names.
+ * @type {Object}
+ */
+var glEnums = null;
+
+/**
+ * Initializes this module. Safe to call more than once.
+ * @param {!WebGLRenderingContext} ctx A WebGL context. If
+ *    you have more than one context it doesn't matter which one
+ *    you pass in, it is only used to pull out constants.
+ */
+function init(ctx) {
+  if (glEnums == null) {
+    glEnums = { };
+    for (var propertyName in ctx) {
+      if (typeof ctx[propertyName] == 'number') {
+        glEnums[ctx[propertyName]] = propertyName;
+      }
+    }
+  }
+}
+
+/**
+ * Checks the utils have been initialized.
+ */
+function checkInit() {
+  if (glEnums == null) {
+    throw 'WebGLDebugUtils.init(ctx) not called';
+  }
+}
+
+/**
+ * Returns true or false if value matches any WebGL enum
+ * @param {*} value Value to check if it might be an enum.
+ * @return {boolean} True if value matches one of the WebGL defined enums
+ */
+function mightBeEnum(value) {
+  checkInit();
+  return (glEnums[value] !== undefined);
+}
+
+/**
+ * Gets an string version of an WebGL enum.
+ *
+ * Example:
+ *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
+ *
+ * @param {number} value Value to return an enum for
+ * @return {string} The string version of the enum.
+ */
+function glEnumToString(value) {
+  checkInit();
+  var name = glEnums[value];
+  return (name !== undefined) ? ("gl." + name) :
+      ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + "");
+}
+
+/**
+ * Returns the string version of a WebGL argument.
+ * Attempts to convert enum arguments to strings.
+ * @param {string} functionName the name of the WebGL function.
+ * @param {number} numArgs the number of arguments passed to the function.
+ * @param {number} argumentIndx the index of the argument.
+ * @param {*} value The value of the argument.
+ * @return {string} The value as a string.
+ */
+function glFunctionArgToString(functionName, numArgs, argumentIndex, value) {
+  var funcInfo = glValidEnumContexts[functionName];
+  if (funcInfo !== undefined) {
+    var funcInfo = funcInfo[numArgs];
+    if (funcInfo !== undefined) {
+      if (funcInfo[argumentIndex]) {
+        return glEnumToString(value);
+      }
+    }
+  }
+  if (value === null) {
+    return "null";
+  } else if (value === undefined) {
+    return "undefined";
+  } else {
+    return value.toString();
+  }
+}
+
+/**
+ * Converts the arguments of a WebGL function to a string.
+ * Attempts to convert enum arguments to strings.
+ *
+ * @param {string} functionName the name of the WebGL function.
+ * @param {number} args The arguments.
+ * @return {string} The arguments as a string.
+ */
+function glFunctionArgsToString(functionName, args) {
+  // apparently we can't do args.join(",");
+  var argStr = "";
+  var numArgs = args.length;
+  for (var ii = 0; ii < numArgs; ++ii) {
+    argStr += ((ii == 0) ? '' : ', ') +
+        glFunctionArgToString(functionName, numArgs, ii, args[ii]);
+  }
+  return argStr;
+};
+
+
+function makePropertyWrapper(wrapper, original, propertyName) {
+  //log("wrap prop: " + propertyName);
+  wrapper.__defineGetter__(propertyName, function() {
+    return original[propertyName];
+  });
+  // TODO(gmane): this needs to handle properties that take more than
+  // one value?
+  wrapper.__defineSetter__(propertyName, function(value) {
+    //log("set: " + propertyName);
+    original[propertyName] = value;
+  });
+}
+
+// Makes a function that calls a function on another object.
+function makeFunctionWrapper(original, functionName) {
+  //log("wrap fn: " + functionName);
+  var f = original[functionName];
+  return function() {
+    //log("call: " + functionName);
+    var result = f.apply(original, arguments);
+    return result;
+  };
+}
+
+/**
+ * Given a WebGL context returns a wrapped context that calls
+ * gl.getError after every command and calls a function if the
+ * result is not gl.NO_ERROR.
+ *
+ * @param {!WebGLRenderingContext} ctx The webgl context to
+ *        wrap.
+ * @param {!function(err, funcName, args): void} opt_onErrorFunc
+ *        The function to call when gl.getError returns an
+ *        error. If not specified the default function calls
+ *        console.log with a message.
+ * @param {!function(funcName, args): void} opt_onFunc The
+ *        function to call when each webgl function is called.
+ *        You can use this to log all calls for example.
+ */
+function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc) {
+  init(ctx);
+  opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {
+        // apparently we can't do args.join(",");
+        var argStr = "";
+        var numArgs = args.length;
+        for (var ii = 0; ii < numArgs; ++ii) {
+          argStr += ((ii == 0) ? '' : ', ') +
+              glFunctionArgToString(functionName, numArgs, ii, args[ii]);
+        }
+        error("WebGL error "+ glEnumToString(err) + " in "+ functionName +
+              "(" + argStr + ")");
+      };
+
+  // Holds booleans for each GL error so after we get the error ourselves
+  // we can still return it to the client app.
+  var glErrorShadow = { };
+
+  // Makes a function that calls a WebGL function and then calls getError.
+  function makeErrorWrapper(ctx, functionName) {
+    return function() {
+      if (opt_onFunc) {
+        opt_onFunc(functionName, arguments);
+      }
+      var result = ctx[functionName].apply(ctx, arguments);
+      var err = ctx.getError();
+      if (err != 0) {
+        glErrorShadow[err] = true;
+        opt_onErrorFunc(err, functionName, arguments);
+      }
+      return result;
+    };
+  }
+
+  // Make a an object that has a copy of every property of the WebGL context
+  // but wraps all functions.
+  var wrapper = {};
+  for (var propertyName in ctx) {
+    if (typeof ctx[propertyName] == 'function') {
+       wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
+     } else {
+       makePropertyWrapper(wrapper, ctx, propertyName);
+     }
+  }
+
+  // Override the getError function with one that returns our saved results.
+  wrapper.getError = function() {
+    for (var err in glErrorShadow) {
+      if (glErrorShadow.hasOwnProperty(err)) {
+        if (glErrorShadow[err]) {
+          glErrorShadow[err] = false;
+          return err;
+        }
+      }
+    }
+    return ctx.NO_ERROR;
+  };
+
+  return wrapper;
+}
+
+function resetToInitialState(ctx) {
+  var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
+  var tmp = ctx.createBuffer();
+  ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);
+  for (var ii = 0; ii < numAttribs; ++ii) {
+    ctx.disableVertexAttribArray(ii);
+    ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);
+    ctx.vertexAttrib1f(ii, 0);
+  }
+  ctx.deleteBuffer(tmp);
+
+  var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
+  for (var ii = 0; ii < numTextureUnits; ++ii) {
+    ctx.activeTexture(ctx.TEXTURE0 + ii);
+    ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);
+    ctx.bindTexture(ctx.TEXTURE_2D, null);
+  }
+
+  ctx.activeTexture(ctx.TEXTURE0);
+  ctx.useProgram(null);
+  ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
+  ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
+  ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);
+  ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);
+  ctx.disable(ctx.BLEND);
+  ctx.disable(ctx.CULL_FACE);
+  ctx.disable(ctx.DEPTH_TEST);
+  ctx.disable(ctx.DITHER);
+  ctx.disable(ctx.SCISSOR_TEST);
+  ctx.blendColor(0, 0, 0, 0);
+  ctx.blendEquation(ctx.FUNC_ADD);
+  ctx.blendFunc(ctx.ONE, ctx.ZERO);
+  ctx.clearColor(0, 0, 0, 0);
+  ctx.clearDepth(1);
+  ctx.clearStencil(-1);
+  ctx.colorMask(true, true, true, true);
+  ctx.cullFace(ctx.BACK);
+  ctx.depthFunc(ctx.LESS);
+  ctx.depthMask(true);
+  ctx.depthRange(0, 1);
+  ctx.frontFace(ctx.CCW);
+  ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);
+  ctx.lineWidth(1);
+  ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);
+  ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);
+  ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);
+  ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+  // TODO: Delete this IF.
+  if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {
+    ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);
+  }
+  ctx.polygonOffset(0, 0);
+  ctx.sampleCoverage(1, false);
+  ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);
+  ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);
+  ctx.stencilMask(0xFFFFFFFF);
+  ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);
+  ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height);
+  ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
+
+  // TODO: This should NOT be needed but Firefox fails with 'hint'
+  while(ctx.getError());
+}
+
+function makeLostContextSimulatingCanvas(canvas) {
+  var unwrappedContext_;
+  var wrappedContext_;
+  var onLost_ = [];
+  var onRestored_ = [];
+  var wrappedContext_ = {};
+  var contextId_ = 1;
+  var contextLost_ = false;
+  var resourceId_ = 0;
+  var resourceDb_ = [];
+  var numCallsToLoseContext_ = 0;
+  var numCalls_ = 0;
+  var canRestore_ = false;
+  var restoreTimeout_ = 0;
+
+  // Holds booleans for each GL error so can simulate errors.
+  var glErrorShadow_ = { };
+
+  canvas.getContext = function(f) {
+    return function() {
+      var ctx = f.apply(canvas, arguments);
+      // Did we get a context and is it a WebGL context?
+      if (ctx instanceof WebGLRenderingContext) {
+        if (ctx != unwrappedContext_) {
+          if (unwrappedContext_) {
+            throw "got different context"
+          }
+          unwrappedContext_ = ctx;
+          wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_);
+        }
+        return wrappedContext_;
+      }
+      return ctx;
+    }
+  }(canvas.getContext);
+
+  function wrapEvent(listener) {
+    if (typeof(listener) == "function") {
+      return listener;
+    } else {
+      return function(info) {
+        listener.handleEvent(info);
+      }
+    }
+  }
+
+  var addOnContextLostListener = function(listener) {
+    onLost_.push(wrapEvent(listener));
+  };
+
+  var addOnContextRestoredListener = function(listener) {
+    onRestored_.push(wrapEvent(listener));
+  };
+
+
+  function wrapAddEventListener(canvas) {
+    var f = canvas.addEventListener;
+    canvas.addEventListener = function(type, listener, bubble) {
+      switch (type) {
+        case 'webglcontextlost':
+          addOnContextLostListener(listener);
+          break;
+        case 'webglcontextrestored':
+          addOnContextRestoredListener(listener);
+          break;
+        default:
+          f.apply(canvas, arguments);
+      }
+    };
+  }
+
+  wrapAddEventListener(canvas);
+
+  canvas.loseContext = function() {
+    if (!contextLost_) {
+      contextLost_ = true;
+      numCallsToLoseContext_ = 0;
+      ++contextId_;
+      while (unwrappedContext_.getError());
+      clearErrors();
+      glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true;
+      var event = makeWebGLContextEvent("context lost");
+      var callbacks = onLost_.slice();
+      setTimeout(function() {
+          //log("numCallbacks:" + callbacks.length);
+          for (var ii = 0; ii < callbacks.length; ++ii) {
+            //log("calling callback:" + ii);
+            callbacks[ii](event);
+          }
+          if (restoreTimeout_ >= 0) {
+            setTimeout(function() {
+                canvas.restoreContext();
+              }, restoreTimeout_);
+          }
+        }, 0);
+    }
+  };
+
+  canvas.restoreContext = function() {
+    if (contextLost_) {
+      if (onRestored_.length) {
+        setTimeout(function() {
+            if (!canRestore_) {
+              throw "can not restore. webglcontestlost listener did not call event.preventDefault";
+            }
+            freeResources();
+            resetToInitialState(unwrappedContext_);
+            contextLost_ = false;
+            numCalls_ = 0;
+            canRestore_ = false;
+            var callbacks = onRestored_.slice();
+            var event = makeWebGLContextEvent("context restored");
+            for (var ii = 0; ii < callbacks.length; ++ii) {
+              callbacks[ii](event);
+            }
+          }, 0);
+      }
+    }
+  };
+
+  canvas.loseContextInNCalls = function(numCalls) {
+    if (contextLost_) {
+      throw "You can not ask a lost contet to be lost";
+    }
+    numCallsToLoseContext_ = numCalls_ + numCalls;
+  };
+
+  canvas.getNumCalls = function() {
+    return numCalls_;
+  };
+
+  canvas.setRestoreTimeout = function(timeout) {
+    restoreTimeout_ = timeout;
+  };
+
+  function isWebGLObject(obj) {
+    //return false;
+    return (obj instanceof WebGLBuffer ||
+            obj instanceof WebGLFramebuffer ||
+            obj instanceof WebGLProgram ||
+            obj instanceof WebGLRenderbuffer ||
+            obj instanceof WebGLShader ||
+            obj instanceof WebGLTexture);
+  }
+
+  function checkResources(args) {
+    for (var ii = 0; ii < args.length; ++ii) {
+      var arg = args[ii];
+      if (isWebGLObject(arg)) {
+        return arg.__webglDebugContextLostId__ == contextId_;
+      }
+    }
+    return true;
+  }
+
+  function clearErrors() {
+    var k = Object.keys(glErrorShadow_);
+    for (var ii = 0; ii < k.length; ++ii) {
+      delete glErrorShadow_[k];
+    }
+  }
+
+  function loseContextIfTime() {
+    ++numCalls_;
+    if (!contextLost_) {
+      if (numCallsToLoseContext_ == numCalls_) {
+        canvas.loseContext();
+      }
+    }
+  }
+
+  // Makes a function that simulates WebGL when out of context.
+  function makeLostContextFunctionWrapper(ctx, functionName) {
+    var f = ctx[functionName];
+    return function() {
+      // log("calling:" + functionName);
+      // Only call the functions if the context is not lost.
+      loseContextIfTime();
+      if (!contextLost_) {
+        //if (!checkResources(arguments)) {
+        //  glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true;
+        //  return;
+        //}
+        var result = f.apply(ctx, arguments);
+        return result;
+      }
+    };
+  }
+
+  function freeResources() {
+    for (var ii = 0; ii < resourceDb_.length; ++ii) {
+      var resource = resourceDb_[ii];
+      if (resource instanceof WebGLBuffer) {
+        unwrappedContext_.deleteBuffer(resource);
+      } else if (resource instanceof WebGLFramebuffer) {
+        unwrappedContext_.deleteFramebuffer(resource);
+      } else if (resource instanceof WebGLProgram) {
+        unwrappedContext_.deleteProgram(resource);
+      } else if (resource instanceof WebGLRenderbuffer) {
+        unwrappedContext_.deleteRenderbuffer(resource);
+      } else if (resource instanceof WebGLShader) {
+        unwrappedContext_.deleteShader(resource);
+      } else if (resource instanceof WebGLTexture) {
+        unwrappedContext_.deleteTexture(resource);
+      }
+    }
+  }
+
+  function makeWebGLContextEvent(statusMessage) {
+    return {
+      statusMessage: statusMessage,
+      preventDefault: function() {
+          canRestore_ = true;
+        }
+    };
+  }
+
+  return canvas;
+
+  function makeLostContextSimulatingContext(ctx) {
+    // copy all functions and properties to wrapper
+    for (var propertyName in ctx) {
+      if (typeof ctx[propertyName] == 'function') {
+         wrappedContext_[propertyName] = makeLostContextFunctionWrapper(
+             ctx, propertyName);
+       } else {
+         makePropertyWrapper(wrappedContext_, ctx, propertyName);
+       }
+    }
+
+    // Wrap a few functions specially.
+    wrappedContext_.getError = function() {
+      loseContextIfTime();
+      if (!contextLost_) {
+        var err;
+        while (err = unwrappedContext_.getError()) {
+          glErrorShadow_[err] = true;
+        }
+      }
+      for (var err in glErrorShadow_) {
+        if (glErrorShadow_[err]) {
+          delete glErrorShadow_[err];
+          return err;
+        }
+      }
+      return wrappedContext_.NO_ERROR;
+    };
+
+    var creationFunctions = [
+      "createBuffer",
+      "createFramebuffer",
+      "createProgram",
+      "createRenderbuffer",
+      "createShader",
+      "createTexture"
+    ];
+    for (var ii = 0; ii < creationFunctions.length; ++ii) {
+      var functionName = creationFunctions[ii];
+      wrappedContext_[functionName] = function(f) {
+        return function() {
+          loseContextIfTime();
+          if (contextLost_) {
+            return null;
+          }
+          var obj = f.apply(ctx, arguments);
+          obj.__webglDebugContextLostId__ = contextId_;
+          resourceDb_.push(obj);
+          return obj;
+        };
+      }(ctx[functionName]);
+    }
+
+    var functionsThatShouldReturnNull = [
+      "getActiveAttrib",
+      "getActiveUniform",
+      "getBufferParameter",
+      "getContextAttributes",
+      "getAttachedShaders",
+      "getFramebufferAttachmentParameter",
+      "getParameter",
+      "getProgramParameter",
+      "getProgramInfoLog",
+      "getRenderbufferParameter",
+      "getShaderParameter",
+      "getShaderInfoLog",
+      "getShaderSource",
+      "getTexParameter",
+      "getUniform",
+      "getUniformLocation",
+      "getVertexAttrib"
+    ];
+    for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {
+      var functionName = functionsThatShouldReturnNull[ii];
+      wrappedContext_[functionName] = function(f) {
+        return function() {
+          loseContextIfTime();
+          if (contextLost_) {
+            return null;
+          }
+          return f.apply(ctx, arguments);
+        }
+      }(wrappedContext_[functionName]);
+    }
+
+    var isFunctions = [
+      "isBuffer",
+      "isEnabled",
+      "isFramebuffer",
+      "isProgram",
+      "isRenderbuffer",
+      "isShader",
+      "isTexture"
+    ];
+    for (var ii = 0; ii < isFunctions.length; ++ii) {
+      var functionName = isFunctions[ii];
+      wrappedContext_[functionName] = function(f) {
+        return function() {
+          loseContextIfTime();
+          if (contextLost_) {
+            return false;
+          }
+          return f.apply(ctx, arguments);
+        }
+      }(wrappedContext_[functionName]);
+    }
+
+    wrappedContext_.checkFramebufferStatus = function(f) {
+      return function() {
+        loseContextIfTime();
+        if (contextLost_) {
+          return wrappedContext_.FRAMEBUFFER_UNSUPPORTED;
+        }
+        return f.apply(ctx, arguments);
+      };
+    }(wrappedContext_.checkFramebufferStatus);
+
+    wrappedContext_.getAttribLocation = function(f) {
+      return function() {
+        loseContextIfTime();
+        if (contextLost_) {
+          return -1;
+        }
+        return f.apply(ctx, arguments);
+      };
+    }(wrappedContext_.getAttribLocation);
+
+    wrappedContext_.getVertexAttribOffset = function(f) {
+      return function() {
+        loseContextIfTime();
+        if (contextLost_) {
+          return 0;
+        }
+        return f.apply(ctx, arguments);
+      };
+    }(wrappedContext_.getVertexAttribOffset);
+
+    wrappedContext_.isContextLost = function() {
+      return contextLost_;
+    };
+
+    return wrappedContext_;
+  }
+}
+
+return {
+    /**
+     * Initializes this module. Safe to call more than once.
+     * @param {!WebGLRenderingContext} ctx A WebGL context. If
+    }
+   *    you have more than one context it doesn't matter which one
+   *    you pass in, it is only used to pull out constants.
+   */
+  'init': init,
+
+  /**
+   * Returns true or false if value matches any WebGL enum
+   * @param {*} value Value to check if it might be an enum.
+   * @return {boolean} True if value matches one of the WebGL defined enums
+   */
+  'mightBeEnum': mightBeEnum,
+
+  /**
+   * Gets an string version of an WebGL enum.
+   *
+   * Example:
+   *   WebGLDebugUtil.init(ctx);
+   *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
+   *
+   * @param {number} value Value to return an enum for
+   * @return {string} The string version of the enum.
+   */
+  'glEnumToString': glEnumToString,
+
+  /**
+   * Converts the argument of a WebGL function to a string.
+   * Attempts to convert enum arguments to strings.
+   *
+   * Example:
+   *   WebGLDebugUtil.init(ctx);
+   *   var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D);
+   *
+   * would return 'TEXTURE_2D'
+   *
+   * @param {string} functionName the name of the WebGL function.
+   * @param {number} numArgs The number of arguments
+   * @param {number} argumentIndx the index of the argument.
+   * @param {*} value The value of the argument.
+   * @return {string} The value as a string.
+   */
+  'glFunctionArgToString': glFunctionArgToString,
+
+  /**
+   * Converts the arguments of a WebGL function to a string.
+   * Attempts to convert enum arguments to strings.
+   *
+   * @param {string} functionName the name of the WebGL function.
+   * @param {number} args The arguments.
+   * @return {string} The arguments as a string.
+   */
+  'glFunctionArgsToString': glFunctionArgsToString,
+
+  /**
+   * Given a WebGL context returns a wrapped context that calls
+   * gl.getError after every command and calls a function if the
+   * result is not NO_ERROR.
+   *
+   * You can supply your own function if you want. For example, if you'd like
+   * an exception thrown on any GL error you could do this
+   *
+   *    function throwOnGLError(err, funcName, args) {
+   *      throw WebGLDebugUtils.glEnumToString(err) +
+   *            " was caused by call to " + funcName;
+   *    };
+   *
+   *    ctx = WebGLDebugUtils.makeDebugContext(
+   *        canvas.getContext("webgl"), throwOnGLError);
+   *
+   * @param {!WebGLRenderingContext} ctx The webgl context to wrap.
+   * @param {!function(err, funcName, args): void} opt_onErrorFunc The function
+   *     to call when gl.getError returns an error. If not specified the default
+   *     function calls console.log with a message.
+   * @param {!function(funcName, args): void} opt_onFunc The
+   *     function to call when each webgl function is called. You
+   *     can use this to log all calls for example.
+   */
+  'makeDebugContext': makeDebugContext,
+
+  /**
+   * Given a canvas element returns a wrapped canvas element that will
+   * simulate lost context. The canvas returned adds the following functions.
+   *
+   * loseContext:
+   *   simulates a lost context event.
+   *
+   * restoreContext:
+   *   simulates the context being restored.
+   *
+   * lostContextInNCalls:
+   *   loses the context after N gl calls.
+   *
+   * getNumCalls:
+   *   tells you how many gl calls there have been so far.
+   *
+   * setRestoreTimeout:
+   *   sets the number of milliseconds until the context is restored
+   *   after it has been lost. Defaults to 0. Pass -1 to prevent
+   *   automatic restoring.
+   *
+   * @param {!Canvas} canvas The canvas element to wrap.
+   */
+  'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas,
+
+  /**
+   * Resets a context to the initial state.
+   * @param {!WebGLRenderingContext} ctx The webgl context to
+   *     reset.
+   */
+  'resetToInitialState': resetToInitialState
+};
+
+}();
+
diff --git a/client/deps/webgl_mpd/src/resources/J3DI.js b/client/deps/webgl_mpd/src/resources/J3DI.js
new file mode 100644
index 0000000..597417b
--- /dev/null
+++ b/client/deps/webgl_mpd/src/resources/J3DI.js
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//
+// initWebGL
+//
+// Initialize the Canvas element with the passed name as a WebGL object and return the
+// WebGLRenderingContext.
+function initWebGL(canvasName, vshader, fshader, attribs, clearColor, clearDepth)
+{
+    var canvas = document.getElementById(canvasName);
+    return gl = WebGLUtils.setupWebGL(canvas);
+}
+
+function log(msg) {
+    if (window.console && window.console.log) {
+        window.console.log(msg);
+    }
+}
+
+// Load shaders with the passed names and create a program with them. Return this program
+// in the 'program' property of the returned context.
+//
+// For each string in the passed attribs array, bind an attrib with that name at that index.
+// Once the attribs are bound, link the program and then use it.
+//
+// Set the clear color to the passed array (4 values) and set the clear depth to the passed value.
+// Enable depth testing and blending with a blend func of (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
+//
+// A console function is added to the context: console(string). This can be replaced
+// by the caller. By default, it maps to the window.console() function on WebKit and to
+// an empty function on other browsers.
+//
+function simpleSetup(gl, vshader, fshader, attribs, clearColor, clearDepth)
+{
+    // create our shaders
+    var vertexShader = loadShader(gl, vshader);
+    var fragmentShader = loadShader(gl, fshader);
+
+    // Create the program object
+    var program = gl.createProgram();
+
+    // Attach our two shaders to the program
+    gl.attachShader (program, vertexShader);
+    gl.attachShader (program, fragmentShader);
+
+    // Bind attributes
+    for (var i = 0; i < attribs.length; ++i)
+        gl.bindAttribLocation (program, i, attribs[i]);
+
+    // Link the program
+    gl.linkProgram(program);
+
+    // Check the link status
+    var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+    if (!linked && !gl.isContextLost()) {
+        // something went wrong with the link
+        var error = gl.getProgramInfoLog (program);
+        log("Error in program linking:"+error);
+
+        gl.deleteProgram(program);
+        gl.deleteProgram(fragmentShader);
+        gl.deleteProgram(vertexShader);
+
+        return null;
+    }
+
+    gl.useProgram(program);
+
+    gl.clearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
+    gl.clearDepth(clearDepth);
+
+    gl.enable(gl.DEPTH_TEST);
+    gl.enable(gl.BLEND);
+    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
+
+    return program;
+}
+
+//
+// loadShader
+//
+// 'shaderId' is the id of a <script> element containing the shader source string.
+// Load this shader and return the WebGLShader object corresponding to it.
+//
+function loadShader(ctx, shaderId)
+{
+    var shaderScript = document.getElementById(shaderId);
+    if (!shaderScript) {
+        log("*** Error: shader script '"+shaderId+"' not found");
+        return null;
+    }
+
+    if (shaderScript.type == "x-shader/x-vertex")
+        var shaderType = ctx.VERTEX_SHADER;
+    else if (shaderScript.type == "x-shader/x-fragment")
+        var shaderType = ctx.FRAGMENT_SHADER;
+    else {
+        log("*** Error: shader script '"+shaderId+"' of undefined type '"+shaderScript.type+"'");
+        return null;
+    }
+
+    // Create the shader object
+    var shader = ctx.createShader(shaderType);
+
+    // Load the shader source
+    ctx.shaderSource(shader, shaderScript.text);
+
+    // Compile the shader
+    ctx.compileShader(shader);
+
+    // Check the compile status
+    var compiled = ctx.getShaderParameter(shader, ctx.COMPILE_STATUS);
+    if (!compiled && !ctx.isContextLost()) {
+        // Something went wrong during compilation; get the error
+        var error = ctx.getShaderInfoLog(shader);
+        log("*** Error compiling shader '"+shaderId+"':"+error);
+        ctx.deleteShader(shader);
+        return null;
+    }
+
+    return shader;
+}
+
+//
+// makeBox
+//
+// Create a box with vertices, normals and texCoords. Create VBOs for each as well as the index array.
+// Return an object with the following properties:
+//
+//  normalObject        WebGLBuffer object for normals
+//  texCoordObject      WebGLBuffer object for texCoords
+//  vertexObject        WebGLBuffer object for vertices
+//  indexObject         WebGLBuffer object for indices
+//  numIndices          The number of indices in the indexObject
+//
+function makeBox(ctx)
+{
+    // box
+    //    v6----- v5
+    //   /|      /|
+    //  v1------v0|
+    //  | |     | |
+    //  | |v7---|-|v4
+    //  |/      |/
+    //  v2------v3
+    //
+    // vertex coords array
+    var vertices = new Float32Array(
+        [  1, 1, 1,  -1, 1, 1,  -1,-1, 1,   1,-1, 1,    // v0-v1-v2-v3 front
+           1, 1, 1,   1,-1, 1,   1,-1,-1,   1, 1,-1,    // v0-v3-v4-v5 right
+           1, 1, 1,   1, 1,-1,  -1, 1,-1,  -1, 1, 1,    // v0-v5-v6-v1 top
+          -1, 1, 1,  -1, 1,-1,  -1,-1,-1,  -1,-1, 1,    // v1-v6-v7-v2 left
+          -1,-1,-1,   1,-1,-1,   1,-1, 1,  -1,-1, 1,    // v7-v4-v3-v2 bottom
+           1,-1,-1,  -1,-1,-1,  -1, 1,-1,   1, 1,-1 ]   // v4-v7-v6-v5 back
+    );
+
+    // normal array
+    var normals = new Float32Array(
+        [  0, 0, 1,   0, 0, 1,   0, 0, 1,   0, 0, 1,     // v0-v1-v2-v3 front
+           1, 0, 0,   1, 0, 0,   1, 0, 0,   1, 0, 0,     // v0-v3-v4-v5 right
+           0, 1, 0,   0, 1, 0,   0, 1, 0,   0, 1, 0,     // v0-v5-v6-v1 top
+          -1, 0, 0,  -1, 0, 0,  -1, 0, 0,  -1, 0, 0,     // v1-v6-v7-v2 left
+           0,-1, 0,   0,-1, 0,   0,-1, 0,   0,-1, 0,     // v7-v4-v3-v2 bottom
+           0, 0,-1,   0, 0,-1,   0, 0,-1,   0, 0,-1 ]    // v4-v7-v6-v5 back
+       );
+
+
+    // texCoord array
+    var texCoords = new Float32Array(
+        [  1, 1,   0, 1,   0, 0,   1, 0,    // v0-v1-v2-v3 front
+           0, 1,   0, 0,   1, 0,   1, 1,    // v0-v3-v4-v5 right
+           1, 0,   1, 1,   0, 1,   0, 0,    // v0-v5-v6-v1 top
+           1, 1,   0, 1,   0, 0,   1, 0,    // v1-v6-v7-v2 left
+           0, 0,   1, 0,   1, 1,   0, 1,    // v7-v4-v3-v2 bottom
+           0, 0,   1, 0,   1, 1,   0, 1 ]   // v4-v7-v6-v5 back
+       );
+
+    // index array
+    var indices = new Uint8Array(
+        [  0, 1, 2,   0, 2, 3,    // front
+           4, 5, 6,   4, 6, 7,    // right
+           8, 9,10,   8,10,11,    // top
+          12,13,14,  12,14,15,    // left
+          16,17,18,  16,18,19,    // bottom
+          20,21,22,  20,22,23 ]   // back
+      );
+
+    var retval = { };
+
+    retval.normalObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
+    ctx.bufferData(ctx.ARRAY_BUFFER, normals, ctx.STATIC_DRAW);
+
+    retval.texCoordObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
+    ctx.bufferData(ctx.ARRAY_BUFFER, texCoords, ctx.STATIC_DRAW);
+
+    retval.vertexObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
+    ctx.bufferData(ctx.ARRAY_BUFFER, vertices, ctx.STATIC_DRAW);
+
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
+
+    retval.indexObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
+    ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, indices, ctx.STATIC_DRAW);
+    ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
+
+    retval.numIndices = indices.length;
+
+    return retval;
+}
+
+//
+// makeSphere
+//
+// Create a sphere with the passed number of latitude and longitude bands and the passed radius.
+// Sphere has vertices, normals and texCoords. Create VBOs for each as well as the index array.
+// Return an object with the following properties:
+//
+//  normalObject        WebGLBuffer object for normals
+//  texCoordObject      WebGLBuffer object for texCoords
+//  vertexObject        WebGLBuffer object for vertices
+//  indexObject         WebGLBuffer object for indices
+//  numIndices          The number of indices in the indexObject
+//
+function makeSphere(ctx, radius, lats, longs)
+{
+    var geometryData = [ ];
+    var normalData = [ ];
+    var texCoordData = [ ];
+    var indexData = [ ];
+
+    for (var latNumber = 0; latNumber <= lats; ++latNumber) {
+        for (var longNumber = 0; longNumber <= longs; ++longNumber) {
+            var theta = latNumber * Math.PI / lats;
+            var phi = longNumber * 2 * Math.PI / longs;
+            var sinTheta = Math.sin(theta);
+            var sinPhi = Math.sin(phi);
+            var cosTheta = Math.cos(theta);
+            var cosPhi = Math.cos(phi);
+
+            var x = cosPhi * sinTheta;
+            var y = cosTheta;
+            var z = sinPhi * sinTheta;
+            var u = 1-(longNumber/longs);
+            var v = latNumber/lats;
+
+            normalData.push(x);
+            normalData.push(y);
+            normalData.push(z);
+            texCoordData.push(u);
+            texCoordData.push(v);
+            geometryData.push(radius * x);
+            geometryData.push(radius * y);
+            geometryData.push(radius * z);
+        }
+    }
+
+    for (var latNumber = 0; latNumber < lats; ++latNumber) {
+        for (var longNumber = 0; longNumber < longs; ++longNumber) {
+            var first = (latNumber * (longs+1)) + longNumber;
+            var second = first + longs + 1;
+            indexData.push(first);
+            indexData.push(second);
+            indexData.push(first+1);
+
+            indexData.push(second);
+            indexData.push(second+1);
+            indexData.push(first+1);
+        }
+    }
+
+    var retval = { };
+
+    retval.normalObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.normalObject);
+    ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(normalData), ctx.STATIC_DRAW);
+
+    retval.texCoordObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.texCoordObject);
+    ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(texCoordData), ctx.STATIC_DRAW);
+
+    retval.vertexObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ARRAY_BUFFER, retval.vertexObject);
+    ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array(geometryData), ctx.STATIC_DRAW);
+
+    retval.numIndices = indexData.length;
+    retval.indexObject = ctx.createBuffer();
+    ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, retval.indexObject);
+    ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), ctx.STREAM_DRAW);
+
+    return retval;
+}
+
+// Array of Objects curently loading
+var g_loadingObjects = [];
+
+// Clears all the Objects currently loading.
+// This is used to handle context lost events.
+function clearLoadingObjects() {
+    for (var ii = 0; ii < g_loadingObjects.length; ++ii) {
+        g_loadingObjects[ii].onreadystatechange = undefined;
+    }
+    g_loadingObjects = [];
+}
+
+//
+// loadObj
+//
+// Load a .obj file from the passed URL. Return an object with a 'loaded' property set to false.
+// When the object load is complete, the 'loaded' property becomes true and the following
+// properties are set:
+//
+//  normalObject        WebGLBuffer object for normals
+//  texCoordObject      WebGLBuffer object for texCoords
+//  vertexObject        WebGLBuffer object for vertices
+//  indexObject         WebGLBuffer object for indices
+//  numIndices          The number of indices in the indexObject
+//
+function loadObj(ctx, url)
+{
+    var obj = { loaded : false };
+    obj.ctx = ctx;
+    var req = new XMLHttpRequest();
+    req.obj = obj;
+    g_loadingObjects.push(req);
+    req.onreadystatechange = function () { processLoadObj(req) };
+    req.open("GET", url, true);
+    req.send(null);
+    return obj;
+}
+
+function processLoadObj(req)
+{
+    log("req="+req)
+    // only if req shows "complete"
+    if (req.readyState == 4) {
+        g_loadingObjects.splice(g_loadingObjects.indexOf(req), 1);
+        doLoadObj(req.obj, req.responseText);
+    }
+}
+
+function doLoadObj(obj, text)
+{
+    vertexArray = [ ];
+    normalArray = [ ];
+    textureArray = [ ];
+    indexArray = [ ];
+
+    var vertex = [ ];
+    var normal = [ ];
+    var texture = [ ];
+    var facemap = { };
+    var index = 0;
+
+    // This is a map which associates a range of indices with a name
+    // The name comes from the 'g' tag (of the form "g NAME"). Indices
+    // are part of one group until another 'g' tag is seen. If any indices
+    // come before a 'g' tag, it is given the group name "_unnamed"
+    // 'group' is an object whose property names are the group name and
+    // whose value is a 2 element array with [<first index>, <num indices>]
+    var groups = { };
+    var currentGroup = [-1, 0];
+    groups["_unnamed"] = currentGroup;
+
+    var lines = text.split("\n");
+    for (var lineIndex in lines) {
+        var line = lines[lineIndex].replace(/[ \t]+/g, " ").replace(/\s\s*$/, "");
+
+        // ignore comments
+        if (line[0] == "#")
+            continue;
+
+        var array = line.split(" ");
+        if (array[0] == "g") {
+            // new group
+            currentGroup = [indexArray.length, 0];
+            groups[array[1]] = currentGroup;
+        }
+        else if (array[0] == "v") {
+            // vertex
+            vertex.push(parseFloat(array[1]));
+            vertex.push(parseFloat(array[2]));
+            vertex.push(parseFloat(array[3]));
+        }
+        else if (array[0] == "vt") {
+            // normal
+            texture.push(parseFloat(array[1]));
+            texture.push(parseFloat(array[2]));
+        }
+        else if (array[0] == "vn") {
+            // normal
+            normal.push(parseFloat(array[1]));
+            normal.push(parseFloat(array[2]));
+            normal.push(parseFloat(array[3]));
+        }
+        else if (array[0] == "f") {
+            // face
+            if (array.length != 4) {
+                log("*** Error: face '"+line+"' not handled");
+                continue;
+            }
+
+            for (var i = 1; i < 4; ++i) {
+                if (!(array[i] in facemap)) {
+                    // add a new entry to the map and arrays
+                    var f = array[i].split("/");
+                    var vtx, nor, tex;
+
+                    if (f.length == 1) {
+                        vtx = parseInt(f[0]) - 1;
+                        nor = vtx;
+                        tex = vtx;
+                    }
+                    else if (f.length = 3) {
+                        vtx = parseInt(f[0]) - 1;
+                        tex = parseInt(f[1]) - 1;
+                        nor = parseInt(f[2]) - 1;
+                    }
+                    else {
+                        obj.ctx.console.log("*** Error: did not understand face '"+array[i]+"'");
+                        return null;
+                    }
+
+                    // do the vertices
+                    var x = 0;
+                    var y = 0;
+                    var z = 0;
+                    if (vtx * 3 + 2 < vertex.length) {
+                        x = vertex[vtx*3];
+                        y = vertex[vtx*3+1];
+                        z = vertex[vtx*3+2];
+                    }
+                    vertexArray.push(x);
+                    vertexArray.push(y);
+                    vertexArray.push(z);
+
+                    // do the textures
+                    x = 0;
+                    y = 0;
+                    if (tex * 2 + 1 < texture.length) {
+                        x = texture[tex*2];
+                        y = texture[tex*2+1];
+                    }
+                    textureArray.push(x);
+                    textureArray.push(y);
+
+                    // do the normals
+                    x = 0;
+                    y = 0;
+                    z = 1;
+                    if (nor * 3 + 2 < normal.length) {
+                        x = normal[nor*3];
+                        y = normal[nor*3+1];
+                        z = normal[nor*3+2];
+                    }
+                    normalArray.push(x);
+                    normalArray.push(y);
+                    normalArray.push(z);
+
+                    facemap[array[i]] = index++;
+                }
+
+                indexArray.push(facemap[array[i]]);
+                currentGroup[1]++;
+            }
+        }
+    }
+
+    // set the VBOs
+    obj.normalObject = obj.ctx.createBuffer();
+    obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.normalObject);
+    obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(normalArray), obj.ctx.STATIC_DRAW);
+
+    obj.texCoordObject = obj.ctx.createBuffer();
+    obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.texCoordObject);
+    obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(textureArray), obj.ctx.STATIC_DRAW);
+
+    obj.vertexObject = obj.ctx.createBuffer();
+    obj.ctx.bindBuffer(obj.ctx.ARRAY_BUFFER, obj.vertexObject);
+    obj.ctx.bufferData(obj.ctx.ARRAY_BUFFER, new Float32Array(vertexArray), obj.ctx.STATIC_DRAW);
+
+    obj.numIndices = indexArray.length;
+    obj.indexObject = obj.ctx.createBuffer();
+    obj.ctx.bindBuffer(obj.ctx.ELEMENT_ARRAY_BUFFER, obj.indexObject);
+    obj.ctx.bufferData(obj.ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexArray), obj.ctx.STREAM_DRAW);
+
+    obj.groups = groups;
+
+    obj.loaded = true;
+}
+
+// Array of images curently loading
+var g_loadingImages = [];
+
+// Clears all the images currently loading.
+// This is used to handle context lost events.
+function clearLoadingImages() {
+    for (var ii = 0; ii < g_loadingImages.length; ++ii) {
+        g_loadingImages[ii].onload = undefined;
+    }
+    g_loadingImages = [];
+}
+
+//
+// loadImageTexture
+//
+// Load the image at the passed url, place it in a new WebGLTexture object and return the WebGLTexture.
+//
+function loadImageTexture(ctx, url)
+{
+    var texture = ctx.createTexture();
+    ctx.bindTexture(ctx.TEXTURE_2D, texture);
+    ctx.texImage2D(ctx.TEXTURE_2D, 0, ctx.RGBA, 1, 1, 0, ctx.RGBA, ctx.UNSIGNED_BYTE, null);
+    var image = new Image();
+    g_loadingImages.push(image);
+    image.onload = function() { doLoadImageTexture(ctx, image, texture) }
+    image.src = url;
+    return texture;
+}
+
+function doLoadImageTexture(ctx, image, texture)
+{
+    g_loadingImages.splice(g_loadingImages.indexOf(image), 1);
+    ctx.bindTexture(ctx.TEXTURE_2D, texture);
+    ctx.texImage2D(
+        ctx.TEXTURE_2D, 0, ctx.RGBA, ctx.RGBA, ctx.UNSIGNED_BYTE, image);
+    ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MAG_FILTER, ctx.LINEAR);
+    ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_MIN_FILTER, ctx.LINEAR);
+    ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_S, ctx.CLAMP_TO_EDGE);
+    ctx.texParameteri(ctx.TEXTURE_2D, ctx.TEXTURE_WRAP_T, ctx.CLAMP_TO_EDGE);
+    //ctx.generateMipmap(ctx.TEXTURE_2D)
+    ctx.bindTexture(ctx.TEXTURE_2D, null);
+}
+
+//
+// Framerate object
+//
+// This object keeps track of framerate and displays it as the innerHTML text of the
+// HTML element with the passed id. Once created you call snapshot at the end
+// of every rendering cycle. Every 500ms the framerate is updated in the HTML element.
+//
+Framerate = function(id)
+{
+    this.numFramerates = 10;
+    this.framerateUpdateInterval = 500;
+    this.id = id;
+
+    this.renderTime = -1;
+    this.framerates = [ ];
+    self = this;
+    var fr = function() { self.updateFramerate() }
+    setInterval(fr, this.framerateUpdateInterval);
+}
+
+Framerate.prototype.updateFramerate = function()
+{
+    var tot = 0;
+    for (var i = 0; i < this.framerates.length; ++i)
+        tot += this.framerates[i];
+
+    var framerate = tot / this.framerates.length;
+    framerate = Math.round(framerate);
+    document.getElementById(this.id).innerHTML = "Framerate:"+framerate+"fps";
+}
+
+Framerate.prototype.snapshot = function()
+{
+    if (this.renderTime < 0)
+        this.renderTime = new Date().getTime();
+    else {
+        var newTime = new Date().getTime();
+        var t = newTime - this.renderTime;
+        if (t == 0)
+            return;
+        var framerate = 1000/t;
+        this.framerates.push(framerate);
+        while (this.framerates.length > this.numFramerates)
+            this.framerates.shift();
+        this.renderTime = newTime;
+    }
+}
diff --git a/client/deps/webgl_mpd/src/resources/J3DIMath.js b/client/deps/webgl_mpd/src/resources/J3DIMath.js
new file mode 100644
index 0000000..b3e5b64
--- /dev/null
+++ b/client/deps/webgl_mpd/src/resources/J3DIMath.js
@@ -0,0 +1,1065 @@
+/*
+ * Copyright (C) 2009 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+ // J3DI (Jedi) - A support library for WebGL.
+
+/*
+    J3DI Math Classes. Currently includes:
+
+        J3DIMatrix4 - A 4x4 Matrix
+*/
+
+/*
+    J3DIMatrix4 class
+
+    This class implements a 4x4 matrix. It has functions which duplicate the
+    functionality of the OpenGL matrix stack and glut functions. On browsers
+    that support it, CSSMatrix is used to accelerate operations.
+
+    IDL:
+
+    [
+        Constructor(in J3DIMatrix4 matrix),                 // copy passed matrix into new J3DIMatrix4
+        Constructor(in sequence<float> array)               // create new J3DIMatrix4 with 16 floats (row major)
+        Constructor()                                       // create new J3DIMatrix4 with identity matrix
+    ]
+    interface J3DIMatrix4 {
+        void load(in J3DIMatrix4 matrix);                   // copy the values from the passed matrix
+        void load(in sequence<float> array);                // copy 16 floats into the matrix
+        sequence<float> getAsArray();                       // return the matrix as an array of 16 floats
+        Float32Array getAsFloat32Array();             // return the matrix as a Float32Array with 16 values
+        void setUniform(in WebGLRenderingContext ctx,       // Send the matrix to the passed uniform location in the passed context
+                        in WebGLUniformLocation loc,
+                        in boolean transpose);
+        void makeIdentity();                                // replace the matrix with identity
+        void transpose();                                   // replace the matrix with its transpose
+        void invert();                                      // replace the matrix with its inverse
+
+        void translate(in float x, in float y, in float z); // multiply the matrix by passed translation values on the right
+        void translate(in J3DVector3 v);                    // multiply the matrix by passed translation values on the right
+        void scale(in float x, in float y, in float z);     // multiply the matrix by passed scale values on the right
+        void scale(in J3DVector3 v);                        // multiply the matrix by passed scale values on the right
+        void rotate(in float angle,                         // multiply the matrix by passed rotation values on the right
+                    in float x, in float y, in float z);    // (angle is in degrees)
+        void rotate(in float angle, in J3DVector3 v);       // multiply the matrix by passed rotation values on the right
+                                                            // (angle is in degrees)
+        void multiply(in CanvasMatrix matrix);              // multiply the matrix by the passed matrix on the right
+        void divide(in float divisor);                      // divide the matrix by the passed divisor
+        void ortho(in float left, in float right,           // multiply the matrix by the passed ortho values on the right
+                   in float bottom, in float top,
+                   in float near, in float far);
+        void frustum(in float left, in float right,         // multiply the matrix by the passed frustum values on the right
+                     in float bottom, in float top,
+                     in float near, in float far);
+        void perspective(in float fovy, in float aspect,    // multiply the matrix by the passed perspective values on the right
+                         in float zNear, in float zFar);
+        void lookat(in J3DVector3 eye,                      // multiply the matrix by the passed lookat
+                in J3DVector3 center,  in J3DVector3 up);   // values on the right
+         bool decompose(in J3DVector3 translate,            // decompose the matrix into the passed vector
+                        in J3DVector3 rotate,
+                        in J3DVector3 scale,
+                        in J3DVector3 skew,
+                        in sequence<float> perspective);
+    }
+
+    [
+        Constructor(in J3DVector3 vector),                  // copy passed vector into new J3DVector3
+        Constructor(in sequence<float> array)               // create new J3DVector3 with 3 floats from array
+        Constructor(in float x, in float y, in float z)     // create new J3DVector3 with 3 floats
+        Constructor()                                       // create new J3DVector3 with (0,0,0)
+    ]
+    interface J3DVector3 {
+        void load(in J3DVector3 vector);                    // copy the values from the passed vector
+        void load(in sequence<float> array);                // copy 3 floats into the vector from array
+        void load(in float x, in float y, in float z);      // copy 3 floats into the vector
+        sequence<float> getAsArray();                       // return the vector as an array of 3 floats
+        Float32Array getAsFloat32Array();             // return the matrix as a Float32Array with 16 values
+        void multMatrix(in J3DIMatrix4 matrix);             // multiply the vector by the passed matrix (on the right)
+        float vectorLength();                               // return the length of the vector
+        float dot();                                        // return the dot product of the vector
+        void cross(in J3DVector3 v);                        // replace the vector with vector x v
+        void divide(in float divisor);                      // divide the vector by the passed divisor
+    }
+*/
+
+J3DIHasCSSMatrix = false;
+J3DIHasCSSMatrixCopy = false;
+/*
+if ("WebKitCSSMatrix" in window && ("media" in window && window.media.matchMedium("(-webkit-transform-3d)")) ||
+                                   ("styleMedia" in window && window.styleMedia.matchMedium("(-webkit-transform-3d)"))) {
+    J3DIHasCSSMatrix = true;
+    if ("copy" in WebKitCSSMatrix.prototype)
+        J3DIHasCSSMatrixCopy = true;
+}
+*/
+
+//  console.log("J3DIHasCSSMatrix="+J3DIHasCSSMatrix);
+//  console.log("J3DIHasCSSMatrixCopy="+J3DIHasCSSMatrixCopy);
+
+//
+// J3DIMatrix4
+//
+J3DIMatrix4 = function(m)
+{
+    if (J3DIHasCSSMatrix)
+        this.$matrix = new WebKitCSSMatrix;
+    else
+        this.$matrix = new Object;
+
+    if (typeof m == 'object') {
+        if ("length" in m && m.length >= 16) {
+            this.load(m);
+            return;
+        }
+        else if (m instanceof J3DIMatrix4) {
+            this.load(m);
+            return;
+        }
+    }
+    this.makeIdentity();
+}
+
+J3DIMatrix4.prototype.load = function()
+{
+    if (arguments.length == 1 && typeof arguments[0] == 'object') {
+        var matrix;
+
+        if (arguments[0] instanceof J3DIMatrix4) {
+            matrix = arguments[0].$matrix;
+
+            this.$matrix.m11 = matrix.m11;
+            this.$matrix.m12 = matrix.m12;
+            this.$matrix.m13 = matrix.m13;
+            this.$matrix.m14 = matrix.m14;
+
+            this.$matrix.m21 = matrix.m21;
+            this.$matrix.m22 = matrix.m22;
+            this.$matrix.m23 = matrix.m23;
+            this.$matrix.m24 = matrix.m24;
+
+            this.$matrix.m31 = matrix.m31;
+            this.$matrix.m32 = matrix.m32;
+            this.$matrix.m33 = matrix.m33;
+            this.$matrix.m34 = matrix.m34;
+
+            this.$matrix.m41 = matrix.m41;
+            this.$matrix.m42 = matrix.m42;
+            this.$matrix.m43 = matrix.m43;
+            this.$matrix.m44 = matrix.m44;
+            return;
+        }
+        else
+            matrix = arguments[0];
+
+        if ("length" in matrix && matrix.length >= 16) {
+            this.$matrix.m11 = matrix[0];
+            this.$matrix.m12 = matrix[1];
+            this.$matrix.m13 = matrix[2];
+            this.$matrix.m14 = matrix[3];
+
+            this.$matrix.m21 = matrix[4];
+            this.$matrix.m22 = matrix[5];
+            this.$matrix.m23 = matrix[6];
+            this.$matrix.m24 = matrix[7];
+
+            this.$matrix.m31 = matrix[8];
+            this.$matrix.m32 = matrix[9];
+            this.$matrix.m33 = matrix[10];
+            this.$matrix.m34 = matrix[11];
+
+            this.$matrix.m41 = matrix[12];
+            this.$matrix.m42 = matrix[13];
+            this.$matrix.m43 = matrix[14];
+            this.$matrix.m44 = matrix[15];
+            return;
+        }
+    }
+
+    this.makeIdentity();
+}
+
+J3DIMatrix4.prototype.getAsArray = function()
+{
+    return [
+        this.$matrix.m11, this.$matrix.m12, this.$matrix.m13, this.$matrix.m14,
+        this.$matrix.m21, this.$matrix.m22, this.$matrix.m23, this.$matrix.m24,
+        this.$matrix.m31, this.$matrix.m32, this.$matrix.m33, this.$matrix.m34,
+        this.$matrix.m41, this.$matrix.m42, this.$matrix.m43, this.$matrix.m44
+    ];
+}
+
+J3DIMatrix4.prototype.getAsFloat32Array = function()
+{
+    if (J3DIHasCSSMatrixCopy) {
+        var array = new Float32Array(16);
+        this.$matrix.copy(array);
+        return array;
+    }
+    return new Float32Array(this.getAsArray());
+}
+
+J3DIMatrix4.prototype.setUniform = function(ctx, loc, transpose)
+{
+    if (J3DIMatrix4.setUniformArray == undefined) {
+        J3DIMatrix4.setUniformWebGLArray = new Float32Array(16);
+        J3DIMatrix4.setUniformArray = new Array(16);
+    }
+
+    if (J3DIHasCSSMatrixCopy)
+        this.$matrix.copy(J3DIMatrix4.setUniformWebGLArray);
+    else {
+        J3DIMatrix4.setUniformArray[0] = this.$matrix.m11;
+        J3DIMatrix4.setUniformArray[1] = this.$matrix.m12;
+        J3DIMatrix4.setUniformArray[2] = this.$matrix.m13;
+        J3DIMatrix4.setUniformArray[3] = this.$matrix.m14;
+        J3DIMatrix4.setUniformArray[4] = this.$matrix.m21;
+        J3DIMatrix4.setUniformArray[5] = this.$matrix.m22;
+        J3DIMatrix4.setUniformArray[6] = this.$matrix.m23;
+        J3DIMatrix4.setUniformArray[7] = this.$matrix.m24;
+        J3DIMatrix4.setUniformArray[8] = this.$matrix.m31;
+        J3DIMatrix4.setUniformArray[9] = this.$matrix.m32;
+        J3DIMatrix4.setUniformArray[10] = this.$matrix.m33;
+        J3DIMatrix4.setUniformArray[11] = this.$matrix.m34;
+        J3DIMatrix4.setUniformArray[12] = this.$matrix.m41;
+        J3DIMatrix4.setUniformArray[13] = this.$matrix.m42;
+        J3DIMatrix4.setUniformArray[14] = this.$matrix.m43;
+        J3DIMatrix4.setUniformArray[15] = this.$matrix.m44;
+
+        J3DIMatrix4.setUniformWebGLArray.set(J3DIMatrix4.setUniformArray);
+    }
+
+    ctx.uniformMatrix4fv(loc, transpose, J3DIMatrix4.setUniformWebGLArray);
+}
+
+J3DIMatrix4.prototype.makeIdentity = function()
+{
+    this.$matrix.m11 = 1;
+    this.$matrix.m12 = 0;
+    this.$matrix.m13 = 0;
+    this.$matrix.m14 = 0;
+
+    this.$matrix.m21 = 0;
+    this.$matrix.m22 = 1;
+    this.$matrix.m23 = 0;
+    this.$matrix.m24 = 0;
+
+    this.$matrix.m31 = 0;
+    this.$matrix.m32 = 0;
+    this.$matrix.m33 = 1;
+    this.$matrix.m34 = 0;
+
+    this.$matrix.m41 = 0;
+    this.$matrix.m42 = 0;
+    this.$matrix.m43 = 0;
+    this.$matrix.m44 = 1;
+}
+
+J3DIMatrix4.prototype.transpose = function()
+{
+    var tmp = this.$matrix.m12;
+    this.$matrix.m12 = this.$matrix.m21;
+    this.$matrix.m21 = tmp;
+
+    tmp = this.$matrix.m13;
+    this.$matrix.m13 = this.$matrix.m31;
+    this.$matrix.m31 = tmp;
+
+    tmp = this.$matrix.m14;
+    this.$matrix.m14 = this.$matrix.m41;
+    this.$matrix.m41 = tmp;
+
+    tmp = this.$matrix.m23;
+    this.$matrix.m23 = this.$matrix.m32;
+    this.$matrix.m32 = tmp;
+
+    tmp = this.$matrix.m24;
+    this.$matrix.m24 = this.$matrix.m42;
+    this.$matrix.m42 = tmp;
+
+    tmp = this.$matrix.m34;
+    this.$matrix.m34 = this.$matrix.m43;
+    this.$matrix.m43 = tmp;
+}
+
+J3DIMatrix4.prototype.invert = function()
+{
+    if (J3DIHasCSSMatrix) {
+        this.$matrix = this.$matrix.inverse();
+        return;
+    }
+
+    // Calculate the 4x4 determinant
+    // If the determinant is zero,
+    // then the inverse matrix is not unique.
+    var det = this._determinant4x4();
+
+    if (Math.abs(det) < 1e-8)
+        return null;
+
+    this._makeAdjoint();
+
+    // Scale the adjoint matrix to get the inverse
+    this.$matrix.m11 /= det;
+    this.$matrix.m12 /= det;
+    this.$matrix.m13 /= det;
+    this.$matrix.m14 /= det;
+
+    this.$matrix.m21 /= det;
+    this.$matrix.m22 /= det;
+    this.$matrix.m23 /= det;
+    this.$matrix.m24 /= det;
+
+    this.$matrix.m31 /= det;
+    this.$matrix.m32 /= det;
+    this.$matrix.m33 /= det;
+    this.$matrix.m34 /= det;
+
+    this.$matrix.m41 /= det;
+    this.$matrix.m42 /= det;
+    this.$matrix.m43 /= det;
+    this.$matrix.m44 /= det;
+}
+
+J3DIMatrix4.prototype.translate = function(x,y,z)
+{
+    if (typeof x == 'object' && "length" in x) {
+        var t = x;
+        x = t[0];
+        y = t[1];
+        z = t[2];
+    }
+    else {
+        if (x == undefined)
+            x = 0;
+        if (y == undefined)
+            y = 0;
+        if (z == undefined)
+            z = 0;
+    }
+
+    if (J3DIHasCSSMatrix) {
+        this.$matrix = this.$matrix.translate(x, y, z);
+        return;
+    }
+
+    var matrix = new J3DIMatrix4();
+    matrix.$matrix.m41 = x;
+    matrix.$matrix.m42 = y;
+    matrix.$matrix.m43 = z;
+
+    this.multiply(matrix);
+}
+
+J3DIMatrix4.prototype.scale = function(x,y,z)
+{
+    if (typeof x == 'object' && "length" in x) {
+        var t = x;
+        x = t[0];
+        y = t[1];
+        z = t[2];
+    }
+    else {
+        if (x == undefined)
+            x = 1;
+        if (z == undefined) {
+            if (y == undefined) {
+                y = x;
+                z = x;
+            }
+            else
+                z = 1;
+        }
+        else if (y == undefined)
+            y = x;
+    }
+
+    if (J3DIHasCSSMatrix) {
+        this.$matrix = this.$matrix.scale(x, y, z);
+        return;
+    }
+
+    var matrix = new J3DIMatrix4();
+    matrix.$matrix.m11 = x;
+    matrix.$matrix.m22 = y;
+    matrix.$matrix.m33 = z;
+
+    this.multiply(matrix);
+}
+
+J3DIMatrix4.prototype.rotate = function(angle,x,y,z)
+{
+    // Forms are (angle, x,y,z), (angle,vector), (angleX, angleY, angleZ), (angle)
+    if (typeof x == 'object' && "length" in x) {
+        var t = x;
+        x = t[0];
+        y = t[1];
+        z = t[2];
+    }
+    else {
+        if (arguments.length == 1) {
+            x = 0;
+            y = 0;
+            z = 1;
+        }
+        else if (arguments.length == 3) {
+            this.rotate(angle, 1,0,0); // about X axis
+            this.rotate(x, 0,1,0); // about Y axis
+            this.rotate(y, 0,0,1); // about Z axis
+            return;
+        }
+    }
+
+    if (J3DIHasCSSMatrix) {
+        this.$matrix = this.$matrix.rotateAxisAngle(x, y, z, angle);
+        return;
+    }
+
+    // angles are in degrees. Switch to radians
+    angle = angle / 180 * Math.PI;
+
+    angle /= 2;
+    var sinA = Math.sin(angle);
+    var cosA = Math.cos(angle);
+    var sinA2 = sinA * sinA;
+
+    // normalize
+    var len = Math.sqrt(x * x + y * y + z * z);
+    if (len == 0) {
+        // bad vector, just use something reasonable
+        x = 0;
+        y = 0;
+        z = 1;
+    } else if (len != 1) {
+        x /= len;
+        y /= len;
+        z /= len;
+    }
+
+    var mat = new J3DIMatrix4();
+
+    // optimize case where axis is along major axis
+    if (x == 1 && y == 0 && z == 0) {
+        mat.$matrix.m11 = 1;
+        mat.$matrix.m12 = 0;
+        mat.$matrix.m13 = 0;
+        mat.$matrix.m21 = 0;
+        mat.$matrix.m22 = 1 - 2 * sinA2;
+        mat.$matrix.m23 = 2 * sinA * cosA;
+        mat.$matrix.m31 = 0;
+        mat.$matrix.m32 = -2 * sinA * cosA;
+        mat.$matrix.m33 = 1 - 2 * sinA2;
+        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
+        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
+        mat.$matrix.m44 = 1;
+    } else if (x == 0 && y == 1 && z == 0) {
+        mat.$matrix.m11 = 1 - 2 * sinA2;
+        mat.$matrix.m12 = 0;
+        mat.$matrix.m13 = -2 * sinA * cosA;
+        mat.$matrix.m21 = 0;
+        mat.$matrix.m22 = 1;
+        mat.$matrix.m23 = 0;
+        mat.$matrix.m31 = 2 * sinA * cosA;
+        mat.$matrix.m32 = 0;
+        mat.$matrix.m33 = 1 - 2 * sinA2;
+        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
+        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
+        mat.$matrix.m44 = 1;
+    } else if (x == 0 && y == 0 && z == 1) {
+        mat.$matrix.m11 = 1 - 2 * sinA2;
+        mat.$matrix.m12 = 2 * sinA * cosA;
+        mat.$matrix.m13 = 0;
+        mat.$matrix.m21 = -2 * sinA * cosA;
+        mat.$matrix.m22 = 1 - 2 * sinA2;
+        mat.$matrix.m23 = 0;
+        mat.$matrix.m31 = 0;
+        mat.$matrix.m32 = 0;
+        mat.$matrix.m33 = 1;
+        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
+        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
+        mat.$matrix.m44 = 1;
+    } else {
+        var x2 = x*x;
+        var y2 = y*y;
+        var z2 = z*z;
+
+        mat.$matrix.m11 = 1 - 2 * (y2 + z2) * sinA2;
+        mat.$matrix.m12 = 2 * (x * y * sinA2 + z * sinA * cosA);
+        mat.$matrix.m13 = 2 * (x * z * sinA2 - y * sinA * cosA);
+        mat.$matrix.m21 = 2 * (y * x * sinA2 - z * sinA * cosA);
+        mat.$matrix.m22 = 1 - 2 * (z2 + x2) * sinA2;
+        mat.$matrix.m23 = 2 * (y * z * sinA2 + x * sinA * cosA);
+        mat.$matrix.m31 = 2 * (z * x * sinA2 + y * sinA * cosA);
+        mat.$matrix.m32 = 2 * (z * y * sinA2 - x * sinA * cosA);
+        mat.$matrix.m33 = 1 - 2 * (x2 + y2) * sinA2;
+        mat.$matrix.m14 = mat.$matrix.m24 = mat.$matrix.m34 = 0;
+        mat.$matrix.m41 = mat.$matrix.m42 = mat.$matrix.m43 = 0;
+        mat.$matrix.m44 = 1;
+    }
+    this.multiply(mat);
+}
+
+J3DIMatrix4.prototype.multiply = function(mat)
+{
+    if (J3DIHasCSSMatrix) {
+        this.$matrix = this.$matrix.multiply(mat.$matrix);
+        return;
+    }
+
+    var m11 = (mat.$matrix.m11 * this.$matrix.m11 + mat.$matrix.m12 * this.$matrix.m21
+               + mat.$matrix.m13 * this.$matrix.m31 + mat.$matrix.m14 * this.$matrix.m41);
+    var m12 = (mat.$matrix.m11 * this.$matrix.m12 + mat.$matrix.m12 * this.$matrix.m22
+               + mat.$matrix.m13 * this.$matrix.m32 + mat.$matrix.m14 * this.$matrix.m42);
+    var m13 = (mat.$matrix.m11 * this.$matrix.m13 + mat.$matrix.m12 * this.$matrix.m23
+               + mat.$matrix.m13 * this.$matrix.m33 + mat.$matrix.m14 * this.$matrix.m43);
+    var m14 = (mat.$matrix.m11 * this.$matrix.m14 + mat.$matrix.m12 * this.$matrix.m24
+               + mat.$matrix.m13 * this.$matrix.m34 + mat.$matrix.m14 * this.$matrix.m44);
+
+    var m21 = (mat.$matrix.m21 * this.$matrix.m11 + mat.$matrix.m22 * this.$matrix.m21
+               + mat.$matrix.m23 * this.$matrix.m31 + mat.$matrix.m24 * this.$matrix.m41);
+    var m22 = (mat.$matrix.m21 * this.$matrix.m12 + mat.$matrix.m22 * this.$matrix.m22
+               + mat.$matrix.m23 * this.$matrix.m32 + mat.$matrix.m24 * this.$matrix.m42);
+    var m23 = (mat.$matrix.m21 * this.$matrix.m13 + mat.$matrix.m22 * this.$matrix.m23
+               + mat.$matrix.m23 * this.$matrix.m33 + mat.$matrix.m24 * this.$matrix.m43);
+    var m24 = (mat.$matrix.m21 * this.$matrix.m14 + mat.$matrix.m22 * this.$matrix.m24
+               + mat.$matrix.m23 * this.$matrix.m34 + mat.$matrix.m24 * this.$matrix.m44);
+
+    var m31 = (mat.$matrix.m31 * this.$matrix.m11 + mat.$matrix.m32 * this.$matrix.m21
+               + mat.$matrix.m33 * this.$matrix.m31 + mat.$matrix.m34 * this.$matrix.m41);
+    var m32 = (mat.$matrix.m31 * this.$matrix.m12 + mat.$matrix.m32 * this.$matrix.m22
+               + mat.$matrix.m33 * this.$matrix.m32 + mat.$matrix.m34 * this.$matrix.m42);
+    var m33 = (mat.$matrix.m31 * this.$matrix.m13 + mat.$matrix.m32 * this.$matrix.m23
+               + mat.$matrix.m33 * this.$matrix.m33 + mat.$matrix.m34 * this.$matrix.m43);
+    var m34 = (mat.$matrix.m31 * this.$matrix.m14 + mat.$matrix.m32 * this.$matrix.m24
+               + mat.$matrix.m33 * this.$matrix.m34 + mat.$matrix.m34 * this.$matrix.m44);
+
+    var m41 = (mat.$matrix.m41 * this.$matrix.m11 + mat.$matrix.m42 * this.$matrix.m21
+               + mat.$matrix.m43 * this.$matrix.m31 + mat.$matrix.m44 * this.$matrix.m41);
+    var m42 = (mat.$matrix.m41 * this.$matrix.m12 + mat.$matrix.m42 * this.$matrix.m22
+               + mat.$matrix.m43 * this.$matrix.m32 + mat.$matrix.m44 * this.$matrix.m42);
+    var m43 = (mat.$matrix.m41 * this.$matrix.m13 + mat.$matrix.m42 * this.$matrix.m23
+               + mat.$matrix.m43 * this.$matrix.m33 + mat.$matrix.m44 * this.$matrix.m43);
+    var m44 = (mat.$matrix.m41 * this.$matrix.m14 + mat.$matrix.m42 * this.$matrix.m24
+               + mat.$matrix.m43 * this.$matrix.m34 + mat.$matrix.m44 * this.$matrix.m44);
+
+    this.$matrix.m11 = m11;
+    this.$matrix.m12 = m12;
+    this.$matrix.m13 = m13;
+    this.$matrix.m14 = m14;
+
+    this.$matrix.m21 = m21;
+    this.$matrix.m22 = m22;
+    this.$matrix.m23 = m23;
+    this.$matrix.m24 = m24;
+
+    this.$matrix.m31 = m31;
+    this.$matrix.m32 = m32;
+    this.$matrix.m33 = m33;
+    this.$matrix.m34 = m34;
+
+    this.$matrix.m41 = m41;
+    this.$matrix.m42 = m42;
+    this.$matrix.m43 = m43;
+    this.$matrix.m44 = m44;
+}
+
+J3DIMatrix4.prototype.divide = function(divisor)
+{
+    this.$matrix.m11 /= divisor;
+    this.$matrix.m12 /= divisor;
+    this.$matrix.m13 /= divisor;
+    this.$matrix.m14 /= divisor;
+
+    this.$matrix.m21 /= divisor;
+    this.$matrix.m22 /= divisor;
+    this.$matrix.m23 /= divisor;
+    this.$matrix.m24 /= divisor;
+
+    this.$matrix.m31 /= divisor;
+    this.$matrix.m32 /= divisor;
+    this.$matrix.m33 /= divisor;
+    this.$matrix.m34 /= divisor;
+
+    this.$matrix.m41 /= divisor;
+    this.$matrix.m42 /= divisor;
+    this.$matrix.m43 /= divisor;
+    this.$matrix.m44 /= divisor;
+
+}
+
+J3DIMatrix4.prototype.ortho = function(left, right, bottom, top, near, far)
+{
+    var tx = (left + right) / (left - right);
+    var ty = (top + bottom) / (top - bottom);
+    var tz = (far + near) / (far - near);
+
+    var matrix = new J3DIMatrix4();
+    matrix.$matrix.m11 = 2 / (left - right);
+    matrix.$matrix.m12 = 0;
+    matrix.$matrix.m13 = 0;
+    matrix.$matrix.m14 = 0;
+    matrix.$matrix.m21 = 0;
+    matrix.$matrix.m22 = 2 / (top - bottom);
+    matrix.$matrix.m23 = 0;
+    matrix.$matrix.m24 = 0;
+    matrix.$matrix.m31 = 0;
+    matrix.$matrix.m32 = 0;
+    matrix.$matrix.m33 = -2 / (far - near);
+    matrix.$matrix.m34 = 0;
+    matrix.$matrix.m41 = tx;
+    matrix.$matrix.m42 = ty;
+    matrix.$matrix.m43 = tz;
+    matrix.$matrix.m44 = 1;
+
+    this.multiply(matrix);
+}
+
+J3DIMatrix4.prototype.frustum = function(left, right, bottom, top, near, far)
+{
+    var matrix = new J3DIMatrix4();
+    var A = (right + left) / (right - left);
+    var B = (top + bottom) / (top - bottom);
+    var C = -(far + near) / (far - near);
+    var D = -(2 * far * near) / (far - near);
+
+    matrix.$matrix.m11 = (2 * near) / (right - left);
+    matrix.$matrix.m12 = 0;
+    matrix.$matrix.m13 = 0;
+    matrix.$matrix.m14 = 0;
+
+    matrix.$matrix.m21 = 0;
+    matrix.$matrix.m22 = 2 * near / (top - bottom);
+    matrix.$matrix.m23 = 0;
+    matrix.$matrix.m24 = 0;
+
+    matrix.$matrix.m31 = A;
+    matrix.$matrix.m32 = B;
+    matrix.$matrix.m33 = C;
+    matrix.$matrix.m34 = -1;
+
+    matrix.$matrix.m41 = 0;
+    matrix.$matrix.m42 = 0;
+    matrix.$matrix.m43 = D;
+    matrix.$matrix.m44 = 0;
+
+    this.multiply(matrix);
+}
+
+J3DIMatrix4.prototype.perspective = function(fovy, aspect, zNear, zFar)
+{
+    var top = Math.tan(fovy * Math.PI / 360) * zNear;
+    var bottom = -top;
+    var left = aspect * bottom;
+    var right = aspect * top;
+    this.frustum(left, right, bottom, top, zNear, zFar);
+}
+
+J3DIMatrix4.prototype.lookat = function(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz)
+{
+    if (typeof eyez == 'object' && "length" in eyez) {
+        var t = eyez;
+        upx = t[0];
+        upy = t[1];
+        upz = t[2];
+
+        t = eyey;
+        centerx = t[0];
+        centery = t[1];
+        centerz = t[2];
+
+        t = eyex;
+        eyex = t[0];
+        eyey = t[1];
+        eyez = t[2];
+    }
+
+    var matrix = new J3DIMatrix4();
+
+    // Make rotation matrix
+
+    // Z vector
+    var zx = eyex - centerx;
+    var zy = eyey - centery;
+    var zz = eyez - centerz;
+    var mag = Math.sqrt(zx * zx + zy * zy + zz * zz);
+    if (mag) {
+        zx /= mag;
+        zy /= mag;
+        zz /= mag;
+    }
+
+    // Y vector
+    var yx = upx;
+    var yy = upy;
+    var yz = upz;
+
+    // X vector = Y cross Z
+    xx =  yy * zz - yz * zy;
+    xy = -yx * zz + yz * zx;
+    xz =  yx * zy - yy * zx;
+
+    // Recompute Y = Z cross X
+    yx = zy * xz - zz * xy;
+    yy = -zx * xz + zz * xx;
+    yx = zx * xy - zy * xx;
+
+    // cross product gives area of parallelogram, which is < 1.0 for
+    // non-perpendicular unit-length vectors; so normalize x, y here
+
+    mag = Math.sqrt(xx * xx + xy * xy + xz * xz);
+    if (mag) {
+        xx /= mag;
+        xy /= mag;
+        xz /= mag;
+    }
+
+    mag = Math.sqrt(yx * yx + yy * yy + yz * yz);
+    if (mag) {
+        yx /= mag;
+        yy /= mag;
+        yz /= mag;
+    }
+
+    matrix.$matrix.m11 = xx;
+    matrix.$matrix.m12 = xy;
+    matrix.$matrix.m13 = xz;
+    matrix.$matrix.m14 = 0;
+
+    matrix.$matrix.m21 = yx;
+    matrix.$matrix.m22 = yy;
+    matrix.$matrix.m23 = yz;
+    matrix.$matrix.m24 = 0;
+
+    matrix.$matrix.m31 = zx;
+    matrix.$matrix.m32 = zy;
+    matrix.$matrix.m33 = zz;
+    matrix.$matrix.m34 = 0;
+
+    matrix.$matrix.m41 = 0;
+    matrix.$matrix.m42 = 0;
+    matrix.$matrix.m43 = 0;
+    matrix.$matrix.m44 = 1;
+    matrix.translate(-eyex, -eyey, -eyez);
+
+    this.multiply(matrix);
+}
+
+// Returns true on success, false otherwise. All params are Array objects
+J3DIMatrix4.prototype.decompose = function(_translate, _rotate, _scale, _skew, _perspective)
+{
+    // Normalize the matrix.
+    if (this.$matrix.m44 == 0)
+        return false;
+
+    // Gather the params
+    var translate, rotate, scale, skew, perspective;
+
+    var translate = (_translate == undefined || !("length" in _translate)) ? new J3DIVector3 : _translate;
+    var rotate = (_rotate == undefined || !("length" in _rotate)) ? new J3DIVector3 : _rotate;
+    var scale = (_scale == undefined || !("length" in _scale)) ? new J3DIVector3 : _scale;
+    var skew = (_skew == undefined || !("length" in _skew)) ? new J3DIVector3 : _skew;
+    var perspective = (_perspective == undefined || !("length" in _perspective)) ? new Array(4) : _perspective;
+
+    var matrix = new J3DIMatrix4(this);
+
+    matrix.divide(matrix.$matrix.m44);
+
+    // perspectiveMatrix is used to solve for perspective, but it also provides
+    // an easy way to test for singularity of the upper 3x3 component.
+    var perspectiveMatrix = new J3DIMatrix4(matrix);
+
+    perspectiveMatrix.$matrix.m14 = 0;
+    perspectiveMatrix.$matrix.m24 = 0;
+    perspectiveMatrix.$matrix.m34 = 0;
+    perspectiveMatrix.$matrix.m44 = 1;
+
+    if (perspectiveMatrix._determinant4x4() == 0)
+        return false;
+
+    // First, isolate perspective.
+    if (matrix.$matrix.m14 != 0 || matrix.$matrix.m24 != 0 || matrix.$matrix.m34 != 0) {
+        // rightHandSide is the right hand side of the equation.
+        var rightHandSide = [ matrix.$matrix.m14, matrix.$matrix.m24, matrix.$matrix.m34, matrix.$matrix.m44 ];
+
+        // Solve the equation by inverting perspectiveMatrix and multiplying
+        // rightHandSide by the inverse.
+        var inversePerspectiveMatrix = new J3DIMatrix4(perspectiveMatrix);
+        inversePerspectiveMatrix.invert();
+        var transposedInversePerspectiveMatrix = new J3DIMatrix4(inversePerspectiveMatrix);
+        transposedInversePerspectiveMatrix.transpose();
+        transposedInversePerspectiveMatrix.multVecMatrix(perspective, rightHandSide);
+
+        // Clear the perspective partition
+        matrix.$matrix.m14 = matrix.$matrix.m24 = matrix.$matrix.m34 = 0
+        matrix.$matrix.m44 = 1;
+    }
+    else {
+        // No perspective.
+        perspective[0] = perspective[1] = perspective[2] = 0;
+        perspective[3] = 1;
+    }
+
+    // Next take care of translation
+    translate[0] = matrix.$matrix.m41
+    matrix.$matrix.m41 = 0
+    translate[1] = matrix.$matrix.m42
+    matrix.$matrix.m42 = 0
+    translate[2] = matrix.$matrix.m43
+    matrix.$matrix.m43 = 0
+
+    // Now get scale and shear. 'row' is a 3 element array of 3 component vectors
+    var row0 = new J3DIVector3(matrix.$matrix.m11, matrix.$matrix.m12, matrix.$matrix.m13);
+    var row1 = new J3DIVector3(matrix.$matrix.m21, matrix.$matrix.m22, matrix.$matrix.m23);
+    var row2 = new J3DIVector3(matrix.$matrix.m31, matrix.$matrix.m32, matrix.$matrix.m33);
+
+    // Compute X scale factor and normalize first row.
+    scale[0] = row0.vectorLength();
+    row0.divide(scale[0]);
+
+    // Compute XY shear factor and make 2nd row orthogonal to 1st.
+    skew[0] = row0.dot(row1);
+    row1.combine(row0, 1.0, -skew[0]);
+
+    // Now, compute Y scale and normalize 2nd row.
+    scale[1] = row1.vectorLength();
+    row1.divide(scale[1]);
+    skew[0] /= scale[1];
+
+    // Compute XZ and YZ shears, orthogonalize 3rd row
+    skew[1] = row1.dot(row2);
+    row2.combine(row0, 1.0, -skew[1]);
+    skew[2] = row1.dot(row2);
+    row2.combine(row1, 1.0, -skew[2]);
+
+    // Next, get Z scale and normalize 3rd row.
+    scale[2] = row2.vectorLength();
+    row2.divide(scale[2]);
+    skew[1] /= scale[2];
+    skew[2] /= scale[2];
+
+    // At this point, the matrix (in rows) is orthonormal.
+    // Check for a coordinate system flip.  If the determinant
+    // is -1, then negate the matrix and the scaling factors.
+    var pdum3 = new J3DIVector3(row1);
+    pdum3.cross(row2);
+    if (row0.dot(pdum3) < 0) {
+        for (i = 0; i < 3; i++) {
+            scale[i] *= -1;
+            row[0][i] *= -1;
+            row[1][i] *= -1;
+            row[2][i] *= -1;
+        }
+    }
+
+    // Now, get the rotations out
+    rotate[1] = Math.asin(-row0[2]);
+    if (Math.cos(rotate[1]) != 0) {
+        rotate[0] = Math.atan2(row1[2], row2[2]);
+        rotate[2] = Math.atan2(row0[1], row0[0]);
+    }
+    else {
+        rotate[0] = Math.atan2(-row2[0], row1[1]);
+        rotate[2] = 0;
+    }
+
+    // Convert rotations to degrees
+    var rad2deg = 180 / Math.PI;
+    rotate[0] *= rad2deg;
+    rotate[1] *= rad2deg;
+    rotate[2] *= rad2deg;
+
+    return true;
+}
+
+J3DIMatrix4.prototype._determinant2x2 = function(a, b, c, d)
+{
+    return a * d - b * c;
+}
+
+J3DIMatrix4.prototype._determinant3x3 = function(a1, a2, a3, b1, b2, b3, c1, c2, c3)
+{
+    return a1 * this._determinant2x2(b2, b3, c2, c3)
+         - b1 * this._determinant2x2(a2, a3, c2, c3)
+         + c1 * this._determinant2x2(a2, a3, b2, b3);
+}
+
+J3DIMatrix4.prototype._determinant4x4 = function()
+{
+    var a1 = this.$matrix.m11;
+    var b1 = this.$matrix.m12;
+    var c1 = this.$matrix.m13;
+    var d1 = this.$matrix.m14;
+
+    var a2 = this.$matrix.m21;
+    var b2 = this.$matrix.m22;
+    var c2 = this.$matrix.m23;
+    var d2 = this.$matrix.m24;
+
+    var a3 = this.$matrix.m31;
+    var b3 = this.$matrix.m32;
+    var c3 = this.$matrix.m33;
+    var d3 = this.$matrix.m34;
+
+    var a4 = this.$matrix.m41;
+    var b4 = this.$matrix.m42;
+    var c4 = this.$matrix.m43;
+    var d4 = this.$matrix.m44;
+
+    return a1 * this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4)
+         - b1 * this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4)
+         + c1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4)
+         - d1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
+}
+
+J3DIMatrix4.prototype._makeAdjoint = function()
+{
+    var a1 = this.$matrix.m11;
+    var b1 = this.$matrix.m12;
+    var c1 = this.$matrix.m13;
+    var d1 = this.$matrix.m14;
+
+    var a2 = this.$matrix.m21;
+    var b2 = this.$matrix.m22;
+    var c2 = this.$matrix.m23;
+    var d2 = this.$matrix.m24;
+
+    var a3 = this.$matrix.m31;
+    var b3 = this.$matrix.m32;
+    var c3 = this.$matrix.m33;
+    var d3 = this.$matrix.m34;
+
+    var a4 = this.$matrix.m41;
+    var b4 = this.$matrix.m42;
+    var c4 = this.$matrix.m43;
+    var d4 = this.$matrix.m44;
+
+    // Row column labeling reversed since we transpose rows & columns
+    this.$matrix.m11  =   this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4);
+    this.$matrix.m21  = - this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4);
+    this.$matrix.m31  =   this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4);
+    this.$matrix.m41  = - this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
+
+    this.$matrix.m12  = - this._determinant3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4);
+    this.$matrix.m22  =   this._determinant3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4);
+    this.$matrix.m32  = - this._determinant3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4);
+    this.$matrix.m42  =   this._determinant3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4);
+
+    this.$matrix.m13  =   this._determinant3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4);
+    this.$matrix.m23  = - this._determinant3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4);
+    this.$matrix.m33  =   this._determinant3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4);
+    this.$matrix.m43  = - this._determinant3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4);
+
+    this.$matrix.m14  = - this._determinant3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3);
+    this.$matrix.m24  =   this._determinant3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3);
+    this.$matrix.m34  = - this._determinant3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3);
+    this.$matrix.m44  =   this._determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3);
+}
+
+//
+// J3DIVector3
+//
+J3DIVector3 = function(x,y,z)
+{
+    this.load(x,y,z);
+}
+
+J3DIVector3.prototype.load = function(x,y,z)
+{
+    if (typeof x == 'object' && "length" in x) {
+        this[0] = x[0];
+        this[1] = x[1];
+        this[2] = x[2];
+    }
+    else if (typeof x == 'number') {
+        this[0] = x;
+        this[1] = y;
+        this[2] = z;
+    }
+    else {
+        this[0] = 0;
+        this[1] = 0;
+        this[2] = 0;
+    }
+}
+
+J3DIVector3.prototype.getAsArray = function()
+{
+    return [ this[0], this[1], this[2] ];
+}
+
+J3DIVector3.prototype.getAsFloat32Array = function()
+{
+    return new Float32Array(this.getAsArray());
+}
+
+J3DIVector3.prototype.vectorLength = function()
+{
+    return Math.sqrt(this[0] * this[0] + this[1] * this[1] + this[2] * this[2]);
+}
+
+J3DIVector3.prototype.divide = function(divisor)
+{
+    this[0] /= divisor; this[1] /= divisor; this[2] /= divisor;
+}
+
+J3DIVector3.prototype.cross = function(v)
+{
+    this[0] =  this[1] * v[2] - this[2] * v[1];
+    this[1] = -this[0] * v[2] + this[2] * v[0];
+    this[2] =  this[0] * v[1] - this[1] * v[0];
+}
+
+J3DIVector3.prototype.dot = function(v)
+{
+    return this[0] * v[0] + this[1] * v[1] + this[2] * v[2];
+}
+
+J3DIVector3.prototype.combine = function(v, ascl, bscl)
+{
+    this[0] = (ascl * this[0]) + (bscl * v[0]);
+    this[1] = (ascl * this[1]) + (bscl * v[1]);
+    this[2] = (ascl * this[2]) + (bscl * v[2]);
+}
+
+J3DIVector3.prototype.multVecMatrix = function(matrix)
+{
+    var x = this[0];
+    var y = this[1];
+    var z = this[2];
+
+    this[0] = matrix.$matrix.m41 + x * matrix.$matrix.m11 + y * matrix.$matrix.m21 + z * matrix.$matrix.m31;
+    this[1] = matrix.$matrix.m42 + x * matrix.$matrix.m12 + y * matrix.$matrix.m22 + z * matrix.$matrix.m32;
+    this[2] = matrix.$matrix.m43 + x * matrix.$matrix.m13 + y * matrix.$matrix.m23 + z * matrix.$matrix.m33;
+    var w = matrix.$matrix.m44 + x * matrix.$matrix.m14 + y * matrix.$matrix.m24 + z * matrix.$matrix.m34;
+    if (w != 1 && w != 0) {
+        this[0] /= w;
+        this[1] /= w;
+        this[2] /= w;
+    }
+}
+
+J3DIVector3.prototype.toString = function()
+{
+    return "["+this[0]+","+this[1]+","+this[2]+"]";
+}
diff --git a/client/deps/webgl_mpd/src/resources/earthmap1k.jpg b/client/deps/webgl_mpd/src/resources/earthmap1k.jpg
new file mode 100644
index 0000000..7dcab8a
--- /dev/null
+++ b/client/deps/webgl_mpd/src/resources/earthmap1k.jpg
Binary files differ
diff --git a/client/deps/webgl_mpd/src/resources/mars500x250.png b/client/deps/webgl_mpd/src/resources/mars500x250.png
new file mode 100644
index 0000000..f38ef09
--- /dev/null
+++ b/client/deps/webgl_mpd/src/resources/mars500x250.png
Binary files differ
diff --git a/client/deps/webgl_mpd/webgl_mpd.py b/client/deps/webgl_mpd/webgl_mpd.py
new file mode 100755
index 0000000..05fdc2d
--- /dev/null
+++ b/client/deps/webgl_mpd/webgl_mpd.py
@@ -0,0 +1,17 @@
+#!/usr/bin/python
+
+# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import common, os
+from autotest_lib.client.bin import utils
+
+version = 1
+
+def setup():
+    """Nothing needs to be done here."""
+    pass
+
+pwd = os.getcwd()
+utils.update_version(pwd + '/src', True, version, setup)