Browse Source

add rotation animation support

Nikolay Suslov 6 years ago
parent
commit
c20bd79dcd
67 changed files with 21084 additions and 3881 deletions
  1. 54 39
      public/defaults/proxy/vwf.example.com/animation/animationNode.vwf.yaml
  2. 3598 1071
      public/lib/closure/base.js
  3. 1 278
      public/lib/closure/deps.js
  4. 18 10
      public/lib/closure/vec/float32array.js
  5. 11 0
      public/lib/closure/vec/float32array_test.html
  6. 55 0
      public/lib/closure/vec/float32array_test.js
  7. 25 10
      public/lib/closure/vec/float64array.js
  8. 11 0
      public/lib/closure/vec/float64array_test.html
  9. 55 0
      public/lib/closure/vec/float64array_test.js
  10. 337 47
      public/lib/closure/vec/mat3.js
  11. 11 0
      public/lib/closure/vec/mat3_test.html
  12. 451 0
      public/lib/closure/vec/mat3_test.js
  13. 1052 0
      public/lib/closure/vec/mat3d.js
  14. 11 0
      public/lib/closure/vec/mat3d_test.html
  15. 422 0
      public/lib/closure/vec/mat3d_test.js
  16. 1052 0
      public/lib/closure/vec/mat3f.js
  17. 11 0
      public/lib/closure/vec/mat3f_test.html
  18. 422 0
      public/lib/closure/vec/mat3f_test.js
  19. 295 147
      public/lib/closure/vec/mat4.js
  20. 11 0
      public/lib/closure/vec/mat4_test.html
  21. 769 0
      public/lib/closure/vec/mat4_test.js
  22. 1943 0
      public/lib/closure/vec/mat4d.js
  23. 11 0
      public/lib/closure/vec/mat4d_test.html
  24. 788 0
      public/lib/closure/vec/mat4d_test.js
  25. 1943 0
      public/lib/closure/vec/mat4f.js
  26. 11 0
      public/lib/closure/vec/mat4f_test.html
  27. 788 0
      public/lib/closure/vec/mat4f_test.js
  28. 0 722
      public/lib/closure/vec/matrix3.js
  29. 0 1405
      public/lib/closure/vec/matrix4.js
  30. 337 112
      public/lib/closure/vec/quaternion.js
  31. 11 0
      public/lib/closure/vec/quaternion_test.html
  32. 292 0
      public/lib/closure/vec/quaternion_test.js
  33. 6 6
      public/lib/closure/vec/ray.js
  34. 11 0
      public/lib/closure/vec/ray_test.html
  35. 51 0
      public/lib/closure/vec/ray_test.js
  36. 14 6
      public/lib/closure/vec/vec.js
  37. 65 2
      public/lib/closure/vec/vec2.js
  38. 11 0
      public/lib/closure/vec/vec2_test.html
  39. 239 0
      public/lib/closure/vec/vec2_test.js
  40. 463 0
      public/lib/closure/vec/vec2d.js
  41. 11 0
      public/lib/closure/vec/vec2d_test.html
  42. 282 0
      public/lib/closure/vec/vec2d_test.js
  43. 463 0
      public/lib/closure/vec/vec2f.js
  44. 11 0
      public/lib/closure/vec/vec2f_test.html
  45. 282 0
      public/lib/closure/vec/vec2f_test.js
  46. 82 13
      public/lib/closure/vec/vec3.js
  47. 11 0
      public/lib/closure/vec/vec3_test.html
  48. 281 0
      public/lib/closure/vec/vec3_test.js
  49. 548 0
      public/lib/closure/vec/vec3d.js
  50. 11 0
      public/lib/closure/vec/vec3d_test.html
  51. 386 0
      public/lib/closure/vec/vec3d_test.js
  52. 548 0
      public/lib/closure/vec/vec3f.js
  53. 11 0
      public/lib/closure/vec/vec3f_test.html
  54. 386 0
      public/lib/closure/vec/vec3f_test.js
  55. 84 10
      public/lib/closure/vec/vec4.js
  56. 11 0
      public/lib/closure/vec/vec4_test.html
  57. 215 0
      public/lib/closure/vec/vec4_test.js
  58. 408 0
      public/lib/closure/vec/vec4d.js
  59. 11 0
      public/lib/closure/vec/vec4d_test.html
  60. 206 0
      public/lib/closure/vec/vec4d_test.js
  61. 408 0
      public/lib/closure/vec/vec4f.js
  62. 11 0
      public/lib/closure/vec/vec4f_test.html
  63. 206 0
      public/lib/closure/vec/vec4f_test.js
  64. 443 0
      public/lib/closure/vec/vec_array_perf.html
  65. 101 0
      public/lib/closure/vec/vec_perf.html
  66. 8 0
      public/vwf/model/aframe/addon/aframe-components.js
  67. 3 3
      public/vwf/model/aframe/addon/aframe-interpolation.js

+ 54 - 39
public/defaults/proxy/vwf.example.com/animation/animationNode.vwf.yaml

@@ -105,13 +105,13 @@ methods:
       - duration
       - frame
     body: |
-      var rotation = this.rotationFromValue( rotation );
-      var deltaQuaternion = goog.vec.Quaternion.fromAngleAxis(
-        rotation[3] * Math.PI / 180,
-        goog.vec.Vec3.createFromValues( rotation[0], rotation[1], rotation[2] ),
-        goog.vec.Quaternion.create()
-      );
-      this.quaterniateBy( deltaQuaternion, duration, frame ); //@ sourceURL=node3.animation.rotateBy.vwf
+       let rotationValue = this.translationFromValue(rotation);
+       let deltaQuaternion = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
+          (THREE.Math.degToRad(rotationValue[0])),
+          (THREE.Math.degToRad(rotationValue[1])),
+          (THREE.Math.degToRad(rotationValue[2])), 'YXZ'
+        ));
+        this.quaterniateBy( deltaQuaternion, duration, frame ); //@ sourceURL=node3.animation.rotateBy.vwf
 
   ## Rotate to given rotation over duration.
   ## 
@@ -127,12 +127,12 @@ methods:
       - rotation
       - duration
     body: |
-      var rotation = this.rotationFromValue( rotation );
-      var stopQuaternion = goog.vec.Quaternion.fromAngleAxis(
-        rotation[3] * Math.PI / 180,
-        goog.vec.Vec3.createFromValues( rotation[0], rotation[1], rotation[2] ),
-        goog.vec.Quaternion.create()
-      );
+      let rotationValue = this.translationFromValue(rotation);
+      let stopQuaternion = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
+          (THREE.Math.degToRad(rotationValue[0])),
+          (THREE.Math.degToRad(rotationValue[1])),
+          (THREE.Math.degToRad(rotationValue[2])), 'YXZ'
+        ));
       this.quaterniateTo( stopQuaternion, duration ); //@ sourceURL=node3.animation.rotateTo.vwf
 
   ## Rotate by given quaternion over duration.
@@ -151,34 +151,38 @@ methods:
       - duration
       - frame
     body: |
-      this.startQuaternionSIM = this.quaternion || goog.vec.Quaternion.createFromValues( 0, 0, 0, 1 );
-      var deltaQuaternion = this.quaternionFromValue( quaternion );
+      this.startQuaternionSIM = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
+          (THREE.Math.degToRad(this.rotation[0])),
+          (THREE.Math.degToRad(this.rotation[1])),
+          (THREE.Math.degToRad(this.rotation[2])), 'YXZ'
+        ));
+      var deltaQuaternion = (new THREE.Quaternion).copy(quaternion);
       if ( ! frame || frame == "rotated" ) {
-        this.stopQuaternionSIM = goog.vec.Quaternion.concat(
-          deltaQuaternion,
-          this.startQuaternionSIM,
-          goog.vec.Quaternion.create()
-        );
+        this.stopQuaternionSIM = (new THREE.Quaternion()).multiplyQuaternions(deltaQuaternion,  this.startQuaternionSIM);
       } else if ( frame == "scaled" ) {
-        this.stopQuaternionSIM = goog.vec.Quaternion.concat(
-          this.startQuaternionSIM,
-          deltaQuaternion,
-          goog.vec.Quaternion.create()
-        );
+        this.stopQuaternionSIM = (new THREE.Quaternion()).multiplyQuaternions(this.startQuaternionSIM, deltaQuaternion);
       }
+      //this.stopQuaternionSIM = (new THREE.Quaternion()).copy(quaternion);
       if(duration > 0) {
         this.animationDuration = duration;
         this.animationUpdate = function(time, duration) {
-          this.quaternion = goog.vec.Quaternion.slerp(
-            this.startQuaternionSIM, this.stopQuaternionSIM,
-            time >= duration ? 1 : time / duration,
-            goog.vec.Quaternion.create()
-          );
+
+          let q = new THREE.Quaternion();
+          let e = new THREE.Euler();
+          let step = (time >= duration) ? 1 : time / duration;
+
+          THREE.Quaternion.slerp(this.startQuaternionSIM, this.stopQuaternionSIM, q, step || 0);
+          let interp = e.setFromQuaternion(q, 'YXZ');
+          this.rotation = [THREE.Math.radToDeg(interp.x), THREE.Math.radToDeg(interp.y), THREE.Math.radToDeg(interp.z)];
         }
         this.animationPlay(0, duration);
       }
       else {
-        this.quaternion = this.stopQuaternionSIM;
+        let eE = new THREE.Euler();
+        let eQ = (new THREE.Quaternion).copy(this.stopQuaternionSIM);
+        let interpE = eE.setFromQuaternion(eQ, 'YXZ');
+        this.rotation = [THREE.Math.radToDeg(interpE.x),THREE.Math.radToDeg(interpE.y), THREE.Math.radToDeg(interpE.z)];
+        //this.quaternion = this.stopQuaternionSIM;
       } //@ sourceURL=node3.animation.quaterniateBy.vwf
 
   ## Rotate to given quaternion over duration.
@@ -195,21 +199,32 @@ methods:
       - quaternion
       - duration
     body: |
-      this.startQuaternionSIM = this.quaternion || goog.vec.Quaternion.createFromValues( 0, 0, 0, 1 );
-      this.stopQuaternionSIM = this.quaternionFromValue( quaternion );
+      this.startQuaternionSIM = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
+          (THREE.Math.degToRad(this.rotation[0])),
+          (THREE.Math.degToRad(this.rotation[1])),
+          (THREE.Math.degToRad(this.rotation[2])), 'YXZ'
+        ));
+      this.stopQuaternionSIM = (new THREE.Quaternion).copy(quaternion);
       if(duration > 0) {
         this.animationDuration = duration;
         this.animationUpdate = function(time, duration) {
-          this.quaternion = goog.vec.Quaternion.slerp(
-            this.startQuaternionSIM, this.stopQuaternionSIM,
-            duration == 0 ? duration : time / duration,
-            goog.vec.Quaternion.create()
-          );
+
+          let q = new THREE.Quaternion();
+          let e = new THREE.Euler();
+          let step = (time >= duration) ? 1 : time / duration;
+
+          THREE.Quaternion.slerp(this.startQuaternionSIM, this.stopQuaternionSIM, q, step || 0);
+          let interp = e.setFromQuaternion(q, 'YXZ');
+          this.rotation = [THREE.Math.radToDeg(interp.x), THREE.Math.radToDeg(interp.y), THREE.Math.radToDeg(interp.z)];
         }
         this.animationPlay(0, duration);
       }
       else {
-        this.quaternion = this.stopQuaternionSIM;
+        let eE = new THREE.Euler();
+        let eQ = (new THREE.Quaternion).copy(this.stopQuaternionSIM);
+        let interpE = eE.setFromQuaternion(eQ, 'YXZ');
+        this.rotation = [THREE.Math.radToDeg(interpE.x),THREE.Math.radToDeg(interpE.y), THREE.Math.radToDeg(interpE.z)];
+        //this.quaternion = this.stopQuaternionSIM;
       } //@ sourceURL=node3.animation.quaterniateTo.vwf
 
   ## Scale by given scale over duration.

+ 3598 - 1071
public/lib/closure/base.js

@@ -15,153 +15,131 @@
 /**
  * @fileoverview Bootstrap for the Google JS Library (Closure).
  *
- * In uncompiled mode base.js will write out Closure's deps file, unless the
- * global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects to
- * include their own deps file(s) from different locations.
+ * In uncompiled mode base.js will attempt to load Closure's deps file, unless
+ * the global <code>CLOSURE_NO_DEPS</code> is set to true.  This allows projects
+ * to include their own deps file(s) from different locations.
  *
+ * Avoid including base.js more than once. This is strictly discouraged and not
+ * supported. goog.require(...) won't work properly in that case.
+ *
+ * @provideGoog
  */
 
 
 /**
- * @define {boolean} Overridden to true by the compiler when --closure_pass
- *     or --mark_as_compiled is specified.
+ * @define {boolean} Overridden to true by the compiler.
  */
 var COMPILED = false;
 
 
 /**
- * Base namespace for the Closure library.  Checks to see goog is
- * already defined in the current scope before assigning to prevent
- * clobbering if base.js is loaded more than once.
+ * Base namespace for the Closure library.  Checks to see goog is already
+ * defined in the current scope before assigning to prevent clobbering if
+ * base.js is loaded more than once.
  *
  * @const
  */
-var goog = goog || {}; // Identifies this file as the Closure base.
-
+var goog = goog || {};
 
 /**
  * Reference to the global context.  In most cases this will be 'window'.
+ * @const
+ * @suppress {newCheckTypes}
  */
 goog.global = this;
 
 
 /**
- * @define {boolean} DEBUG is provided as a convenience so that debugging code
- * that should not be included in a production js_binary can be easily stripped
- * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
- * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
- * because they are generally used for debugging purposes and it is difficult
- * for the JSCompiler to statically determine whether they are used.
+ * A hook for overriding the define values in uncompiled mode.
+ *
+ * In uncompiled mode, `CLOSURE_UNCOMPILED_DEFINES` may be defined before
+ * loading base.js.  If a key is defined in `CLOSURE_UNCOMPILED_DEFINES`,
+ * `goog.define` will use the value instead of the default value.  This
+ * allows flags to be overwritten without compilation (this is normally
+ * accomplished with the compiler's "define" flag).
+ *
+ * Example:
+ * <pre>
+ *   var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
+ * </pre>
+ *
+ * @type {Object<string, (string|number|boolean)>|undefined}
  */
-goog.DEBUG = true;
+goog.global.CLOSURE_UNCOMPILED_DEFINES;
 
 
 /**
- * @define {string} LOCALE defines the locale being used for compilation. It is
- * used to select locale specific data to be compiled in js binary. BUILD rule
- * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
- * option.
+ * A hook for overriding the define values in uncompiled or compiled mode,
+ * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code.  In
+ * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
  *
- * Take into account that the locale code format is important. You should use
- * the canonical Unicode format with hyphen as a delimiter. Language must be
- * lowercase, Language Script - Capitalized, Region - UPPERCASE.
- * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
+ * string literals or the compiler will emit an error.
  *
- * See more info about locale codes here:
- * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ * While any @define value may be set, only those set with goog.define will be
+ * effective for uncompiled code.
  *
- * For language codes you should use values defined by ISO 693-1. See it here
- * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
- * this rule: the Hebrew language. For legacy reasons the old code (iw) should
- * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
+ * Example:
+ * <pre>
+ *   var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
+ * </pre>
+ *
+ * @type {Object<string, (string|number|boolean)>|undefined}
  */
-goog.LOCALE = 'en';  // default to en
+goog.global.CLOSURE_DEFINES;
 
 
 /**
- * Creates object stubs for a namespace.  The presence of one or more
- * goog.provide() calls indicate that the file defines the given
- * objects/namespaces.  Build tools also scan for provide/require statements
- * to discern dependencies, build dependency files (see deps.js), etc.
- * @see goog.require
- * @param {string} name Namespace provided by this file in the form
- *     "goog.package.part".
+ * Returns true if the specified value is not undefined.
+ *
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is defined.
  */
-goog.provide = function(name) {
-  if (!COMPILED) {
-    // Ensure that the same namespace isn't provided twice. This is intended
-    // to teach new developers that 'goog.provide' is effectively a variable
-    // declaration. And when JSCompiler transforms goog.provide into a real
-    // variable declaration, the compiled JS should work the same as the raw
-    // JS--even when the raw JS uses goog.provide incorrectly.
-    if (goog.isProvided_(name)) {
-      throw Error('Namespace "' + name + '" already declared.');
-    }
-    delete goog.implicitNamespaces_[name];
-
-    var namespace = name;
-    while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
-      if (goog.getObjectByName(namespace)) {
-        break;
-      }
-      goog.implicitNamespaces_[namespace] = true;
-    }
-  }
-
-  goog.exportPath_(name);
+goog.isDef = function(val) {
+  // void 0 always evaluates to undefined and hence we do not need to depend on
+  // the definition of the global variable named 'undefined'.
+  return val !== void 0;
 };
 
-
 /**
- * Marks that the current file should only be used for testing, and never for
- * live code in production.
- * @param {string=} opt_message Optional message to add to the error that's
- *     raised when used in production code.
+ * Returns true if the specified value is a string.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a string.
  */
-goog.setTestOnly = function(opt_message) {
-  if (COMPILED && !goog.DEBUG) {
-    opt_message = opt_message || '';
-    throw Error('Importing test-only code into non-debug environment' +
-                opt_message ? ': ' + opt_message : '.');
-  }
+goog.isString = function(val) {
+  return typeof val == 'string';
 };
 
 
-if (!COMPILED) {
+/**
+ * Returns true if the specified value is a boolean.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is boolean.
+ */
+goog.isBoolean = function(val) {
+  return typeof val == 'boolean';
+};
 
-  /**
-   * Check if the given name has been goog.provided. This will return false for
-   * names that are available only as implicit namespaces.
-   * @param {string} name name of the object to look for.
-   * @return {boolean} Whether the name has been provided.
-   * @private
-   */
-  goog.isProvided_ = function(name) {
-    return !goog.implicitNamespaces_[name] && !!goog.getObjectByName(name);
-  };
 
-  /**
-   * Namespaces implicitly defined by goog.provide. For example,
-   * goog.provide('goog.events.Event') implicitly declares
-   * that 'goog' and 'goog.events' must be namespaces.
-   *
-   * @type {Object}
-   * @private
-   */
-  goog.implicitNamespaces_ = {};
-}
+/**
+ * Returns true if the specified value is a number.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a number.
+ */
+goog.isNumber = function(val) {
+  return typeof val == 'number';
+};
 
 
 /**
- * Builds an object structure for the provided namespace path,
- * ensuring that names that already exist are not overwritten. For
- * example:
+ * Builds an object structure for the provided namespace path, ensuring that
+ * names that already exist are not overwritten. For example:
  * "a.b.c" -> a = {};a.b={};a.b.c={};
  * Used by goog.provide and goog.exportSymbol.
  * @param {string} name name of the object that this file defines.
  * @param {*=} opt_object the object to expose at the end of the path.
  * @param {Object=} opt_objectToExportTo The object to add the path to; default
- *     is |goog.global|.
+ *     is `goog.global`.
  * @private
  */
 goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
@@ -171,21 +149,15 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
   // Internet Explorer exhibits strange behavior when throwing errors from
   // methods externed in this manner.  See the testExportSymbolExceptions in
   // base_test.html for an example.
-  if (!(parts[0] in cur) && cur.execScript) {
+  if (!(parts[0] in cur) && typeof cur.execScript != 'undefined') {
     cur.execScript('var ' + parts[0]);
   }
 
-  // Certain browsers cannot parse code in the form for((a in b); c;);
-  // This pattern is produced by the JSCompiler when it collapses the
-  // statement above into the conditional loop below. To prevent this from
-  // happening, use a for-loop and reserve the init logic as below.
-
-  // Parentheses added to eliminate strict JS warning in Firefox.
   for (var part; parts.length && (part = parts.shift());) {
     if (!parts.length && goog.isDef(opt_object)) {
       // last part and we have an object; use it
       cur[part] = opt_object;
-    } else if (cur[part]) {
+    } else if (cur[part] && cur[part] !== Object.prototype[part]) {
       cur = cur[part];
     } else {
       cur = cur[part] = {};
@@ -195,1285 +167,3840 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
 
 
 /**
- * Returns an object based on its fully qualified external name.  If you are
- * using a compilation pass that renames property names beware that using this
- * function will not find renamed properties.
+ * Defines a named value. In uncompiled mode, the value is retrieved from
+ * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
+ * has the property specified, and otherwise used the defined defaultValue.
+ * When compiled the default can be overridden using the compiler
+ * options or the value set in the CLOSURE_DEFINES object.
  *
- * @param {string} name The fully qualified name.
- * @param {Object=} opt_obj The object within which to look; default is
- *     |goog.global|.
- * @return {?} The value (object or primitive) or, if not found, null.
+ * @param {string} name The distinguished name to provide.
+ * @param {string|number|boolean} defaultValue
  */
-goog.getObjectByName = function(name, opt_obj) {
-  var parts = name.split('.');
-  var cur = opt_obj || goog.global;
-  for (var part; part = parts.shift(); ) {
-    if (goog.isDefAndNotNull(cur[part])) {
-      cur = cur[part];
-    } else {
-      return null;
+goog.define = function(name, defaultValue) {
+  var value = defaultValue;
+  if (!COMPILED) {
+    var uncompiledDefines = goog.global.CLOSURE_UNCOMPILED_DEFINES;
+    var defines = goog.global.CLOSURE_DEFINES;
+    if (uncompiledDefines &&
+        // Anti DOM-clobbering runtime check (b/37736576).
+        /** @type {?} */ (uncompiledDefines).nodeType === undefined &&
+        Object.prototype.hasOwnProperty.call(uncompiledDefines, name)) {
+      value = uncompiledDefines[name];
+    } else if (
+        defines &&
+        // Anti DOM-clobbering runtime check (b/37736576).
+        /** @type {?} */ (defines).nodeType === undefined &&
+        Object.prototype.hasOwnProperty.call(defines, name)) {
+      value = defines[name];
     }
   }
-  return cur;
+  goog.exportPath_(name, value);
 };
 
 
 /**
- * Globalizes a whole namespace, such as goog or goog.lang.
- *
- * @param {Object} obj The namespace to globalize.
- * @param {Object=} opt_global The object to add the properties to.
- * @deprecated Properties may be explicitly exported to the global scope, but
- *     this should no longer be done in bulk.
+ * @define {boolean} DEBUG is provided as a convenience so that debugging code
+ * that should not be included in a production. It can be easily stripped
+ * by specifying --define goog.DEBUG=false to the Closure Compiler aka
+ * JSCompiler. For example, most toString() methods should be declared inside an
+ * "if (goog.DEBUG)" conditional because they are generally used for debugging
+ * purposes and it is difficult for the JSCompiler to statically determine
+ * whether they are used.
  */
-goog.globalize = function(obj, opt_global) {
-  var global = opt_global || goog.global;
-  for (var x in obj) {
-    global[x] = obj[x];
-  }
-};
+goog.define('goog.DEBUG', true);
 
 
 /**
- * Adds a dependency from a file to the files it requires.
- * @param {string} relPath The path to the js file.
- * @param {Array} provides An array of strings with the names of the objects
- *                         this file provides.
- * @param {Array} requires An array of strings with the names of the objects
- *                         this file requires.
+ * @define {string} LOCALE defines the locale being used for compilation. It is
+ * used to select locale specific data to be compiled in js binary. BUILD rule
+ * can specify this value by "--define goog.LOCALE=<locale_name>" as a compiler
+ * option.
+ *
+ * Take into account that the locale code format is important. You should use
+ * the canonical Unicode format with hyphen as a delimiter. Language must be
+ * lowercase, Language Script - Capitalized, Region - UPPERCASE.
+ * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
+ *
+ * See more info about locale codes here:
+ * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
+ *
+ * For language codes you should use values defined by ISO 693-1. See it here
+ * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
+ * this rule: the Hebrew language. For legacy reasons the old code (iw) should
+ * be used instead of the new code (he).
+ *
  */
-goog.addDependency = function(relPath, provides, requires) {
-  if (!COMPILED) {
-    var provide, require;
-    var path = relPath.replace(/\\/g, '/');
-    var deps = goog.dependencies_;
-    for (var i = 0; provide = provides[i]; i++) {
-      deps.nameToPath[provide] = path;
-      if (!(path in deps.pathToNames)) {
-        deps.pathToNames[path] = {};
-      }
-      deps.pathToNames[path][provide] = true;
-    }
-    for (var j = 0; require = requires[j]; j++) {
-      if (!(path in deps.requires)) {
-        deps.requires[path] = {};
-      }
-      deps.requires[path][require] = true;
-    }
-  }
-};
-
+goog.define('goog.LOCALE', 'en');  // default to en
 
 
-
-// NOTE(nnaze): The debug DOM loader was included in base.js as an orignal
-// way to do "debug-mode" development.  The dependency system can sometimes
-// be confusing, as can the debug DOM loader's asyncronous nature.
-//
-// With the DOM loader, a call to goog.require() is not blocking -- the
-// script will not load until some point after the current script.  If a
-// namespace is needed at runtime, it needs to be defined in a previous
-// script, or loaded via require() with its registered dependencies.
-// User-defined namespaces may need their own deps file.  See http://go/js_deps,
-// http://go/genjsdeps, or, externally, DepsWriter.
-// http://code.google.com/closure/library/docs/depswriter.html
-//
-// Because of legacy clients, the DOM loader can't be easily removed from
-// base.js.  Work is being done to make it disableable or replaceable for
-// different environments (DOM-less JavaScript interpreters like Rhino or V8,
-// for example). See bootstrap/ for more information.
+/**
+ * @define {boolean} Whether this code is running on trusted sites.
+ *
+ * On untrusted sites, several native functions can be defined or overridden by
+ * external libraries like Prototype, Datejs, and JQuery and setting this flag
+ * to false forces closure to use its own implementations when possible.
+ *
+ * If your JavaScript can be loaded by a third party site and you are wary about
+ * relying on non-standard implementations, specify
+ * "--define goog.TRUSTED_SITE=false" to the compiler.
+ */
+goog.define('goog.TRUSTED_SITE', true);
 
 
 /**
- * @define {boolean} Whether to enable the debug loader.
+ * @define {boolean} Whether a project is expected to be running in strict mode.
  *
- * If enabled, a call to goog.require() will attempt to load the namespace by
- * appending a script tag to the DOM (if the namespace has been registered).
+ * This define can be used to trigger alternate implementations compatible with
+ * running in EcmaScript Strict mode or warn about unavailable functionality.
+ * @see https://goo.gl/PudQ4y
  *
- * If disabled, goog.require() will simply assert that the namespace has been
- * provided (and depend on the fact that some outside tool correctly ordered
- * the script).
  */
-goog.ENABLE_DEBUG_LOADER = true;
+goog.define('goog.STRICT_MODE_COMPATIBLE', false);
 
 
 /**
- * Implements a system for the dynamic resolution of dependencies
- * that works in parallel with the BUILD system. Note that all calls
- * to goog.require will be stripped by the JSCompiler when the
- * --closure_pass option is used.
- * @see goog.provide
- * @param {string} name Namespace to include (as was given in goog.provide())
- *     in the form "goog.package.part".
+ * @define {boolean} Whether code that calls {@link goog.setTestOnly} should
+ *     be disallowed in the compilation unit.
+ */
+goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG);
+
+
+/**
+ * @define {boolean} Whether to use a Chrome app CSP-compliant method for
+ *     loading scripts via goog.require. @see appendScriptSrcNode_.
  */
-goog.require = function(name) {
+goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
+
 
-  // if the object already exists we do not need do do anything
-  // TODO(arv): If we start to support require based on file name this has
-  //            to change
-  // TODO(arv): If we allow goog.foo.* this has to change
-  // TODO(arv): If we implement dynamic load after page load we should probably
-  //            not remove this code for the compiled output
+/**
+ * Defines a namespace in Closure.
+ *
+ * A namespace may only be defined once in a codebase. It may be defined using
+ * goog.provide() or goog.module().
+ *
+ * The presence of one or more goog.provide() calls in a file indicates
+ * that the file defines the given objects/namespaces.
+ * Provided symbols must not be null or undefined.
+ *
+ * In addition, goog.provide() creates the object stubs for a namespace
+ * (for example, goog.provide("goog.foo.bar") will create the object
+ * goog.foo.bar if it does not already exist).
+ *
+ * Build tools also scan for provide/require/module statements
+ * to discern dependencies, build dependency files (see deps.js), etc.
+ *
+ * @see goog.require
+ * @see goog.module
+ * @param {string} name Namespace provided by this file in the form
+ *     "goog.package.part".
+ */
+goog.provide = function(name) {
+  if (goog.isInModuleLoader_()) {
+    throw new Error('goog.provide cannot be used within a module.');
+  }
   if (!COMPILED) {
+    // Ensure that the same namespace isn't provided twice.
+    // A goog.module/goog.provide maps a goog.require to a specific file
     if (goog.isProvided_(name)) {
-      return;
-    }
-
-    if (goog.ENABLE_DEBUG_LOADER) {
-      var path = goog.getPathFromDeps_(name);
-      if (path) {
-        goog.included_[path] = true;
-        goog.writeScripts_();
-        return;
-      }
+      throw new Error('Namespace "' + name + '" already declared.');
     }
+  }
 
-    var errorMessage = 'goog.require could not find: ' + name;
-    if (goog.global.console) {
-      goog.global.console['error'](errorMessage);
-    }
+  goog.constructNamespace_(name);
+};
 
 
-      throw Error(errorMessage);
+/**
+ * @param {string} name Namespace provided by this file in the form
+ *     "goog.package.part".
+ * @param {Object=} opt_obj The object to embed in the namespace.
+ * @private
+ */
+goog.constructNamespace_ = function(name, opt_obj) {
+  if (!COMPILED) {
+    delete goog.implicitNamespaces_[name];
 
+    var namespace = name;
+    while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
+      if (goog.getObjectByName(namespace)) {
+        break;
+      }
+      goog.implicitNamespaces_[namespace] = true;
+    }
   }
+
+  goog.exportPath_(name, opt_obj);
 };
 
 
 /**
- * Path for included scripts
- * @type {string}
+ * Returns CSP nonce, if set for any script tag.
+ * @param {?Window=} opt_window The window context used to retrieve the nonce.
+ *     Defaults to global context.
+ * @return {string} CSP nonce or empty string if no nonce is present.
  */
-goog.basePath = '';
+goog.getScriptNonce = function(opt_window) {
+  if (opt_window && opt_window != goog.global) {
+    return goog.getScriptNonce_(opt_window.document);
+  }
+  if (goog.cspNonce_ === null) {
+    goog.cspNonce_ = goog.getScriptNonce_(goog.global.document);
+  }
+  return goog.cspNonce_;
+};
 
 
 /**
- * A hook for overriding the base path.
- * @type {string|undefined}
+ * According to the CSP3 spec a nonce must be a valid base64 string.
+ * @see https://www.w3.org/TR/CSP3/#grammardef-base64-value
+ * @private @const
  */
-goog.global.CLOSURE_BASE_PATH;
+goog.NONCE_PATTERN_ = /^[\w+/_-]+[=]{0,2}$/;
 
 
 /**
- * Whether to write out Closure's deps file. By default,
- * the deps are written.
- * @type {boolean|undefined}
+ * @private {?string}
  */
-goog.global.CLOSURE_NO_DEPS;
+goog.cspNonce_ = null;
 
 
 /**
- * A function to import a single script. This is meant to be overridden when
- * Closure is being run in non-HTML contexts, such as web workers. It's defined
- * in the global scope so that it can be set before base.js is loaded, which
- * allows deps.js to be imported properly.
- *
- * The function is passed the script source, which is a relative URI. It should
- * return true if the script was imported, false otherwise.
+ * Returns CSP nonce, if set for any script tag.
+ * @param {!Document} doc
+ * @return {string} CSP nonce or empty string if no nonce is present.
+ * @private
  */
-goog.global.CLOSURE_IMPORT_SCRIPT;
+goog.getScriptNonce_ = function(doc) {
+  var script = doc.querySelector && doc.querySelector('script[nonce]');
+  if (script) {
+    // Try to get the nonce from the IDL property first, because browsers that
+    // implement additional nonce protection features (currently only Chrome) to
+    // prevent nonce stealing via CSS do not expose the nonce via attributes.
+    // See https://github.com/whatwg/html/issues/2369
+    var nonce = script['nonce'] || script.getAttribute('nonce');
+    if (nonce && goog.NONCE_PATTERN_.test(nonce)) {
+      return nonce;
+    }
+  }
+  return '';
+};
 
 
 /**
- * Null function used for default values of callbacks, etc.
- * @return {void} Nothing.
+ * Module identifier validation regexp.
+ * Note: This is a conservative check, it is very possible to be more lenient,
+ *   the primary exclusion here is "/" and "\" and a leading ".", these
+ *   restrictions are intended to leave the door open for using goog.require
+ *   with relative file paths rather than module identifiers.
+ * @private
  */
-goog.nullFunction = function() {};
+goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
 
 
 /**
- * The identity function. Returns its first argument.
+ * Defines a module in Closure.
+ *
+ * Marks that this file must be loaded as a module and claims the namespace.
+ *
+ * A namespace may only be defined once in a codebase. It may be defined using
+ * goog.provide() or goog.module().
+ *
+ * goog.module() has three requirements:
+ * - goog.module may not be used in the same file as goog.provide.
+ * - goog.module must be the first statement in the file.
+ * - only one goog.module is allowed per file.
+ *
+ * When a goog.module annotated file is loaded, it is enclosed in
+ * a strict function closure. This means that:
+ * - any variables declared in a goog.module file are private to the file
+ * (not global), though the compiler is expected to inline the module.
+ * - The code must obey all the rules of "strict" JavaScript.
+ * - the file will be marked as "use strict"
  *
- * @param {...*} var_args The arguments of the function.
- * @return {*} The first argument.
- * @deprecated Use goog.functions.identity instead.
+ * NOTE: unlike goog.provide, goog.module does not declare any symbols by
+ * itself. If declared symbols are desired, use
+ * goog.module.declareLegacyNamespace().
+ *
+ *
+ * See the public goog.module proposal: http://goo.gl/Va1hin
+ *
+ * @param {string} name Namespace provided by this file in the form
+ *     "goog.package.part", is expected but not required.
+ * @return {void}
  */
-goog.identityFunction = function(var_args) {
-  return arguments[0];
+goog.module = function(name) {
+  if (!goog.isString(name) || !name ||
+      name.search(goog.VALID_MODULE_RE_) == -1) {
+    throw new Error('Invalid module identifier');
+  }
+  if (!goog.isInGoogModuleLoader_()) {
+    throw new Error(
+        'Module ' + name + ' has been loaded incorrectly. Note, ' +
+        'modules cannot be loaded as normal scripts. They require some kind of ' +
+        'pre-processing step. You\'re likely trying to load a module via a ' +
+        'script tag or as a part of a concatenated bundle without rewriting the ' +
+        'module. For more info see: ' +
+        'https://github.com/google/closure-library/wiki/goog.module:-an-ES6-module-like-alternative-to-goog.provide.');
+  }
+  if (goog.moduleLoaderState_.moduleName) {
+    throw new Error('goog.module may only be called once per module.');
+  }
+
+  // Store the module name for the loader.
+  goog.moduleLoaderState_.moduleName = name;
+  if (!COMPILED) {
+    // Ensure that the same namespace isn't provided twice.
+    // A goog.module/goog.provide maps a goog.require to a specific file
+    if (goog.isProvided_(name)) {
+      throw new Error('Namespace "' + name + '" already declared.');
+    }
+    delete goog.implicitNamespaces_[name];
+  }
 };
 
 
 /**
- * When defining a class Foo with an abstract method bar(), you can do:
- *
- * Foo.prototype.bar = goog.abstractMethod
+ * @param {string} name The module identifier.
+ * @return {?} The module exports for an already loaded module or null.
  *
- * Now if a subclass of Foo fails to override bar(), an error
- * will be thrown when bar() is invoked.
- *
- * Note: This does not take the name of the function to override as
- * an argument because that would make it more difficult to obfuscate
- * our JavaScript code.
- *
- * @type {!Function}
- * @throws {Error} when invoked to indicate the method should be
- *   overridden.
+ * Note: This is not an alternative to goog.require, it does not
+ * indicate a hard dependency, instead it is used to indicate
+ * an optional dependency or to access the exports of a module
+ * that has already been loaded.
+ * @suppress {missingProvide}
  */
-goog.abstractMethod = function() {
-  throw Error('unimplemented abstract method');
+goog.module.get = function(name) {
+
+  return goog.module.getInternal_(name);
 };
 
 
 /**
- * Adds a {@code getInstance} static method that always return the same instance
- * object.
- * @param {!Function} ctor The constructor for the class to add the static
- *     method to.
+ * @param {string} name The module identifier.
+ * @return {?} The module exports for an already loaded module or null.
+ * @private
  */
-goog.addSingletonGetter = function(ctor) {
-  ctor.getInstance = function() {
-    return ctor.instance_ || (ctor.instance_ = new ctor());
-  };
+goog.module.getInternal_ = function(name) {
+  if (!COMPILED) {
+    if (name in goog.loadedModules_) {
+      return goog.loadedModules_[name].exports;
+    } else if (!goog.implicitNamespaces_[name]) {
+      var ns = goog.getObjectByName(name);
+      return ns != null ? ns : null;
+    }
+  }
+  return null;
 };
 
 
-if (!COMPILED && goog.ENABLE_DEBUG_LOADER) {
-  /**
-   * Object used to keep track of urls that have already been added. This
-   * record allows the prevention of circular dependencies.
-   * @type {Object}
-   * @private
-   */
-  goog.included_ = {};
+/**
+ * Types of modules the debug loader can load.
+ * @enum {string}
+ */
+goog.ModuleType = {
+  ES6: 'es6',
+  GOOG: 'goog'
+};
 
 
-  /**
-   * This object is used to keep track of dependencies and other data that is
-   * used for loading scripts
-   * @private
-   * @type {Object}
-   */
-  goog.dependencies_ = {
-    pathToNames: {}, // 1 to many
-    nameToPath: {}, // 1 to 1
-    requires: {}, // 1 to many
-    // used when resolving dependencies to prevent us from
-    // visiting the file twice
-    visited: {},
-    written: {} // used to keep track of script files we have written
-  };
+/**
+ * @private {?{
+ *   moduleName: (string|undefined),
+ *   declareLegacyNamespace:boolean,
+ *   type: ?goog.ModuleType
+ * }}
+ */
+goog.moduleLoaderState_ = null;
 
 
-  /**
-   * Tries to detect whether is in the context of an HTML document.
-   * @return {boolean} True if it looks like HTML document.
-   * @private
-   */
-  goog.inHtmlDocument_ = function() {
-    var doc = goog.global.document;
-    return typeof doc != 'undefined' &&
-           'write' in doc;  // XULDocument misses write.
-  };
+/**
+ * @private
+ * @return {boolean} Whether a goog.module or an es6 module is currently being
+ *     initialized.
+ */
+goog.isInModuleLoader_ = function() {
+  return goog.isInGoogModuleLoader_() || goog.isInEs6ModuleLoader_();
+};
 
 
-  /**
-   * Tries to detect the base path of the base.js script that bootstraps Closure
-   * @private
-   */
-  goog.findBasePath_ = function() {
-    if (goog.global.CLOSURE_BASE_PATH) {
-      goog.basePath = goog.global.CLOSURE_BASE_PATH;
-      return;
-    } else if (!goog.inHtmlDocument_()) {
-      return;
-    }
-    var doc = goog.global.document;
-    var scripts = doc.getElementsByTagName('script');
-    // Search backwards since the current script is in almost all cases the one
-    // that has base.js.
-    for (var i = scripts.length - 1; i >= 0; --i) {
-      var src = scripts[i].src;
-      var qmark = src.lastIndexOf('?');
-      var l = qmark == -1 ? src.length : qmark;
-      if (src.substr(l - 7, 7) == 'base.js') {
-        goog.basePath = src.substr(0, l - 7);
-        return;
-      }
-    }
-  };
+/**
+ * @private
+ * @return {boolean} Whether a goog.module is currently being initialized.
+ */
+goog.isInGoogModuleLoader_ = function() {
+  return !!goog.moduleLoaderState_ &&
+      goog.moduleLoaderState_.type == goog.ModuleType.GOOG;
+};
 
 
-  /**
-   * Imports a script if, and only if, that script hasn't already been imported.
-   * (Must be called at execution time)
-   * @param {string} src Script source.
-   * @private
-   */
-  goog.importScript_ = function(src) {
-    var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
-        goog.writeScriptTag_;
-    if (!goog.dependencies_.written[src] && importScript(src)) {
-      goog.dependencies_.written[src] = true;
-    }
-  };
+/**
+ * @private
+ * @return {boolean} Whether an es6 module is currently being initialized.
+ */
+goog.isInEs6ModuleLoader_ = function() {
+  var inLoader = !!goog.moduleLoaderState_ &&
+      goog.moduleLoaderState_.type == goog.ModuleType.ES6;
 
+  if (inLoader) {
+    return true;
+  }
 
-  /**
-   * The default implementation of the import function. Writes a script tag to
-   * import the script.
-   *
-   * @param {string} src The script source.
-   * @return {boolean} True if the script was imported, false otherwise.
-   * @private
-   */
-  goog.writeScriptTag_ = function(src) {
-    if (goog.inHtmlDocument_()) {
-      var doc = goog.global.document;
-      doc.write(
-          '<script type="text/javascript" src="' + src + '"></' + 'script>');
-      return true;
-    } else {
+  var jscomp = goog.global['$jscomp'];
+
+  if (jscomp) {
+    // jscomp may not have getCurrentModulePath if this is a compiled bundle
+    // that has some of the runtime, but not all of it. This can happen if
+    // optimizations are turned on so the unused runtime is removed but renaming
+    // and Closure pass are off (so $jscomp is still named $jscomp and the
+    // goog.provide/require calls still exist).
+    if (typeof jscomp.getCurrentModulePath != 'function') {
       return false;
     }
-  };
 
+    // Bundled ES6 module.
+    return !!jscomp.getCurrentModulePath();
+  }
 
-  /**
-   * Resolves dependencies based on the dependencies added using addDependency
-   * and calls importScript_ in the correct order.
-   * @private
-   */
-  goog.writeScripts_ = function() {
-    // the scripts we need to write this time
-    var scripts = [];
-    var seenScript = {};
-    var deps = goog.dependencies_;
-
-    function visitNode(path) {
-      if (path in deps.written) {
-        return;
-      }
+  return false;
+};
 
-      // we have already visited this one. We can get here if we have cyclic
-      // dependencies
-      if (path in deps.visited) {
-        if (!(path in seenScript)) {
-          seenScript[path] = true;
-          scripts.push(path);
-        }
-        return;
-      }
 
-      deps.visited[path] = true;
+/**
+ * Provide the module's exports as a globally accessible object under the
+ * module's declared name.  This is intended to ease migration to goog.module
+ * for files that have existing usages.
+ * @suppress {missingProvide}
+ */
+goog.module.declareLegacyNamespace = function() {
+  if (!COMPILED && !goog.isInGoogModuleLoader_()) {
+    throw new Error(
+        'goog.module.declareLegacyNamespace must be called from ' +
+        'within a goog.module');
+  }
+  if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
+    throw new Error(
+        'goog.module must be called prior to ' +
+        'goog.module.declareLegacyNamespace.');
+  }
+  goog.moduleLoaderState_.declareLegacyNamespace = true;
+};
 
-      if (path in deps.requires) {
-        for (var requireName in deps.requires[path]) {
-          // If the required name is defined, we assume that it was already
-          // bootstrapped by other means.
-          if (!goog.isProvided_(requireName)) {
-            if (requireName in deps.nameToPath) {
-              visitNode(deps.nameToPath[requireName]);
-            } else {
-              throw Error('Undefined nameToPath for ' + requireName);
-            }
-          }
-        }
-      }
 
-      if (!(path in seenScript)) {
-        seenScript[path] = true;
-        scripts.push(path);
-      }
+/**
+ * Associates an ES6 module with a Closure module ID so that is available via
+ * goog.require. The associated ID  acts like a goog.module ID - it does not
+ * create any global names, it is merely available via goog.require /
+ * goog.module.get / goog.forwardDeclare / goog.requireType. goog.require and
+ * goog.module.get will return the entire module as if it was import *'d. This
+ * allows Closure files to reference ES6 modules for the sake of migration.
+ *
+ * @param {string} namespace
+ * @suppress {missingProvide}
+ */
+goog.declareModuleId = function(namespace) {
+  if (!COMPILED) {
+    if (!goog.isInEs6ModuleLoader_()) {
+      throw new Error(
+          'goog.declareModuleId may only be called from ' +
+          'within an ES6 module');
     }
-
-    for (var path in goog.included_) {
-      if (!deps.written[path]) {
-        visitNode(path);
-      }
+    if (goog.moduleLoaderState_ && goog.moduleLoaderState_.moduleName) {
+      throw new Error(
+          'goog.declareModuleId may only be called once per module.');
     }
-
-    for (var i = 0; i < scripts.length; i++) {
-      if (scripts[i]) {
-        goog.importScript_(goog.basePath + scripts[i]);
-      } else {
-        throw Error('Undefined script input');
-      }
+    if (namespace in goog.loadedModules_) {
+      throw new Error(
+          'Module with namespace "' + namespace + '" already exists.');
     }
-  };
+  }
+  if (goog.moduleLoaderState_) {
+    // Not bundled - debug loading.
+    goog.moduleLoaderState_.moduleName = namespace;
+  } else {
+    // Bundled - not debug loading, no module loader state.
+    var jscomp = goog.global['$jscomp'];
+    if (!jscomp || typeof jscomp.getCurrentModulePath != 'function') {
+      throw new Error(
+          'Module with namespace "' + namespace +
+          '" has been loaded incorrectly.');
+    }
+    var exports = jscomp.require(jscomp.getCurrentModulePath());
+    goog.loadedModules_[namespace] = {
+      exports: exports,
+      type: goog.ModuleType.ES6,
+      moduleId: namespace
+    };
+  }
+};
 
 
-  /**
-   * Looks at the dependency rules and tries to determine the script file that
-   * fulfills a particular rule.
-   * @param {string} rule In the form goog.namespace.Class or project.script.
-   * @return {?string} Url corresponding to the rule, or null.
-   * @private
-   */
-  goog.getPathFromDeps_ = function(rule) {
-    if (rule in goog.dependencies_.nameToPath) {
-      return goog.dependencies_.nameToPath[rule];
-    } else {
-      return null;
-    }
-  };
+/**
+ * Deprecated old name for goog.declareModuleId. This function is being renamed
+ * to help disambiguate with goog.module.declareLegacyNamespace.
+ *
+ * @type {function(string): undefined}
+ * @suppress {missingProvide}
+ */
+goog.module.declareNamespace = goog.declareModuleId;
 
-  goog.findBasePath_();
 
-  // Allow projects to manage the deps files themselves.
-  if (!goog.global.CLOSURE_NO_DEPS) {
-    goog.importScript_(goog.basePath + 'deps.js');
+/**
+ * Marks that the current file should only be used for testing, and never for
+ * live code in production.
+ *
+ * In the case of unit tests, the message may optionally be an exact namespace
+ * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
+ * provide (if not explicitly defined in the code).
+ *
+ * @param {string=} opt_message Optional message to add to the error that's
+ *     raised when used in production code.
+ */
+goog.setTestOnly = function(opt_message) {
+  if (goog.DISALLOW_TEST_ONLY_CODE) {
+    opt_message = opt_message || '';
+    throw new Error(
+        'Importing test-only code into non-debug environment' +
+        (opt_message ? ': ' + opt_message : '.'));
   }
-}
-
+};
 
 
-//==============================================================================
-// Language Enhancements
-//==============================================================================
+/**
+ * Forward declares a symbol. This is an indication to the compiler that the
+ * symbol may be used in the source yet is not required and may not be provided
+ * in compilation.
+ *
+ * The most common usage of forward declaration is code that takes a type as a
+ * function parameter but does not need to require it. By forward declaring
+ * instead of requiring, no hard dependency is made, and (if not required
+ * elsewhere) the namespace may never be required and thus, not be pulled
+ * into the JavaScript binary. If it is required elsewhere, it will be type
+ * checked as normal.
+ *
+ * Before using goog.forwardDeclare, please read the documentation at
+ * https://github.com/google/closure-compiler/wiki/Bad-Type-Annotation to
+ * understand the options and tradeoffs when working with forward declarations.
+ *
+ * @param {string} name The namespace to forward declare in the form of
+ *     "goog.package.part".
+ */
+goog.forwardDeclare = function(name) {};
 
 
 /**
- * This is a "fixed" version of the typeof operator.  It differs from the typeof
- * operator in such a way that null returns 'null' and arrays return 'array'.
- * @param {*} value The value to get the type of.
- * @return {string} The name of the type.
+ * Forward declare type information. Used to assign types to goog.global
+ * referenced object that would otherwise result in unknown type references
+ * and thus block property disambiguation.
  */
-goog.typeOf = function(value) {
-  var s = typeof value;
-  if (s == 'object') {
-    if (value) {
-      // Check these first, so we can avoid calling Object.prototype.toString if
-      // possible.
-      //
-      // IE improperly marshals tyepof across execution contexts, but a
-      // cross-context object will still return false for "instanceof Object".
-      if (value instanceof Array) {
-        return 'array';
-      } else if (value instanceof Object) {
-        return s;
-      }
+goog.forwardDeclare('Document');
+goog.forwardDeclare('HTMLScriptElement');
+goog.forwardDeclare('XMLHttpRequest');
 
-      // HACK: In order to use an Object prototype method on the arbitrary
-      //   value, the compiler requires the value be cast to type Object,
-      //   even though the ECMA spec explicitly allows it.
-      var className = Object.prototype.toString.call(
-          /** @type {Object} */ (value));
-      // In Firefox 3.6, attempting to access iframe window objects' length
-      // property throws an NS_ERROR_FAILURE, so we need to special-case it
-      // here.
-      if (className == '[object Window]') {
-        return 'object';
-      }
 
-      // We cannot always use constructor == Array or instanceof Array because
-      // different frames have different Array objects. In IE6, if the iframe
-      // where the array was created is destroyed, the array loses its
-      // prototype. Then dereferencing val.splice here throws an exception, so
-      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
-      // so that will work. In this case, this function will return false and
-      // most array functions will still work because the array is still
-      // array-like (supports length and []) even though it has lost its
-      // prototype.
-      // Mark Miller noticed that Object.prototype.toString
-      // allows access to the unforgeable [[Class]] property.
-      //  15.2.4.2 Object.prototype.toString ( )
-      //  When the toString method is called, the following steps are taken:
-      //      1. Get the [[Class]] property of this object.
-      //      2. Compute a string value by concatenating the three strings
-      //         "[object ", Result(1), and "]".
-      //      3. Return Result(2).
-      // and this behavior survives the destruction of the execution context.
-      if ((className == '[object Array]' ||
-           // In IE all non value types are wrapped as objects across window
-           // boundaries (not iframe though) so we have to do object detection
-           // for this edge case
-           typeof value.length == 'number' &&
-           typeof value.splice != 'undefined' &&
-           typeof value.propertyIsEnumerable != 'undefined' &&
-           !value.propertyIsEnumerable('splice')
+if (!COMPILED) {
+  /**
+   * Check if the given name has been goog.provided. This will return false for
+   * names that are available only as implicit namespaces.
+   * @param {string} name name of the object to look for.
+   * @return {boolean} Whether the name has been provided.
+   * @private
+   */
+  goog.isProvided_ = function(name) {
+    return (name in goog.loadedModules_) ||
+        (!goog.implicitNamespaces_[name] &&
+         goog.isDefAndNotNull(goog.getObjectByName(name)));
+  };
 
-          )) {
-        return 'array';
-      }
-      // HACK: There is still an array case that fails.
-      //     function ArrayImpostor() {}
-      //     ArrayImpostor.prototype = [];
-      //     var impostor = new ArrayImpostor;
-      // this can be fixed by getting rid of the fast path
-      // (value instanceof Array) and solely relying on
-      // (value && Object.prototype.toString.vall(value) === '[object Array]')
-      // but that would require many more function calls and is not warranted
-      // unless closure code is receiving objects from untrusted sources.
+  /**
+   * Namespaces implicitly defined by goog.provide. For example,
+   * goog.provide('goog.events.Event') implicitly declares that 'goog' and
+   * 'goog.events' must be namespaces.
+   *
+   * @type {!Object<string, (boolean|undefined)>}
+   * @private
+   */
+  goog.implicitNamespaces_ = {'goog.module': true};
 
-      // IE in cross-window calls does not correctly marshal the function type
-      // (it appears just as an object) so we cannot use just typeof val ==
-      // 'function'. However, if the object has a call property, it is a
-      // function.
-      if ((className == '[object Function]' ||
-          typeof value.call != 'undefined' &&
-          typeof value.propertyIsEnumerable != 'undefined' &&
-          !value.propertyIsEnumerable('call'))) {
-        return 'function';
-      }
+  // NOTE: We add goog.module as an implicit namespace as goog.module is defined
+  // here and because the existing module package has not been moved yet out of
+  // the goog.module namespace. This satisifies both the debug loader and
+  // ahead-of-time dependency management.
+}
 
 
-    } else {
-      return 'null';
+/**
+ * Returns an object based on its fully qualified external name.  The object
+ * is not found if null or undefined.  If you are using a compilation pass that
+ * renames property names beware that using this function will not find renamed
+ * properties.
+ *
+ * @param {string} name The fully qualified name.
+ * @param {Object=} opt_obj The object within which to look; default is
+ *     |goog.global|.
+ * @return {?} The value (object or primitive) or, if not found, null.
+ */
+goog.getObjectByName = function(name, opt_obj) {
+  var parts = name.split('.');
+  var cur = opt_obj || goog.global;
+  for (var i = 0; i < parts.length; i++) {
+    cur = cur[parts[i]];
+    if (!goog.isDefAndNotNull(cur)) {
+      return null;
     }
-
-  } else if (s == 'function' && typeof value.call == 'undefined') {
-    // In Safari typeof nodeList returns 'function', and on Firefox
-    // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
-    // and RegExps.  We would like to return object for those and we can
-    // detect an invalid function by making sure that the function
-    // object has a call method.
-    return 'object';
   }
-  return s;
+  return cur;
 };
 
 
 /**
- * Returns true if the specified value is not |undefined|.
- * WARNING: Do not use this to test if an object has a property. Use the in
- * operator instead.  Additionally, this function assumes that the global
- * undefined variable has not been redefined.
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is defined.
+ * Globalizes a whole namespace, such as goog or goog.lang.
+ *
+ * @param {!Object} obj The namespace to globalize.
+ * @param {Object=} opt_global The object to add the properties to.
+ * @deprecated Properties may be explicitly exported to the global scope, but
+ *     this should no longer be done in bulk.
  */
-goog.isDef = function(val) {
-  return val !== undefined;
+goog.globalize = function(obj, opt_global) {
+  var global = opt_global || goog.global;
+  for (var x in obj) {
+    global[x] = obj[x];
+  }
 };
 
 
 /**
- * Returns true if the specified value is |null|
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is null.
+ * Adds a dependency from a file to the files it requires.
+ * @param {string} relPath The path to the js file.
+ * @param {!Array<string>} provides An array of strings with
+ *     the names of the objects this file provides.
+ * @param {!Array<string>} requires An array of strings with
+ *     the names of the objects this file requires.
+ * @param {boolean|!Object<string>=} opt_loadFlags Parameters indicating
+ *     how the file must be loaded.  The boolean 'true' is equivalent
+ *     to {'module': 'goog'} for backwards-compatibility.  Valid properties
+ *     and values include {'module': 'goog'} and {'lang': 'es6'}.
  */
-goog.isNull = function(val) {
-  return val === null;
+goog.addDependency = function(relPath, provides, requires, opt_loadFlags) {
+  if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
+    goog.debugLoader_.addDependency(relPath, provides, requires, opt_loadFlags);
+  }
 };
 
 
-/**
- * Returns true if the specified value is defined and not null
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is defined and not null.
+
+
+// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
+// to do "debug-mode" development.  The dependency system can sometimes be
+// confusing, as can the debug DOM loader's asynchronous nature.
+//
+// With the DOM loader, a call to goog.require() is not blocking -- the script
+// will not load until some point after the current script.  If a namespace is
+// needed at runtime, it needs to be defined in a previous script, or loaded via
+// require() with its registered dependencies.
+//
+// User-defined namespaces may need their own deps file. For a reference on
+// creating a deps file, see:
+// Externally: https://developers.google.com/closure/library/docs/depswriter
+//
+// Because of legacy clients, the DOM loader can't be easily removed from
+// base.js.  Work was done to make it disableable or replaceable for
+// different environments (DOM-less JavaScript interpreters like Rhino or V8,
+// for example). See bootstrap/ for more information.
+
+
+/**
+ * @define {boolean} Whether to enable the debug loader.
+ *
+ * If enabled, a call to goog.require() will attempt to load the namespace by
+ * appending a script tag to the DOM (if the namespace has been registered).
+ *
+ * If disabled, goog.require() will simply assert that the namespace has been
+ * provided (and depend on the fact that some outside tool correctly ordered
+ * the script).
  */
-goog.isDefAndNotNull = function(val) {
-  // Note that undefined == null.
-  return val != null;
-};
+goog.define('goog.ENABLE_DEBUG_LOADER', true);
 
 
 /**
- * Returns true if the specified value is an array
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is an array.
+ * @param {string} msg
+ * @private
  */
-goog.isArray = function(val) {
-  return goog.typeOf(val) == 'array';
+goog.logToConsole_ = function(msg) {
+  if (goog.global.console) {
+    goog.global.console['error'](msg);
+  }
 };
 
 
 /**
- * Returns true if the object looks like an array. To qualify as array like
- * the value needs to be either a NodeList or an object with a Number length
- * property.
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is an array.
+ * Implements a system for the dynamic resolution of dependencies that works in
+ * parallel with the BUILD system.
+ *
+ * Note that all calls to goog.require will be stripped by the compiler.
+ *
+ * @see goog.provide
+ * @param {string} namespace Namespace (as was given in goog.provide,
+ *     goog.module, or goog.declareModuleId) in the form
+ *     "goog.package.part".
+ * @return {?} If called within a goog.module or ES6 module file, the associated
+ *     namespace or module otherwise null.
  */
-goog.isArrayLike = function(val) {
-  var type = goog.typeOf(val);
-  return type == 'array' || type == 'object' && typeof val.length == 'number';
+goog.require = function(namespace) {
+  if (!COMPILED) {
+    // Might need to lazy load on old IE.
+    if (goog.ENABLE_DEBUG_LOADER) {
+      goog.debugLoader_.requested(namespace);
+    }
+
+    // If the object already exists we do not need to do anything.
+    if (goog.isProvided_(namespace)) {
+      if (goog.isInModuleLoader_()) {
+        return goog.module.getInternal_(namespace);
+      }
+    } else if (goog.ENABLE_DEBUG_LOADER) {
+      var moduleLoaderState = goog.moduleLoaderState_;
+      goog.moduleLoaderState_ = null;
+      try {
+        goog.debugLoader_.load_(namespace);
+      } finally {
+        goog.moduleLoaderState_ = moduleLoaderState;
+      }
+    }
+
+    return null;
+  }
 };
 
 
 /**
- * Returns true if the object looks like a Date. To qualify as Date-like
- * the value needs to be an object and have a getFullYear() function.
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is a like a Date.
+ * Requires a symbol for its type information. This is an indication to the
+ * compiler that the symbol may appear in type annotations, yet it is not
+ * referenced at runtime.
+ *
+ * When called within a goog.module or ES6 module file, the return value may be
+ * assigned to or destructured into a variable, but it may not be otherwise used
+ * in code outside of a type annotation.
+ *
+ * Note that all calls to goog.requireType will be stripped by the compiler.
+ *
+ * @param {string} namespace Namespace (as was given in goog.provide,
+ *     goog.module, or goog.declareModuleId) in the form
+ *     "goog.package.part".
+ * @return {?}
  */
-goog.isDateLike = function(val) {
-  return goog.isObject(val) && typeof val.getFullYear == 'function';
+goog.requireType = function(namespace) {
+  // Return an empty object so that single-level destructuring of the return
+  // value doesn't crash at runtime when using the debug loader. Multi-level
+  // destructuring isn't supported.
+  return {};
 };
 
 
 /**
- * Returns true if the specified value is a string
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is a string.
+ * Path for included scripts.
+ * @type {string}
  */
-goog.isString = function(val) {
-  return typeof val == 'string';
-};
+goog.basePath = '';
 
 
 /**
- * Returns true if the specified value is a boolean
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is boolean.
+ * A hook for overriding the base path.
+ * @type {string|undefined}
  */
-goog.isBoolean = function(val) {
-  return typeof val == 'boolean';
-};
+goog.global.CLOSURE_BASE_PATH;
 
 
 /**
- * Returns true if the specified value is a number
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is a number.
+ * Whether to attempt to load Closure's deps file. By default, when uncompiled,
+ * deps files will attempt to be loaded.
+ * @type {boolean|undefined}
  */
-goog.isNumber = function(val) {
-  return typeof val == 'number';
-};
+goog.global.CLOSURE_NO_DEPS;
 
 
 /**
- * Returns true if the specified value is a function
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is a function.
+ * A function to import a single script. This is meant to be overridden when
+ * Closure is being run in non-HTML contexts, such as web workers. It's defined
+ * in the global scope so that it can be set before base.js is loaded, which
+ * allows deps.js to be imported properly.
+ *
+ * The first parameter the script source, which is a relative URI. The second,
+ * optional parameter is the script contents, in the event the script needed
+ * transformation. It should return true if the script was imported, false
+ * otherwise.
+ * @type {(function(string, string=): boolean)|undefined}
  */
-goog.isFunction = function(val) {
-  return goog.typeOf(val) == 'function';
-};
+goog.global.CLOSURE_IMPORT_SCRIPT;
 
 
 /**
- * Returns true if the specified value is an object.  This includes arrays
- * and functions.
- * @param {*} val Variable to test.
- * @return {boolean} Whether variable is an object.
+ * Null function used for default values of callbacks, etc.
+ * @return {void} Nothing.
  */
-goog.isObject = function(val) {
-  var type = typeof val;
-  return type == 'object' && val != null || type == 'function';
-  // return Object(val) === val also works, but is slower, especially if val is
-  // not an object.
-};
+goog.nullFunction = function() {};
 
 
 /**
- * Gets a unique ID for an object. This mutates the object so that further
- * calls with the same object as a parameter returns the same value. The unique
- * ID is guaranteed to be unique across the current session amongst objects that
- * are passed into {@code getUid}. There is no guarantee that the ID is unique
- * or consistent across sessions. It is unsafe to generate unique ID for
- * function prototypes.
+ * When defining a class Foo with an abstract method bar(), you can do:
+ * Foo.prototype.bar = goog.abstractMethod
  *
- * @param {Object} obj The object to get the unique ID for.
- * @return {number} The unique ID for the object.
+ * Now if a subclass of Foo fails to override bar(), an error will be thrown
+ * when bar() is invoked.
+ *
+ * @type {!Function}
+ * @throws {Error} when invoked to indicate the method should be overridden.
  */
-goog.getUid = function(obj) {
-  // TODO(arv): Make the type stricter, do not accept null.
-
-  // In Opera window.hasOwnProperty exists but always returns false so we avoid
-  // using it. As a consequence the unique ID generated for BaseClass.prototype
-  // and SubClass.prototype will be the same.
-  return obj[goog.UID_PROPERTY_] ||
-      (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
+goog.abstractMethod = function() {
+  throw new Error('unimplemented abstract method');
 };
 
 
 /**
- * Removes the unique ID from an object. This is useful if the object was
- * previously mutated using {@code goog.getUid} in which case the mutation is
- * undone.
- * @param {Object} obj The object to remove the unique ID field from.
+ * Adds a `getInstance` static method that always returns the same
+ * instance object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ *     method to.
+ * @suppress {missingProperties} 'instance_' isn't a property on 'Function'
+ *     but we don't have a better type to use here.
  */
-goog.removeUid = function(obj) {
-  // TODO(arv): Make the type stricter, do not accept null.
-
-  // DOM nodes in IE are not instance of Object and throws exception
-  // for delete. Instead we try to use removeAttribute
-  if ('removeAttribute' in obj) {
-    obj.removeAttribute(goog.UID_PROPERTY_);
-  }
-  /** @preserveTry */
-  try {
-    delete obj[goog.UID_PROPERTY_];
-  } catch (ex) {
-  }
+goog.addSingletonGetter = function(ctor) {
+  // instance_ is immediately set to prevent issues with sealed constructors
+  // such as are encountered when a constructor is returned as the export object
+  // of a goog.module in unoptimized code.
+  // Delcare type to avoid conformance violations that ctor.instance_ is unknown
+  /** @type {undefined|!Object} @suppress {underscore} */
+  ctor.instance_ = undefined;
+  ctor.getInstance = function() {
+    if (ctor.instance_) {
+      return ctor.instance_;
+    }
+    if (goog.DEBUG) {
+      // NOTE: JSCompiler can't optimize away Array#push.
+      goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
+    }
+    // Cast to avoid conformance violations that ctor.instance_ is unknown
+    return /** @type {!Object|undefined} */ (ctor.instance_) = new ctor;
+  };
 };
 
 
 /**
- * Name for unique ID property. Initialized in a way to help avoid collisions
- * with other closure javascript on the same page.
- * @type {string}
+ * All singleton classes that have been instantiated, for testing. Don't read
+ * it directly, use the `goog.testing.singleton` module. The compiler
+ * removes this variable if unused.
+ * @type {!Array<!Function>}
  * @private
  */
-goog.UID_PROPERTY_ = 'closure_uid_' +
-    Math.floor(Math.random() * 2147483648).toString(36);
+goog.instantiatedSingletons_ = [];
 
 
 /**
- * Counter for UID.
- * @type {number}
- * @private
+ * @define {boolean} Whether to load goog.modules using `eval` when using
+ * the debug loader.  This provides a better debugging experience as the
+ * source is unmodified and can be edited using Chrome Workspaces or similar.
+ * However in some environments the use of `eval` is banned
+ * so we provide an alternative.
  */
-goog.uidCounter_ = 0;
+goog.define('goog.LOAD_MODULE_USING_EVAL', true);
 
 
 /**
- * Adds a hash code field to an object. The hash code is unique for the
- * given object.
- * @param {Object} obj The object to get the hash code for.
- * @return {number} The hash code for the object.
- * @deprecated Use goog.getUid instead.
+ * @define {boolean} Whether the exports of goog.modules should be sealed when
+ * possible.
  */
-goog.getHashCode = goog.getUid;
+goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG);
 
 
 /**
- * Removes the hash code field from an object.
- * @param {Object} obj The object to remove the field from.
- * @deprecated Use goog.removeUid instead.
+ * The registry of initialized modules:
+ * The module identifier or path to module exports map.
+ * @private @const {!Object<string, {exports:?,type:string,moduleId:string}>}
  */
-goog.removeHashCode = goog.removeUid;
+goog.loadedModules_ = {};
 
 
 /**
- * Clones a value. The input may be an Object, Array, or basic type. Objects and
- * arrays will be cloned recursively.
- *
- * WARNINGS:
- * <code>goog.cloneObject</code> does not detect reference loops. Objects that
- * refer to themselves will cause infinite recursion.
- *
- * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
- * UIDs created by <code>getUid</code> into cloned results.
- *
- * @param {*} obj The value to clone.
- * @return {*} A clone of the input value.
- * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
+ * True if the debug loader enabled and used.
+ * @const {boolean}
  */
-goog.cloneObject = function(obj) {
-  var type = goog.typeOf(obj);
-  if (type == 'object' || type == 'array') {
-    if (obj.clone) {
-      return obj.clone();
-    }
-    var clone = type == 'array' ? [] : {};
-    for (var key in obj) {
-      clone[key] = goog.cloneObject(obj[key]);
-    }
-    return clone;
-  }
+goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
 
-  return obj;
-};
+
+/**
+ * @define {string} How to decide whether to transpile.  Valid values
+ * are 'always', 'never', and 'detect'.  The default ('detect') is to
+ * use feature detection to determine which language levels need
+ * transpilation.
+ */
+// NOTE(sdh): we could expand this to accept a language level to bypass
+// detection: e.g. goog.TRANSPILE == 'es5' would transpile ES6 files but
+// would leave ES3 and ES5 files alone.
+goog.define('goog.TRANSPILE', 'detect');
 
 
 /**
- * Forward declaration for the clone method. This is necessary until the
- * compiler can better support duck-typing constructs as used in
- * goog.cloneObject.
+ * @define {string} If a file needs to be transpiled what the output language
+ * should be. By default this is the highest language level this file detects
+ * the current environment supports. Generally this flag should not be set, but
+ * it could be useful to override. Example: If the current environment supports
+ * ES6 then by default ES7+ files will be transpiled to ES6, unless this is
+ * overridden.
  *
- * TODO(brenneman): Remove once the JSCompiler can infer that the check for
- * proto.clone is safe in goog.cloneObject.
+ * Valid values include: es3, es5, es6, es7, and es8. Anything not recognized
+ * is treated as es3.
  *
- * @type {Function}
+ * Note that setting this value does not force transpilation. Just if
+ * transpilation occurs this will be the output. So this is most useful when
+ * goog.TRANSPILE is set to 'always' and then forcing the language level to be
+ * something lower than what the environment detects.
  */
-Object.prototype.clone;
+goog.define('goog.TRANSPILE_TO_LANGUAGE', '');
 
 
 /**
- * A native implementation of goog.bind.
- * @param {Function} fn A function to partially apply.
- * @param {Object|undefined} selfObj Specifies the object which |this| should
- *     point to when the function is run.
- * @param {...*} var_args Additional arguments that are partially
- *     applied to the function.
- * @return {!Function} A partially-applied form of the function bind() was
- *     invoked as a method of.
- * @private
- * @suppress {deprecated} The compiler thinks that Function.prototype.bind
- *     is deprecated because some people have declared a pure-JS version.
- *     Only the pure-JS version is truly deprecated.
+ * @define {string} Path to the transpiler.  Executing the script at this
+ * path (relative to base.js) should define a function $jscomp.transpile.
  */
-goog.bindNative_ = function(fn, selfObj, var_args) {
-  return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
-};
+goog.define('goog.TRANSPILER', 'transpile.js');
 
 
 /**
- * A pure-JS implementation of goog.bind.
- * @param {Function} fn A function to partially apply.
- * @param {Object|undefined} selfObj Specifies the object which |this| should
- *     point to when the function is run.
- * @param {...*} var_args Additional arguments that are partially
- *     applied to the function.
- * @return {!Function} A partially-applied form of the function bind() was
- *     invoked as a method of.
- * @private
+ * @package {?boolean}
+ * Visible for testing.
  */
-goog.bindJs_ = function(fn, selfObj, var_args) {
-  if (!fn) {
-    throw new Error();
-  }
+goog.hasBadLetScoping = null;
 
-  if (arguments.length > 2) {
-    var boundArgs = Array.prototype.slice.call(arguments, 2);
-    return function() {
-      // Prepend the bound arguments to the current arguments.
-      var newArgs = Array.prototype.slice.call(arguments);
-      Array.prototype.unshift.apply(newArgs, boundArgs);
-      return fn.apply(selfObj, newArgs);
-    };
 
-  } else {
-    return function() {
-      return fn.apply(selfObj, arguments);
-    };
+/**
+ * @return {boolean}
+ * @package Visible for testing.
+ */
+goog.useSafari10Workaround = function() {
+  if (goog.hasBadLetScoping == null) {
+    var hasBadLetScoping;
+    try {
+      hasBadLetScoping = !eval(
+          '"use strict";' +
+          'let x = 1; function f() { return typeof x; };' +
+          'f() == "number";');
+    } catch (e) {
+      // Assume that ES6 syntax isn't supported.
+      hasBadLetScoping = false;
+    }
+    goog.hasBadLetScoping = hasBadLetScoping;
   }
+  return goog.hasBadLetScoping;
 };
 
 
 /**
- * Partially applies this function to a particular 'this object' and zero or
- * more arguments. The result is a new function with some arguments of the first
- * function pre-filled and the value of |this| 'pre-specified'.<br><br>
- *
- * Remaining arguments specified at call-time are appended to the pre-
- * specified ones.<br><br>
- *
- * Also see: {@link #partial}.<br><br>
- *
- * Usage:
- * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
- * barMethBound('arg3', 'arg4');</pre>
- *
- * @param {Function} fn A function to partially apply.
- * @param {Object|undefined} selfObj Specifies the object which |this| should
- *     point to when the function is run.
- * @param {...*} var_args Additional arguments that are partially
- *     applied to the function.
- * @return {!Function} A partially-applied form of the function bind() was
- *     invoked as a method of.
- * @suppress {deprecated} See above.
+ * @param {string} moduleDef
+ * @return {string}
+ * @package Visible for testing.
  */
-goog.bind = function(fn, selfObj, var_args) {
-  // TODO(nicksantos): narrow the type signature.
-  if (Function.prototype.bind &&
-      // NOTE(nicksantos): Somebody pulled base.js into the default
-      // Chrome extension environment. This means that for Chrome extensions,
-      // they get the implementation of Function.prototype.bind that
-      // calls goog.bind instead of the native one. Even worse, we don't want
-      // to introduce a circular dependency between goog.bind and
-      // Function.prototype.bind, so we have to hack this to make sure it
-      // works correctly.
-      Function.prototype.bind.toString().indexOf('native code') != -1) {
-    goog.bind = goog.bindNative_;
-  } else {
-    goog.bind = goog.bindJs_;
-  }
-  return goog.bind.apply(null, arguments);
+goog.workaroundSafari10EvalBug = function(moduleDef) {
+  return '(function(){' + moduleDef +
+      '\n' +  // Terminate any trailing single line comment.
+      ';' +   // Terminate any trailing expression.
+      '})();\n';
 };
 
 
 /**
- * Like bind(), except that a 'this object' is not required. Useful when the
- * target function is already bound.
- *
- * Usage:
- * var g = partial(f, arg1, arg2);
- * g(arg3, arg4);
- *
- * @param {Function} fn A function to partially apply.
- * @param {...*} var_args Additional arguments that are partially
- *     applied to fn.
- * @return {!Function} A partially-applied form of the function bind() was
- *     invoked as a method of.
+ * @param {function(?):?|string} moduleDef The module definition.
  */
-goog.partial = function(fn, var_args) {
-  var args = Array.prototype.slice.call(arguments, 1);
-  return function() {
-    // Prepend the bound arguments to the current arguments.
-    var newArgs = Array.prototype.slice.call(arguments);
-    newArgs.unshift.apply(newArgs, args);
-    return fn.apply(this, newArgs);
-  };
+goog.loadModule = function(moduleDef) {
+  // NOTE: we allow function definitions to be either in the from
+  // of a string to eval (which keeps the original source intact) or
+  // in a eval forbidden environment (CSP) we allow a function definition
+  // which in its body must call `goog.module`, and return the exports
+  // of the module.
+  var previousState = goog.moduleLoaderState_;
+  try {
+    goog.moduleLoaderState_ = {
+      moduleName: '',
+      declareLegacyNamespace: false,
+      type: goog.ModuleType.GOOG
+    };
+    var exports;
+    if (goog.isFunction(moduleDef)) {
+      exports = moduleDef.call(undefined, {});
+    } else if (goog.isString(moduleDef)) {
+      if (goog.useSafari10Workaround()) {
+        moduleDef = goog.workaroundSafari10EvalBug(moduleDef);
+      }
+
+      exports = goog.loadModuleFromSource_.call(undefined, moduleDef);
+    } else {
+      throw new Error('Invalid module definition');
+    }
+
+    var moduleName = goog.moduleLoaderState_.moduleName;
+    if (goog.isString(moduleName) && moduleName) {
+      // Don't seal legacy namespaces as they may be used as a parent of
+      // another namespace
+      if (goog.moduleLoaderState_.declareLegacyNamespace) {
+        goog.constructNamespace_(moduleName, exports);
+      } else if (
+          goog.SEAL_MODULE_EXPORTS && Object.seal &&
+          typeof exports == 'object' && exports != null) {
+        Object.seal(exports);
+      }
+
+      var data = {
+        exports: exports,
+        type: goog.ModuleType.GOOG,
+        moduleId: goog.moduleLoaderState_.moduleName
+      };
+      goog.loadedModules_[moduleName] = data;
+    } else {
+      throw new Error('Invalid module name \"' + moduleName + '\"');
+    }
+  } finally {
+    goog.moduleLoaderState_ = previousState;
+  }
 };
 
 
 /**
- * Copies all the members of a source object to a target object. This method
- * does not work on all browsers for all objects that contain keys such as
- * toString or hasOwnProperty. Use goog.object.extend for this purpose.
- * @param {Object} target Target.
- * @param {Object} source Source.
+ * @private @const
  */
-goog.mixin = function(target, source) {
-  for (var x in source) {
-    target[x] = source[x];
-  }
-
-  // For IE7 or lower, the for-in-loop does not contain any properties that are
-  // not enumerable on the prototype object (for example, isPrototypeOf from
-  // Object.prototype) but also it will not include 'replace' on objects that
-  // extend String and change 'replace' (not that it is common for anyone to
-  // extend anything except Object).
-};
-
-
-/**
- * @return {number} An integer value representing the number of milliseconds
- *     between midnight, January 1, 1970 and the current time.
- */
-goog.now = Date.now || (function() {
-  // Unary plus operator converts its operand to a number which in the case of
-  // a date is done by calling getTime().
-  return +new Date();
-});
+goog.loadModuleFromSource_ = /** @type {function(string):?} */ (function() {
+  // NOTE: we avoid declaring parameters or local variables here to avoid
+  // masking globals or leaking values into the module definition.
+  'use strict';
+  var exports = {};
+  eval(arguments[0]);
+  return exports;
+});
 
 
 /**
- * Evals javascript in the global scope.  In IE this uses execScript, other
- * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
- * global scope (for example, in Safari), appends a script tag instead.
- * Throws an exception if neither execScript or eval is defined.
- * @param {string} script JavaScript string.
+ * Normalize a file path by removing redundant ".." and extraneous "." file
+ * path components.
+ * @param {string} path
+ * @return {string}
+ * @private
  */
-goog.globalEval = function(script) {
-  if (goog.global.execScript) {
-    goog.global.execScript(script, 'JavaScript');
-  } else if (goog.global.eval) {
-    // Test to see if eval works
-    if (goog.evalWorksForGlobals_ == null) {
-      goog.global.eval('var _et_ = 1;');
-      if (typeof goog.global['_et_'] != 'undefined') {
-        delete goog.global['_et_'];
-        goog.evalWorksForGlobals_ = true;
-      } else {
-        goog.evalWorksForGlobals_ = false;
-      }
-    }
-
-    if (goog.evalWorksForGlobals_) {
-      goog.global.eval(script);
+goog.normalizePath_ = function(path) {
+  var components = path.split('/');
+  var i = 0;
+  while (i < components.length) {
+    if (components[i] == '.') {
+      components.splice(i, 1);
+    } else if (
+        i && components[i] == '..' && components[i - 1] &&
+        components[i - 1] != '..') {
+      components.splice(--i, 2);
     } else {
-      var doc = goog.global.document;
-      var scriptElt = doc.createElement('script');
-      scriptElt.type = 'text/javascript';
-      scriptElt.defer = false;
-      // Note(user): can't use .innerHTML since "t('<test>')" will fail and
-      // .text doesn't work in Safari 2.  Therefore we append a text node.
-      scriptElt.appendChild(doc.createTextNode(script));
-      doc.body.appendChild(scriptElt);
-      doc.body.removeChild(scriptElt);
+      i++;
     }
-  } else {
-    throw Error('goog.globalEval not available');
   }
+  return components.join('/');
 };
 
 
 /**
- * Indicates whether or not we can call 'eval' directly to eval code in the
- * global scope. Set to a Boolean by the first call to goog.globalEval (which
- * empirically tests whether eval works for globals). @see goog.globalEval
- * @type {?boolean}
- * @private
+ * Provides a hook for loading a file when using Closure's goog.require() API
+ * with goog.modules.  In particular this hook is provided to support Node.js.
+ *
+ * @type {(function(string):string)|undefined}
  */
-goog.evalWorksForGlobals_ = null;
+goog.global.CLOSURE_LOAD_FILE_SYNC;
 
 
 /**
- * Optional map of CSS class names to obfuscated names used with
- * goog.getCssName().
- * @type {Object|undefined}
+ * Loads file by synchronous XHR. Should not be used in production environments.
+ * @param {string} src Source URL.
+ * @return {?string} File contents, or null if load failed.
  * @private
- * @see goog.setCssNameMapping
  */
-goog.cssNameMapping_;
+goog.loadFileSync_ = function(src) {
+  if (goog.global.CLOSURE_LOAD_FILE_SYNC) {
+    return goog.global.CLOSURE_LOAD_FILE_SYNC(src);
+  } else {
+    try {
+      /** @type {XMLHttpRequest} */
+      var xhr = new goog.global['XMLHttpRequest']();
+      xhr.open('get', src, false);
+      xhr.send();
+      // NOTE: Successful http: requests have a status of 200, but successful
+      // file: requests may have a status of zero.  Any other status, or a
+      // thrown exception (particularly in case of file: requests) indicates
+      // some sort of error, which we treat as a missing or unavailable file.
+      return xhr.status == 0 || xhr.status == 200 ? xhr.responseText : null;
+    } catch (err) {
+      // No need to rethrow or log, since errors should show up on their own.
+      return null;
+    }
+  }
+};
 
 
 /**
- * Optional obfuscation style for CSS class names. Should be set to either
- * 'BY_WHOLE' or 'BY_PART' if defined.
- * @type {string|undefined}
+ * Lazily retrieves the transpiler and applies it to the source.
+ * @param {string} code JS code.
+ * @param {string} path Path to the code.
+ * @param {string} target Language level output.
+ * @return {string} The transpiled code.
  * @private
- * @see goog.setCssNameMapping
  */
-goog.cssNameMappingStyle_;
+goog.transpile_ = function(code, path, target) {
+  var jscomp = goog.global['$jscomp'];
+  if (!jscomp) {
+    goog.global['$jscomp'] = jscomp = {};
+  }
+  var transpile = jscomp.transpile;
+  if (!transpile) {
+    var transpilerPath = goog.basePath + goog.TRANSPILER;
+    var transpilerCode = goog.loadFileSync_(transpilerPath);
+    if (transpilerCode) {
+      // This must be executed synchronously, since by the time we know we
+      // need it, we're about to load and write the ES6 code synchronously,
+      // so a normal script-tag load will be too slow. Wrapped in a function
+      // so that code is eval'd in the global scope.
+      (function() {
+        eval(transpilerCode + '\n//# sourceURL=' + transpilerPath);
+      }).call(goog.global);
+      // Even though the transpiler is optional, if $gwtExport is found, it's
+      // a sign the transpiler was loaded and the $jscomp.transpile *should*
+      // be there.
+      if (goog.global['$gwtExport'] && goog.global['$gwtExport']['$jscomp'] &&
+          !goog.global['$gwtExport']['$jscomp']['transpile']) {
+        throw new Error(
+            'The transpiler did not properly export the "transpile" ' +
+            'method. $gwtExport: ' + JSON.stringify(goog.global['$gwtExport']));
+      }
+      // transpile.js only exports a single $jscomp function, transpile. We
+      // grab just that and add it to the existing definition of $jscomp which
+      // contains the polyfills.
+      goog.global['$jscomp'].transpile =
+          goog.global['$gwtExport']['$jscomp']['transpile'];
+      jscomp = goog.global['$jscomp'];
+      transpile = jscomp.transpile;
+    }
+  }
+  if (!transpile) {
+    // The transpiler is an optional component.  If it's not available then
+    // replace it with a pass-through function that simply logs.
+    var suffix = ' requires transpilation but no transpiler was found.';
+    transpile = jscomp.transpile = function(code, path) {
+      // TODO(sdh): figure out some way to get this error to show up
+      // in test results, noting that the failure may occur in many
+      // different ways, including in loadModule() before the test
+      // runner even comes up.
+      goog.logToConsole_(path + suffix);
+      return code;
+    };
+  }
+  // Note: any transpilation errors/warnings will be logged to the console.
+  return transpile(code, path, target);
+};
+
+//==============================================================================
+// Language Enhancements
+//==============================================================================
 
 
 /**
- * Handles strings that are intended to be used as CSS class names.
- *
- * This function works in tandem with @see goog.setCssNameMapping.
- *
- * Without any mapping set, the arguments are simple joined with a
- * hyphen and passed through unaltered.
- *
- * When there is a mapping, there are two possible styles in which
- * these mappings are used. In the BY_PART style, each part (i.e. in
- * between hyphens) of the passed in css name is rewritten according
- * to the map. In the BY_WHOLE style, the full css name is looked up in
- * the map directly. If a rewrite is not specified by the map, the
- * compiler will output a warning.
- *
- * When the mapping is passed to the compiler, it will replace calls
- * to goog.getCssName with the strings from the mapping, e.g.
- *     var x = goog.getCssName('foo');
- *     var y = goog.getCssName(this.baseClass, 'active');
- *  becomes:
- *     var x= 'foo';
- *     var y = this.baseClass + '-active';
- *
- * If one argument is passed it will be processed, if two are passed
- * only the modifier will be processed, as it is assumed the first
- * argument was generated as a result of calling goog.getCssName.
- *
- * @param {string} className The class name.
- * @param {string=} opt_modifier A modifier to be appended to the class name.
- * @return {string} The class name or the concatenation of the class name and
- *     the modifier.
+ * This is a "fixed" version of the typeof operator.  It differs from the typeof
+ * operator in such a way that null returns 'null' and arrays return 'array'.
+ * @param {?} value The value to get the type of.
+ * @return {string} The name of the type.
  */
-goog.getCssName = function(className, opt_modifier) {
-  var getMapping = function(cssName) {
-    return goog.cssNameMapping_[cssName] || cssName;
-  };
+goog.typeOf = function(value) {
+  var s = typeof value;
+  if (s == 'object') {
+    if (value) {
+      // Check these first, so we can avoid calling Object.prototype.toString if
+      // possible.
+      //
+      // IE improperly marshals typeof across execution contexts, but a
+      // cross-context object will still return false for "instanceof Object".
+      if (value instanceof Array) {
+        return 'array';
+      } else if (value instanceof Object) {
+        return s;
+      }
 
-  var renameByParts = function(cssName) {
-    // Remap all the parts individually.
-    var parts = cssName.split('-');
-    var mapped = [];
-    for (var i = 0; i < parts.length; i++) {
-      mapped.push(getMapping(parts[i]));
+      // HACK: In order to use an Object prototype method on the arbitrary
+      //   value, the compiler requires the value be cast to type Object,
+      //   even though the ECMA spec explicitly allows it.
+      var className = Object.prototype.toString.call(
+          /** @type {!Object} */ (value));
+      // In Firefox 3.6, attempting to access iframe window objects' length
+      // property throws an NS_ERROR_FAILURE, so we need to special-case it
+      // here.
+      if (className == '[object Window]') {
+        return 'object';
+      }
+
+      // We cannot always use constructor == Array or instanceof Array because
+      // different frames have different Array objects. In IE6, if the iframe
+      // where the array was created is destroyed, the array loses its
+      // prototype. Then dereferencing val.splice here throws an exception, so
+      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
+      // so that will work. In this case, this function will return false and
+      // most array functions will still work because the array is still
+      // array-like (supports length and []) even though it has lost its
+      // prototype.
+      // Mark Miller noticed that Object.prototype.toString
+      // allows access to the unforgeable [[Class]] property.
+      //  15.2.4.2 Object.prototype.toString ( )
+      //  When the toString method is called, the following steps are taken:
+      //      1. Get the [[Class]] property of this object.
+      //      2. Compute a string value by concatenating the three strings
+      //         "[object ", Result(1), and "]".
+      //      3. Return Result(2).
+      // and this behavior survives the destruction of the execution context.
+      if ((className == '[object Array]' ||
+           // In IE all non value types are wrapped as objects across window
+           // boundaries (not iframe though) so we have to do object detection
+           // for this edge case.
+           typeof value.length == 'number' &&
+               typeof value.splice != 'undefined' &&
+               typeof value.propertyIsEnumerable != 'undefined' &&
+               !value.propertyIsEnumerable('splice')
+
+               )) {
+        return 'array';
+      }
+      // HACK: There is still an array case that fails.
+      //     function ArrayImpostor() {}
+      //     ArrayImpostor.prototype = [];
+      //     var impostor = new ArrayImpostor;
+      // this can be fixed by getting rid of the fast path
+      // (value instanceof Array) and solely relying on
+      // (value && Object.prototype.toString.vall(value) === '[object Array]')
+      // but that would require many more function calls and is not warranted
+      // unless closure code is receiving objects from untrusted sources.
+
+      // IE in cross-window calls does not correctly marshal the function type
+      // (it appears just as an object) so we cannot use just typeof val ==
+      // 'function'. However, if the object has a call property, it is a
+      // function.
+      if ((className == '[object Function]' ||
+           typeof value.call != 'undefined' &&
+               typeof value.propertyIsEnumerable != 'undefined' &&
+               !value.propertyIsEnumerable('call'))) {
+        return 'function';
+      }
+
+    } else {
+      return 'null';
     }
-    return mapped.join('-');
-  };
 
-  var rename;
-  if (goog.cssNameMapping_) {
-    rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
-        getMapping : renameByParts;
-  } else {
-    rename = function(a) {
-      return a;
-    };
+  } else if (s == 'function' && typeof value.call == 'undefined') {
+    // In Safari typeof nodeList returns 'function', and on Firefox typeof
+    // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
+    // would like to return object for those and we can detect an invalid
+    // function by making sure that the function object has a call method.
+    return 'object';
   }
+  return s;
+};
 
-  if (opt_modifier) {
-    return className + '-' + rename(opt_modifier);
-  } else {
-    return rename(className);
-  }
+
+/**
+ * Returns true if the specified value is null.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is null.
+ */
+goog.isNull = function(val) {
+  return val === null;
 };
 
 
 /**
- * Sets the map to check when returning a value from goog.getCssName(). Example:
- * <pre>
- * goog.setCssNameMapping({
- *   "goog": "a",
- *   "disabled": "b",
- * });
- *
- * var x = goog.getCssName('goog');
- * // The following evaluates to: "a a-b".
- * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
- * </pre>
- * When declared as a map of string literals to string literals, the JSCompiler
- * will replace all calls to goog.getCssName() using the supplied map if the
- * --closure_pass flag is set.
- *
- * @param {!Object} mapping A map of strings to strings where keys are possible
- *     arguments to goog.getCssName() and values are the corresponding values
- *     that should be returned.
- * @param {string=} opt_style The style of css name mapping. There are two valid
- *     options: 'BY_PART', and 'BY_WHOLE'.
- * @see goog.getCssName for a description.
+ * Returns true if the specified value is defined and not null.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is defined and not null.
  */
-goog.setCssNameMapping = function(mapping, opt_style) {
-  goog.cssNameMapping_ = mapping;
-  goog.cssNameMappingStyle_ = opt_style;
+goog.isDefAndNotNull = function(val) {
+  // Note that undefined == null.
+  return val != null;
 };
 
 
 /**
- * To use CSS renaming in compiled mode, one of the input files should have a
- * call to goog.setCssNameMapping() with an object literal that the JSCompiler
- * can extract and use to replace all calls to goog.getCssName(). In uncompiled
- * mode, JavaScript code should be loaded before this base.js file that declares
- * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
- * to ensure that the mapping is loaded before any calls to goog.getCssName()
- * are made in uncompiled mode.
- *
- * A hook for overriding the CSS name mapping.
- * @type {Object|undefined}
+ * Returns true if the specified value is an array.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an array.
  */
-goog.global.CLOSURE_CSS_NAME_MAPPING;
+goog.isArray = function(val) {
+  return goog.typeOf(val) == 'array';
+};
 
 
-if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
-  // This does not call goog.setCssNameMapping() because the JSCompiler
-  // requires that goog.setCssNameMapping() be called with an object literal.
-  goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
-}
+/**
+ * Returns true if the object looks like an array. To qualify as array like
+ * the value needs to be either a NodeList or an object with a Number length
+ * property. Note that for this function neither strings nor functions are
+ * considered "array-like".
+ *
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an array.
+ */
+goog.isArrayLike = function(val) {
+  var type = goog.typeOf(val);
+  // We do not use goog.isObject here in order to exclude function values.
+  return type == 'array' || type == 'object' && typeof val.length == 'number';
+};
 
 
 /**
- * Abstract implementation of goog.getMsg for use with localized messages.
- * @param {string} str Translatable string, places holders in the form {$foo}.
- * @param {Object=} opt_values Map of place holder name to value.
- * @return {string} message with placeholders filled.
+ * Returns true if the object looks like a Date. To qualify as Date-like the
+ * value needs to be an object and have a getFullYear() function.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a like a Date.
  */
-goog.getMsg = function(str, opt_values) {
-  var values = opt_values || {};
-  for (var key in values) {
-    var value = ('' + values[key]).replace(/\$/g, '$$$$');
-    str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), value);
-  }
-  return str;
+goog.isDateLike = function(val) {
+  return goog.isObject(val) && typeof val.getFullYear == 'function';
 };
 
 
 /**
- * Exposes an unobfuscated global namespace path for the given object.
- * Note that fields of the exported object *will* be obfuscated,
- * unless they are exported in turn via this function or
- * goog.exportProperty
- *
- * <p>Also handy for making public items that are defined in anonymous
- * closures.
- *
- * ex. goog.exportSymbol('Foo', Foo);
- *
- * ex. goog.exportSymbol('public.path.Foo.staticFunction',
- *                       Foo.staticFunction);
- *     public.path.Foo.staticFunction();
- *
- * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
- *                       Foo.prototype.myMethod);
- *     new public.path.Foo().myMethod();
- *
- * @param {string} publicPath Unobfuscated name to export.
- * @param {*} object Object the name should point to.
- * @param {Object=} opt_objectToExportTo The object to add the path to; default
- *     is |goog.global|.
+ * Returns true if the specified value is a function.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is a function.
  */
-goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
-  goog.exportPath_(publicPath, object, opt_objectToExportTo);
+goog.isFunction = function(val) {
+  return goog.typeOf(val) == 'function';
 };
 
 
 /**
- * Exports a property unobfuscated into the object's namespace.
- * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
- * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
- * @param {Object} object Object whose static property is being exported.
- * @param {string} publicName Unobfuscated name to export.
- * @param {*} symbol Object the name should point to.
+ * Returns true if the specified value is an object.  This includes arrays and
+ * functions.
+ * @param {?} val Variable to test.
+ * @return {boolean} Whether variable is an object.
  */
-goog.exportProperty = function(object, publicName, symbol) {
-  object[publicName] = symbol;
+goog.isObject = function(val) {
+  var type = typeof val;
+  return type == 'object' && val != null || type == 'function';
+  // return Object(val) === val also works, but is slower, especially if val is
+  // not an object.
 };
 
 
 /**
- * Inherit the prototype methods from one constructor into another.
+ * Gets a unique ID for an object. This mutates the object so that further calls
+ * with the same object as a parameter returns the same value. The unique ID is
+ * guaranteed to be unique across the current session amongst objects that are
+ * passed into `getUid`. There is no guarantee that the ID is unique or
+ * consistent across sessions. It is unsafe to generate unique ID for function
+ * prototypes.
  *
- * Usage:
- * <pre>
- * function ParentClass(a, b) { }
- * ParentClass.prototype.foo = function(a) { }
+ * @param {Object} obj The object to get the unique ID for.
+ * @return {number} The unique ID for the object.
+ */
+goog.getUid = function(obj) {
+  // TODO(arv): Make the type stricter, do not accept null.
+
+  // In Opera window.hasOwnProperty exists but always returns false so we avoid
+  // using it. As a consequence the unique ID generated for BaseClass.prototype
+  // and SubClass.prototype will be the same.
+  return obj[goog.UID_PROPERTY_] ||
+      (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
+};
+
+
+/**
+ * Whether the given object is already assigned a unique ID.
  *
- * function ChildClass(a, b, c) {
- *   goog.base(this, a, b);
- * }
- * goog.inherits(ChildClass, ParentClass);
+ * This does not modify the object.
  *
- * var child = new ChildClass('a', 'b', 'see');
- * child.foo(); // works
- * </pre>
+ * @param {!Object} obj The object to check.
+ * @return {boolean} Whether there is an assigned unique id for the object.
+ */
+goog.hasUid = function(obj) {
+  return !!obj[goog.UID_PROPERTY_];
+};
+
+
+/**
+ * Removes the unique ID from an object. This is useful if the object was
+ * previously mutated using `goog.getUid` in which case the mutation is
+ * undone.
+ * @param {Object} obj The object to remove the unique ID field from.
+ */
+goog.removeUid = function(obj) {
+  // TODO(arv): Make the type stricter, do not accept null.
+
+  // In IE, DOM nodes are not instances of Object and throw an exception if we
+  // try to delete.  Instead we try to use removeAttribute.
+  if (obj !== null && 'removeAttribute' in obj) {
+    obj.removeAttribute(goog.UID_PROPERTY_);
+  }
+
+  try {
+    delete obj[goog.UID_PROPERTY_];
+  } catch (ex) {
+  }
+};
+
+
+/**
+ * Name for unique ID property. Initialized in a way to help avoid collisions
+ * with other closure JavaScript on the same page.
+ * @type {string}
+ * @private
+ */
+goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
+
+
+/**
+ * Counter for UID.
+ * @type {number}
+ * @private
+ */
+goog.uidCounter_ = 0;
+
+
+/**
+ * Adds a hash code field to an object. The hash code is unique for the
+ * given object.
+ * @param {Object} obj The object to get the hash code for.
+ * @return {number} The hash code for the object.
+ * @deprecated Use goog.getUid instead.
+ */
+goog.getHashCode = goog.getUid;
+
+
+/**
+ * Removes the hash code field from an object.
+ * @param {Object} obj The object to remove the field from.
+ * @deprecated Use goog.removeUid instead.
+ */
+goog.removeHashCode = goog.removeUid;
+
+
+/**
+ * Clones a value. The input may be an Object, Array, or basic type. Objects and
+ * arrays will be cloned recursively.
  *
- * In addition, a superclass' implementation of a method can be invoked
- * as follows:
+ * WARNINGS:
+ * <code>goog.cloneObject</code> does not detect reference loops. Objects that
+ * refer to themselves will cause infinite recursion.
  *
- * <pre>
- * ChildClass.prototype.foo = function(a) {
- *   ChildClass.superClass_.foo.call(this, a);
- *   // other code
- * };
- * </pre>
+ * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
+ * UIDs created by <code>getUid</code> into cloned results.
  *
- * @param {Function} childCtor Child class.
- * @param {Function} parentCtor Parent class.
+ * @param {*} obj The value to clone.
+ * @return {*} A clone of the input value.
+ * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
  */
-goog.inherits = function(childCtor, parentCtor) {
-  /** @constructor */
-  function tempCtor() {};
-  tempCtor.prototype = parentCtor.prototype;
-  childCtor.superClass_ = parentCtor.prototype;
-  childCtor.prototype = new tempCtor();
-  childCtor.prototype.constructor = childCtor;
+goog.cloneObject = function(obj) {
+  var type = goog.typeOf(obj);
+  if (type == 'object' || type == 'array') {
+    if (typeof obj.clone === 'function') {
+      return obj.clone();
+    }
+    var clone = type == 'array' ? [] : {};
+    for (var key in obj) {
+      clone[key] = goog.cloneObject(obj[key]);
+    }
+    return clone;
+  }
+
+  return obj;
 };
 
 
 /**
- * Call up to the superclass.
- *
- * If this is called from a constructor, then this calls the superclass
- * contructor with arguments 1-N.
+ * A native implementation of goog.bind.
+ * @param {?function(this:T, ...)} fn A function to partially apply.
+ * @param {T} selfObj Specifies the object which this should point to when the
+ *     function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ *     function.
+ * @return {!Function} A partially-applied form of the function goog.bind() was
+ *     invoked as a method of.
+ * @template T
+ * @private
+ */
+goog.bindNative_ = function(fn, selfObj, var_args) {
+  return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
+};
+
+
+/**
+ * A pure-JS implementation of goog.bind.
+ * @param {?function(this:T, ...)} fn A function to partially apply.
+ * @param {T} selfObj Specifies the object which this should point to when the
+ *     function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ *     function.
+ * @return {!Function} A partially-applied form of the function goog.bind() was
+ *     invoked as a method of.
+ * @template T
+ * @private
+ */
+goog.bindJs_ = function(fn, selfObj, var_args) {
+  if (!fn) {
+    throw new Error();
+  }
+
+  if (arguments.length > 2) {
+    var boundArgs = Array.prototype.slice.call(arguments, 2);
+    return function() {
+      // Prepend the bound arguments to the current arguments.
+      var newArgs = Array.prototype.slice.call(arguments);
+      Array.prototype.unshift.apply(newArgs, boundArgs);
+      return fn.apply(selfObj, newArgs);
+    };
+
+  } else {
+    return function() {
+      return fn.apply(selfObj, arguments);
+    };
+  }
+};
+
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of this 'pre-specified'.
  *
- * If this is called from a prototype method, then you must pass
- * the name of the method as the second argument to this function. If
- * you do not, you will get a runtime error. This calls the superclass'
- * method with arguments 2-N.
+ * Remaining arguments specified at call-time are appended to the pre-specified
+ * ones.
  *
- * This function only works if you use goog.inherits to express
- * inheritance relationships between your classes.
+ * Also see: {@link #partial}.
  *
- * This function is a compiler primitive. At compile-time, the
- * compiler will do macro expansion to remove a lot of
- * the extra overhead that this function introduces. The compiler
- * will also enforce a lot of the assumptions that this function
- * makes, and treat it as a compiler error if you break them.
+ * Usage:
+ * <pre>var barMethBound = goog.bind(myFunction, myObj, 'arg1', 'arg2');
+ * barMethBound('arg3', 'arg4');</pre>
  *
- * @param {!Object} me Should always be "this".
- * @param {*=} opt_methodName The method name if calling a super method.
- * @param {...*} var_args The rest of the arguments.
- * @return {*} The return value of the superclass method.
+ * @param {?function(this:T, ...)} fn A function to partially apply.
+ * @param {T} selfObj Specifies the object which this should point to when the
+ *     function is run.
+ * @param {...*} var_args Additional arguments that are partially applied to the
+ *     function.
+ * @return {!Function} A partially-applied form of the function goog.bind() was
+ *     invoked as a method of.
+ * @template T
+ * @suppress {deprecated} See above.
  */
-goog.base = function(me, opt_methodName, var_args) {
-  var caller = arguments.callee.caller;
-  if (caller.superClass_) {
-    // This is a constructor. Call the superclass constructor.
-    return caller.superClass_.constructor.apply(
-        me, Array.prototype.slice.call(arguments, 1));
+goog.bind = function(fn, selfObj, var_args) {
+  // TODO(nicksantos): narrow the type signature.
+  if (Function.prototype.bind &&
+      // NOTE(nicksantos): Somebody pulled base.js into the default Chrome
+      // extension environment. This means that for Chrome extensions, they get
+      // the implementation of Function.prototype.bind that calls goog.bind
+      // instead of the native one. Even worse, we don't want to introduce a
+      // circular dependency between goog.bind and Function.prototype.bind, so
+      // we have to hack this to make sure it works correctly.
+      Function.prototype.bind.toString().indexOf('native code') != -1) {
+    goog.bind = goog.bindNative_;
+  } else {
+    goog.bind = goog.bindJs_;
   }
+  return goog.bind.apply(null, arguments);
+};
 
-  var args = Array.prototype.slice.call(arguments, 2);
-  var foundCaller = false;
-  for (var ctor = me.constructor;
-       ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
-    if (ctor.prototype[opt_methodName] === caller) {
-      foundCaller = true;
-    } else if (foundCaller) {
-      return ctor.prototype[opt_methodName].apply(me, args);
-    }
-  }
 
-  // If we did not find the caller in the prototype chain,
-  // then one of two things happened:
-  // 1) The caller is an instance method.
-  // 2) This method was not called by the right caller.
-  if (me[opt_methodName] === caller) {
-    return me.constructor.prototype[opt_methodName].apply(me, args);
-  } else {
-    throw Error(
-        'goog.base called from a method of one name ' +
-        'to a method of a different name');
-  }
+/**
+ * Like goog.bind(), except that a 'this object' is not required. Useful when
+ * the target function is already bound.
+ *
+ * Usage:
+ * var g = goog.partial(f, arg1, arg2);
+ * g(arg3, arg4);
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {...*} var_args Additional arguments that are partially applied to fn.
+ * @return {!Function} A partially-applied form of the function goog.partial()
+ *     was invoked as a method of.
+ */
+goog.partial = function(fn, var_args) {
+  var args = Array.prototype.slice.call(arguments, 1);
+  return function() {
+    // Clone the array (with slice()) and append additional arguments
+    // to the existing arguments.
+    var newArgs = args.slice();
+    newArgs.push.apply(newArgs, arguments);
+    return fn.apply(/** @type {?} */ (this), newArgs);
+  };
 };
 
 
 /**
- * Allow for aliasing within scope functions.  This function exists for
- * uncompiled code - in compiled code the calls will be inlined and the
- * aliases applied.  In uncompiled code the function is simply run since the
- * aliases as written are valid JavaScript.
- * @param {function()} fn Function to call.  This function can contain aliases
- *     to namespaces (e.g. "var dom = goog.dom") or classes
- *    (e.g. "var Timer = goog.Timer").
+ * Copies all the members of a source object to a target object. This method
+ * does not work on all browsers for all objects that contain keys such as
+ * toString or hasOwnProperty. Use goog.object.extend for this purpose.
+ * @param {Object} target Target.
+ * @param {Object} source Source.
  */
-goog.scope = function(fn) {
-  fn.call(goog.global);
+goog.mixin = function(target, source) {
+  for (var x in source) {
+    target[x] = source[x];
+  }
+
+  // For IE7 or lower, the for-in-loop does not contain any properties that are
+  // not enumerable on the prototype object (for example, isPrototypeOf from
+  // Object.prototype) but also it will not include 'replace' on objects that
+  // extend String and change 'replace' (not that it is common for anyone to
+  // extend anything except Object).
 };
 
 
+/**
+ * @return {number} An integer value representing the number of milliseconds
+ *     between midnight, January 1, 1970 and the current time.
+ */
+goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
+             // Unary plus operator converts its operand to a number which in
+             // the case of
+             // a date is done by calling getTime().
+             return +new Date();
+           });
+
+
+/**
+ * Evals JavaScript in the global scope.  In IE this uses execScript, other
+ * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
+ * global scope (for example, in Safari), appends a script tag instead.
+ * Throws an exception if neither execScript or eval is defined.
+ * @param {string} script JavaScript string.
+ */
+goog.globalEval = function(script) {
+  if (goog.global.execScript) {
+    goog.global.execScript(script, 'JavaScript');
+  } else if (goog.global.eval) {
+    // Test to see if eval works
+    if (goog.evalWorksForGlobals_ == null) {
+      try {
+        goog.global.eval('var _evalTest_ = 1;');
+      } catch (ignore) {
+      }
+      if (typeof goog.global['_evalTest_'] != 'undefined') {
+        try {
+          delete goog.global['_evalTest_'];
+        } catch (ignore) {
+          // Microsoft edge fails the deletion above in strict mode.
+        }
+        goog.evalWorksForGlobals_ = true;
+      } else {
+        goog.evalWorksForGlobals_ = false;
+      }
+    }
+
+    if (goog.evalWorksForGlobals_) {
+      goog.global.eval(script);
+    } else {
+      /** @type {!Document} */
+      var doc = goog.global.document;
+      var scriptElt =
+          /** @type {!HTMLScriptElement} */ (doc.createElement('SCRIPT'));
+      scriptElt.type = 'text/javascript';
+      scriptElt.defer = false;
+      // Note(user): can't use .innerHTML since "t('<test>')" will fail and
+      // .text doesn't work in Safari 2.  Therefore we append a text node.
+      scriptElt.appendChild(doc.createTextNode(script));
+      doc.head.appendChild(scriptElt);
+      doc.head.removeChild(scriptElt);
+    }
+  } else {
+    throw new Error('goog.globalEval not available');
+  }
+};
+
+
+/**
+ * Indicates whether or not we can call 'eval' directly to eval code in the
+ * global scope. Set to a Boolean by the first call to goog.globalEval (which
+ * empirically tests whether eval works for globals). @see goog.globalEval
+ * @type {?boolean}
+ * @private
+ */
+goog.evalWorksForGlobals_ = null;
+
+
+/**
+ * Optional map of CSS class names to obfuscated names used with
+ * goog.getCssName().
+ * @private {!Object<string, string>|undefined}
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMapping_;
+
+
+/**
+ * Optional obfuscation style for CSS class names. Should be set to either
+ * 'BY_WHOLE' or 'BY_PART' if defined.
+ * @type {string|undefined}
+ * @private
+ * @see goog.setCssNameMapping
+ */
+goog.cssNameMappingStyle_;
+
+
+
+/**
+ * A hook for modifying the default behavior goog.getCssName. The function
+ * if present, will receive the standard output of the goog.getCssName as
+ * its input.
+ *
+ * @type {(function(string):string)|undefined}
+ */
+goog.global.CLOSURE_CSS_NAME_MAP_FN;
+
+
+/**
+ * Handles strings that are intended to be used as CSS class names.
+ *
+ * This function works in tandem with @see goog.setCssNameMapping.
+ *
+ * Without any mapping set, the arguments are simple joined with a hyphen and
+ * passed through unaltered.
+ *
+ * When there is a mapping, there are two possible styles in which these
+ * mappings are used. In the BY_PART style, each part (i.e. in between hyphens)
+ * of the passed in css name is rewritten according to the map. In the BY_WHOLE
+ * style, the full css name is looked up in the map directly. If a rewrite is
+ * not specified by the map, the compiler will output a warning.
+ *
+ * When the mapping is passed to the compiler, it will replace calls to
+ * goog.getCssName with the strings from the mapping, e.g.
+ *     var x = goog.getCssName('foo');
+ *     var y = goog.getCssName(this.baseClass, 'active');
+ *  becomes:
+ *     var x = 'foo';
+ *     var y = this.baseClass + '-active';
+ *
+ * If one argument is passed it will be processed, if two are passed only the
+ * modifier will be processed, as it is assumed the first argument was generated
+ * as a result of calling goog.getCssName.
+ *
+ * @param {string} className The class name.
+ * @param {string=} opt_modifier A modifier to be appended to the class name.
+ * @return {string} The class name or the concatenation of the class name and
+ *     the modifier.
+ */
+goog.getCssName = function(className, opt_modifier) {
+  // String() is used for compatibility with compiled soy where the passed
+  // className can be non-string objects.
+  if (String(className).charAt(0) == '.') {
+    throw new Error(
+        'className passed in goog.getCssName must not start with ".".' +
+        ' You passed: ' + className);
+  }
+
+  var getMapping = function(cssName) {
+    return goog.cssNameMapping_[cssName] || cssName;
+  };
+
+  var renameByParts = function(cssName) {
+    // Remap all the parts individually.
+    var parts = cssName.split('-');
+    var mapped = [];
+    for (var i = 0; i < parts.length; i++) {
+      mapped.push(getMapping(parts[i]));
+    }
+    return mapped.join('-');
+  };
+
+  var rename;
+  if (goog.cssNameMapping_) {
+    rename =
+        goog.cssNameMappingStyle_ == 'BY_WHOLE' ? getMapping : renameByParts;
+  } else {
+    rename = function(a) {
+      return a;
+    };
+  }
+
+  var result =
+      opt_modifier ? className + '-' + rename(opt_modifier) : rename(className);
+
+  // The special CLOSURE_CSS_NAME_MAP_FN allows users to specify further
+  // processing of the class name.
+  if (goog.global.CLOSURE_CSS_NAME_MAP_FN) {
+    return goog.global.CLOSURE_CSS_NAME_MAP_FN(result);
+  }
+
+  return result;
+};
+
+
+/**
+ * Sets the map to check when returning a value from goog.getCssName(). Example:
+ * <pre>
+ * goog.setCssNameMapping({
+ *   "goog": "a",
+ *   "disabled": "b",
+ * });
+ *
+ * var x = goog.getCssName('goog');
+ * // The following evaluates to: "a a-b".
+ * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
+ * </pre>
+ * When declared as a map of string literals to string literals, the JSCompiler
+ * will replace all calls to goog.getCssName() using the supplied map if the
+ * --process_closure_primitives flag is set.
+ *
+ * @param {!Object} mapping A map of strings to strings where keys are possible
+ *     arguments to goog.getCssName() and values are the corresponding values
+ *     that should be returned.
+ * @param {string=} opt_style The style of css name mapping. There are two valid
+ *     options: 'BY_PART', and 'BY_WHOLE'.
+ * @see goog.getCssName for a description.
+ */
+goog.setCssNameMapping = function(mapping, opt_style) {
+  goog.cssNameMapping_ = mapping;
+  goog.cssNameMappingStyle_ = opt_style;
+};
+
+
+/**
+ * To use CSS renaming in compiled mode, one of the input files should have a
+ * call to goog.setCssNameMapping() with an object literal that the JSCompiler
+ * can extract and use to replace all calls to goog.getCssName(). In uncompiled
+ * mode, JavaScript code should be loaded before this base.js file that declares
+ * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
+ * to ensure that the mapping is loaded before any calls to goog.getCssName()
+ * are made in uncompiled mode.
+ *
+ * A hook for overriding the CSS name mapping.
+ * @type {!Object<string, string>|undefined}
+ */
+goog.global.CLOSURE_CSS_NAME_MAPPING;
+
+
+if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
+  // This does not call goog.setCssNameMapping() because the JSCompiler
+  // requires that goog.setCssNameMapping() be called with an object literal.
+  goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
+}
+
+
+/**
+ * Gets a localized message.
+ *
+ * This function is a compiler primitive. If you give the compiler a localized
+ * message bundle, it will replace the string at compile-time with a localized
+ * version, and expand goog.getMsg call to a concatenated string.
+ *
+ * Messages must be initialized in the form:
+ * <code>
+ * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
+ * </code>
+ *
+ * This function produces a string which should be treated as plain text. Use
+ * {@link goog.html.SafeHtmlFormatter} in conjunction with goog.getMsg to
+ * produce SafeHtml.
+ *
+ * @param {string} str Translatable string, places holders in the form {$foo}.
+ * @param {Object<string, string>=} opt_values Maps place holder name to value.
+ * @return {string} message with placeholders filled.
+ */
+goog.getMsg = function(str, opt_values) {
+  if (opt_values) {
+    str = str.replace(/\{\$([^}]+)}/g, function(match, key) {
+      return (opt_values != null && key in opt_values) ? opt_values[key] :
+                                                         match;
+    });
+  }
+  return str;
+};
+
+
+/**
+ * Gets a localized message. If the message does not have a translation, gives a
+ * fallback message.
+ *
+ * This is useful when introducing a new message that has not yet been
+ * translated into all languages.
+ *
+ * This function is a compiler primitive. Must be used in the form:
+ * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
+ * where MSG_A and MSG_B were initialized with goog.getMsg.
+ *
+ * @param {string} a The preferred message.
+ * @param {string} b The fallback message.
+ * @return {string} The best translated message.
+ */
+goog.getMsgWithFallback = function(a, b) {
+  return a;
+};
+
+
+/**
+ * Exposes an unobfuscated global namespace path for the given object.
+ * Note that fields of the exported object *will* be obfuscated, unless they are
+ * exported in turn via this function or goog.exportProperty.
+ *
+ * Also handy for making public items that are defined in anonymous closures.
+ *
+ * ex. goog.exportSymbol('public.path.Foo', Foo);
+ *
+ * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction);
+ *     public.path.Foo.staticFunction();
+ *
+ * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
+ *                       Foo.prototype.myMethod);
+ *     new public.path.Foo().myMethod();
+ *
+ * @param {string} publicPath Unobfuscated name to export.
+ * @param {*} object Object the name should point to.
+ * @param {Object=} opt_objectToExportTo The object to add the path to; default
+ *     is goog.global.
+ */
+goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
+  goog.exportPath_(publicPath, object, opt_objectToExportTo);
+};
+
+
+/**
+ * Exports a property unobfuscated into the object's namespace.
+ * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
+ * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
+ * @param {Object} object Object whose static property is being exported.
+ * @param {string} publicName Unobfuscated name to export.
+ * @param {*} symbol Object the name should point to.
+ */
+goog.exportProperty = function(object, publicName, symbol) {
+  object[publicName] = symbol;
+};
+
+
+/**
+ * Inherit the prototype methods from one constructor into another.
+ *
+ * Usage:
+ * <pre>
+ * function ParentClass(a, b) { }
+ * ParentClass.prototype.foo = function(a) { };
+ *
+ * function ChildClass(a, b, c) {
+ *   ChildClass.base(this, 'constructor', a, b);
+ * }
+ * goog.inherits(ChildClass, ParentClass);
+ *
+ * var child = new ChildClass('a', 'b', 'see');
+ * child.foo(); // This works.
+ * </pre>
+ *
+ * @param {!Function} childCtor Child class.
+ * @param {!Function} parentCtor Parent class.
+ * @suppress {strictMissingProperties} superClass_ and base is not defined on
+ *    Function.
+ */
+goog.inherits = function(childCtor, parentCtor) {
+  /** @constructor */
+  function tempCtor() {}
+  tempCtor.prototype = parentCtor.prototype;
+  childCtor.superClass_ = parentCtor.prototype;
+  childCtor.prototype = new tempCtor();
+  /** @override */
+  childCtor.prototype.constructor = childCtor;
+
+  /**
+   * Calls superclass constructor/method.
+   *
+   * This function is only available if you use goog.inherits to
+   * express inheritance relationships between classes.
+   *
+   * NOTE: This is a replacement for goog.base and for superClass_
+   * property defined in childCtor.
+   *
+   * @param {!Object} me Should always be "this".
+   * @param {string} methodName The method name to call. Calling
+   *     superclass constructor can be done with the special string
+   *     'constructor'.
+   * @param {...*} var_args The arguments to pass to superclass
+   *     method/constructor.
+   * @return {*} The return value of the superclass method/constructor.
+   */
+  childCtor.base = function(me, methodName, var_args) {
+    // Copying using loop to avoid deop due to passing arguments object to
+    // function. This is faster in many JS engines as of late 2014.
+    var args = new Array(arguments.length - 2);
+    for (var i = 2; i < arguments.length; i++) {
+      args[i - 2] = arguments[i];
+    }
+    return parentCtor.prototype[methodName].apply(me, args);
+  };
+};
+
+
+/**
+ * Call up to the superclass.
+ *
+ * If this is called from a constructor, then this calls the superclass
+ * constructor with arguments 1-N.
+ *
+ * If this is called from a prototype method, then you must pass the name of the
+ * method as the second argument to this function. If you do not, you will get a
+ * runtime error. This calls the superclass' method with arguments 2-N.
+ *
+ * This function only works if you use goog.inherits to express inheritance
+ * relationships between your classes.
+ *
+ * This function is a compiler primitive. At compile-time, the compiler will do
+ * macro expansion to remove a lot of the extra overhead that this function
+ * introduces. The compiler will also enforce a lot of the assumptions that this
+ * function makes, and treat it as a compiler error if you break them.
+ *
+ * @param {!Object} me Should always be "this".
+ * @param {*=} opt_methodName The method name if calling a super method.
+ * @param {...*} var_args The rest of the arguments.
+ * @return {*} The return value of the superclass method.
+ * @suppress {es5Strict} This method can not be used in strict mode, but
+ *     all Closure Library consumers must depend on this file.
+ * @deprecated goog.base is not strict mode compatible.  Prefer the static
+ *     "base" method added to the constructor by goog.inherits
+ *     or ES6 classes and the "super" keyword.
+ */
+goog.base = function(me, opt_methodName, var_args) {
+  var caller = arguments.callee.caller;
+
+  if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
+    throw new Error(
+        'arguments.caller not defined.  goog.base() cannot be used ' +
+        'with strict mode code. See ' +
+        'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
+  }
+
+  if (typeof caller.superClass_ !== 'undefined') {
+    // Copying using loop to avoid deop due to passing arguments object to
+    // function. This is faster in many JS engines as of late 2014.
+    var ctorArgs = new Array(arguments.length - 1);
+    for (var i = 1; i < arguments.length; i++) {
+      ctorArgs[i - 1] = arguments[i];
+    }
+    // This is a constructor. Call the superclass constructor.
+    return /** @type {!Function} */ (caller.superClass_)
+        .constructor.apply(me, ctorArgs);
+  }
+
+  if (typeof opt_methodName != 'string' && typeof opt_methodName != 'symbol') {
+    throw new Error(
+        'method names provided to goog.base must be a string or a symbol');
+  }
+
+  // Copying using loop to avoid deop due to passing arguments object to
+  // function. This is faster in many JS engines as of late 2014.
+  var args = new Array(arguments.length - 2);
+  for (var i = 2; i < arguments.length; i++) {
+    args[i - 2] = arguments[i];
+  }
+  var foundCaller = false;
+  for (var ctor = me.constructor; ctor;
+       ctor = ctor.superClass_ && ctor.superClass_.constructor) {
+    if (ctor.prototype[opt_methodName] === caller) {
+      foundCaller = true;
+    } else if (foundCaller) {
+      return ctor.prototype[opt_methodName].apply(me, args);
+    }
+  }
+
+  // If we did not find the caller in the prototype chain, then one of two
+  // things happened:
+  // 1) The caller is an instance method.
+  // 2) This method was not called by the right caller.
+  if (me[opt_methodName] === caller) {
+    return me.constructor.prototype[opt_methodName].apply(me, args);
+  } else {
+    throw new Error(
+        'goog.base called from a method of one name ' +
+        'to a method of a different name');
+  }
+};
+
+
+/**
+ * Allow for aliasing within scope functions.  This function exists for
+ * uncompiled code - in compiled code the calls will be inlined and the aliases
+ * applied.  In uncompiled code the function is simply run since the aliases as
+ * written are valid JavaScript.
+ *
+ *
+ * @param {function()} fn Function to call.  This function can contain aliases
+ *     to namespaces (e.g. "var dom = goog.dom") or classes
+ *     (e.g. "var Timer = goog.Timer").
+ */
+goog.scope = function(fn) {
+  if (goog.isInModuleLoader_()) {
+    throw new Error('goog.scope is not supported within a module.');
+  }
+  fn.call(goog.global);
+};
+
+
+/*
+ * To support uncompiled, strict mode bundles that use eval to divide source
+ * like so:
+ *    eval('someSource;//# sourceUrl sourcefile.js');
+ * We need to export the globally defined symbols "goog" and "COMPILED".
+ * Exporting "goog" breaks the compiler optimizations, so we required that
+ * be defined externally.
+ * NOTE: We don't use goog.exportSymbol here because we don't want to trigger
+ * extern generation when that compiler option is enabled.
+ */
+if (!COMPILED) {
+  goog.global['COMPILED'] = COMPILED;
+}
+
+
+//==============================================================================
+// goog.defineClass implementation
+//==============================================================================
+
+
+/**
+ * Creates a restricted form of a Closure "class":
+ *   - from the compiler's perspective, the instance returned from the
+ *     constructor is sealed (no new properties may be added).  This enables
+ *     better checks.
+ *   - the compiler will rewrite this definition to a form that is optimal
+ *     for type checking and optimization (initially this will be a more
+ *     traditional form).
+ *
+ * @param {Function} superClass The superclass, Object or null.
+ * @param {goog.defineClass.ClassDescriptor} def
+ *     An object literal describing
+ *     the class.  It may have the following properties:
+ *     "constructor": the constructor function
+ *     "statics": an object literal containing methods to add to the constructor
+ *        as "static" methods or a function that will receive the constructor
+ *        function as its only parameter to which static properties can
+ *        be added.
+ *     all other properties are added to the prototype.
+ * @return {!Function} The class constructor.
+ */
+goog.defineClass = function(superClass, def) {
+  // TODO(johnlenz): consider making the superClass an optional parameter.
+  var constructor = def.constructor;
+  var statics = def.statics;
+  // Wrap the constructor prior to setting up the prototype and static methods.
+  if (!constructor || constructor == Object.prototype.constructor) {
+    constructor = function() {
+      throw new Error(
+          'cannot instantiate an interface (no constructor defined).');
+    };
+  }
+
+  var cls = goog.defineClass.createSealingConstructor_(constructor, superClass);
+  if (superClass) {
+    goog.inherits(cls, superClass);
+  }
+
+  // Remove all the properties that should not be copied to the prototype.
+  delete def.constructor;
+  delete def.statics;
+
+  goog.defineClass.applyProperties_(cls.prototype, def);
+  if (statics != null) {
+    if (statics instanceof Function) {
+      statics(cls);
+    } else {
+      goog.defineClass.applyProperties_(cls, statics);
+    }
+  }
+
+  return cls;
+};
+
+
+/**
+ * @typedef {{
+ *   constructor: (!Function|undefined),
+ *   statics: (Object|undefined|function(Function):void)
+ * }}
+ */
+goog.defineClass.ClassDescriptor;
+
+
+/**
+ * @define {boolean} Whether the instances returned by goog.defineClass should
+ *     be sealed when possible.
+ *
+ * When sealing is disabled the constructor function will not be wrapped by
+ * goog.defineClass, making it incompatible with ES6 class methods.
+ */
+goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
+
+
+/**
+ * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is
+ * defined, this function will wrap the constructor in a function that seals the
+ * results of the provided constructor function.
+ *
+ * @param {!Function} ctr The constructor whose results maybe be sealed.
+ * @param {Function} superClass The superclass constructor.
+ * @return {!Function} The replacement constructor.
+ * @private
+ */
+goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
+  if (!goog.defineClass.SEAL_CLASS_INSTANCES) {
+    // Do now wrap the constructor when sealing is disabled. Angular code
+    // depends on this for injection to work properly.
+    return ctr;
+  }
+
+  // Compute whether the constructor is sealable at definition time, rather
+  // than when the instance is being constructed.
+  var superclassSealable = !goog.defineClass.isUnsealable_(superClass);
+
+  /**
+   * @this {Object}
+   * @return {?}
+   */
+  var wrappedCtr = function() {
+    // Don't seal an instance of a subclass when it calls the constructor of
+    // its super class as there is most likely still setup to do.
+    var instance = ctr.apply(this, arguments) || this;
+    instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_];
+
+    if (this.constructor === wrappedCtr && superclassSealable &&
+        Object.seal instanceof Function) {
+      Object.seal(instance);
+    }
+    return instance;
+  };
+
+  return wrappedCtr;
+};
+
+
+/**
+ * @param {Function} ctr The constructor to test.
+ * @return {boolean} Whether the constructor has been tagged as unsealable
+ *     using goog.tagUnsealableClass.
+ * @private
+ */
+goog.defineClass.isUnsealable_ = function(ctr) {
+  return ctr && ctr.prototype &&
+      ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_];
+};
+
+
+// TODO(johnlenz): share these values with the goog.object
+/**
+ * The names of the fields that are defined on Object.prototype.
+ * @type {!Array<string>}
+ * @private
+ * @const
+ */
+goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
+  'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+  'toLocaleString', 'toString', 'valueOf'
+];
+
+
+// TODO(johnlenz): share this function with the goog.object
+/**
+ * @param {!Object} target The object to add properties to.
+ * @param {!Object} source The object to copy properties from.
+ * @private
+ */
+goog.defineClass.applyProperties_ = function(target, source) {
+  // TODO(johnlenz): update this to support ES5 getters/setters
+
+  var key;
+  for (key in source) {
+    if (Object.prototype.hasOwnProperty.call(source, key)) {
+      target[key] = source[key];
+    }
+  }
+
+  // For IE the for-in-loop does not contain any properties that are not
+  // enumerable on the prototype object (for example isPrototypeOf from
+  // Object.prototype) and it will also not include 'replace' on objects that
+  // extend String and change 'replace' (not that it is common for anyone to
+  // extend anything except Object).
+  for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
+    key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i];
+    if (Object.prototype.hasOwnProperty.call(source, key)) {
+      target[key] = source[key];
+    }
+  }
+};
+
+
+/**
+ * Sealing classes breaks the older idiom of assigning properties on the
+ * prototype rather than in the constructor. As such, goog.defineClass
+ * must not seal subclasses of these old-style classes until they are fixed.
+ * Until then, this marks a class as "broken", instructing defineClass
+ * not to seal subclasses.
+ * @param {!Function} ctr The legacy constructor to tag as unsealable.
+ */
+goog.tagUnsealableClass = function(ctr) {
+  if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) {
+    ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true;
+  }
+};
+
+
+/**
+ * Name for unsealable tag property.
+ * @const @private {string}
+ */
+goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';
+
+
+// There's a bug in the compiler where without collapse properties the
+// Closure namespace defines do not guard code correctly. To help reduce code
+// size also check for !COMPILED even though it redundant until this is fixed.
+if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
+
+  /**
+   * Tries to detect whether is in the context of an HTML document.
+   * @return {boolean} True if it looks like HTML document.
+   * @private
+   */
+  goog.inHtmlDocument_ = function() {
+    /** @type {!Document} */
+    var doc = goog.global.document;
+    return doc != null && 'write' in doc;  // XULDocument misses write.
+  };
+
+
+  /**
+   * We'd like to check for if the document readyState is 'loading'; however
+   * there are bugs on IE 10 and below where the readyState being anything other
+   * than 'complete' is not reliable.
+   * @return {boolean}
+   * @private
+   */
+  goog.isDocumentLoading_ = function() {
+    // attachEvent is available on IE 6 thru 10 only, and thus can be used to
+    // detect those browsers.
+    /** @type {!HTMLDocument} */
+    var doc = goog.global.document;
+    return doc.attachEvent ? doc.readyState != 'complete' :
+                             doc.readyState == 'loading';
+  };
+
+
+  /**
+   * Tries to detect the base path of base.js script that bootstraps Closure.
+   * @private
+   */
+  goog.findBasePath_ = function() {
+    if (goog.isDef(goog.global.CLOSURE_BASE_PATH) &&
+        // Anti DOM-clobbering runtime check (b/37736576).
+        goog.isString(goog.global.CLOSURE_BASE_PATH)) {
+      goog.basePath = goog.global.CLOSURE_BASE_PATH;
+      return;
+    } else if (!goog.inHtmlDocument_()) {
+      return;
+    }
+    /** @type {!Document} */
+    var doc = goog.global.document;
+    // If we have a currentScript available, use it exclusively.
+    var currentScript = doc.currentScript;
+    if (currentScript) {
+      var scripts = [currentScript];
+    } else {
+      var scripts = doc.getElementsByTagName('SCRIPT');
+    }
+    // Search backwards since the current script is in almost all cases the one
+    // that has base.js.
+    for (var i = scripts.length - 1; i >= 0; --i) {
+      var script = /** @type {!HTMLScriptElement} */ (scripts[i]);
+      var src = script.src;
+      var qmark = src.lastIndexOf('?');
+      var l = qmark == -1 ? src.length : qmark;
+      if (src.substr(l - 7, 7) == 'base.js') {
+        goog.basePath = src.substr(0, l - 7);
+        return;
+      }
+    }
+  };
+
+  goog.findBasePath_();
+
+  /** @struct @constructor @final */
+  goog.Transpiler = function() {
+    /** @private {?Object<string, boolean>} */
+    this.requiresTranspilation_ = null;
+    /** @private {string} */
+    this.transpilationTarget_ = goog.TRANSPILE_TO_LANGUAGE;
+  };
+
+
+  /**
+   * Returns a newly created map from language mode string to a boolean
+   * indicating whether transpilation should be done for that mode as well as
+   * the highest level language that this environment supports.
+   *
+   * Guaranteed invariant:
+   * For any two modes, l1 and l2 where l2 is a newer mode than l1,
+   * `map[l1] == true` implies that `map[l2] == true`.
+   *
+   * Note this method is extracted and used elsewhere, so it cannot rely on
+   * anything external (it should easily be able to be transformed into a
+   * standalone, top level function).
+   *
+   * @private
+   * @return {{
+   *   target: string,
+   *   map: !Object<string, boolean>
+   * }}
+   */
+  goog.Transpiler.prototype.createRequiresTranspilation_ = function() {
+    var transpilationTarget = 'es3';
+    var /** !Object<string, boolean> */ requiresTranspilation = {'es3': false};
+    var transpilationRequiredForAllLaterModes = false;
+
+    /**
+     * Adds an entry to requiresTranspliation for the given language mode.
+     *
+     * IMPORTANT: Calls must be made in order from oldest to newest language
+     * mode.
+     * @param {string} modeName
+     * @param {function(): boolean} isSupported Returns true if the JS engine
+     *     supports the given mode.
+     */
+    function addNewerLanguageTranspilationCheck(modeName, isSupported) {
+      if (transpilationRequiredForAllLaterModes) {
+        requiresTranspilation[modeName] = true;
+      } else if (isSupported()) {
+        transpilationTarget = modeName;
+        requiresTranspilation[modeName] = false;
+      } else {
+        requiresTranspilation[modeName] = true;
+        transpilationRequiredForAllLaterModes = true;
+      }
+    }
+
+    /**
+     * Does the given code evaluate without syntax errors and return a truthy
+     * result?
+     */
+    function /** boolean */ evalCheck(/** string */ code) {
+      try {
+        return !!eval(code);
+      } catch (ignored) {
+        return false;
+      }
+    }
+
+    var userAgent = goog.global.navigator && goog.global.navigator.userAgent ?
+        goog.global.navigator.userAgent :
+        '';
+
+    // Identify ES3-only browsers by their incorrect treatment of commas.
+    addNewerLanguageTranspilationCheck('es5', function() {
+      return evalCheck('[1,].length==1');
+    });
+    addNewerLanguageTranspilationCheck('es6', function() {
+      // Edge has a non-deterministic (i.e., not reproducible) bug with ES6:
+      // https://github.com/Microsoft/ChakraCore/issues/1496.
+      var re = /Edge\/(\d+)(\.\d)*/i;
+      var edgeUserAgent = userAgent.match(re);
+      if (edgeUserAgent && Number(edgeUserAgent[1]) < 15) {
+        return false;
+      }
+      // Test es6: [FF50 (?), Edge 14 (?), Chrome 50]
+      //   (a) default params (specifically shadowing locals),
+      //   (b) destructuring, (c) block-scoped functions,
+      //   (d) for-of (const), (e) new.target/Reflect.construct
+      var es6fullTest =
+          'class X{constructor(){if(new.target!=String)throw 1;this.x=42}}' +
+          'let q=Reflect.construct(X,[],String);if(q.x!=42||!(q instanceof ' +
+          'String))throw 1;for(const a of[2,3]){if(a==2)continue;function ' +
+          'f(z={a}){let a=0;return z.a}{function f(){return 0;}}return f()' +
+          '==3}';
+
+      return evalCheck('(()=>{"use strict";' + es6fullTest + '})()');
+    });
+    // TODO(joeltine): Remove es6-impl references for b/31340605.
+    // Consider es6-impl (widely-implemented es6 features) to be supported
+    // whenever es6 is supported. Technically es6-impl is a lower level of
+    // support than es6, but we don't have tests specifically for it.
+    addNewerLanguageTranspilationCheck('es6-impl', function() {
+      return true;
+    });
+    // ** and **= are the only new features in 'es7'
+    addNewerLanguageTranspilationCheck('es7', function() {
+      return evalCheck('2 ** 2 == 4');
+    });
+    // async functions are the only new features in 'es8'
+    addNewerLanguageTranspilationCheck('es8', function() {
+      return evalCheck('async () => 1, true');
+    });
+    addNewerLanguageTranspilationCheck('es9', function() {
+      return evalCheck('({...rest} = {}), true');
+    });
+    addNewerLanguageTranspilationCheck('es_next', function() {
+      return false;  // assume it always need to transpile
+    });
+    return {target: transpilationTarget, map: requiresTranspilation};
+  };
+
+
+  /**
+   * Determines whether the given language needs to be transpiled.
+   * @param {string} lang
+   * @param {string|undefined} module
+   * @return {boolean}
+   */
+  goog.Transpiler.prototype.needsTranspile = function(lang, module) {
+    if (goog.TRANSPILE == 'always') {
+      return true;
+    } else if (goog.TRANSPILE == 'never') {
+      return false;
+    } else if (!this.requiresTranspilation_) {
+      var obj = this.createRequiresTranspilation_();
+      this.requiresTranspilation_ = obj.map;
+      this.transpilationTarget_ = this.transpilationTarget_ || obj.target;
+    }
+    if (lang in this.requiresTranspilation_) {
+      if (this.requiresTranspilation_[lang]) {
+        return true;
+      } else if (
+          goog.inHtmlDocument_() && module == 'es6' &&
+          !('noModule' in goog.global.document.createElement('script'))) {
+        return true;
+      } else {
+        return false;
+      }
+    } else {
+      throw new Error('Unknown language mode: ' + lang);
+    }
+  };
+
+
+  /**
+   * Lazily retrieves the transpiler and applies it to the source.
+   * @param {string} code JS code.
+   * @param {string} path Path to the code.
+   * @return {string} The transpiled code.
+   */
+  goog.Transpiler.prototype.transpile = function(code, path) {
+    // TODO(johnplaisted): We should delete goog.transpile_ and just have this
+    // function. But there's some compile error atm where goog.global is being
+    // stripped incorrectly without this.
+    return goog.transpile_(code, path, this.transpilationTarget_);
+  };
+
+
+  /** @private @final {!goog.Transpiler} */
+  goog.transpiler_ = new goog.Transpiler();
+
+  /**
+   * Rewrites closing script tags in input to avoid ending an enclosing script
+   * tag.
+   *
+   * @param {string} str
+   * @return {string}
+   * @private
+   */
+  goog.protectScriptTag_ = function(str) {
+    return str.replace(/<\/(SCRIPT)/ig, '\\x3c/$1');
+  };
+
+
+  /**
+   * A debug loader is responsible for downloading and executing javascript
+   * files in an unbundled, uncompiled environment.
+   *
+   * This can be custimized via the setDependencyFactory method, or by
+   * CLOSURE_IMPORT_SCRIPT/CLOSURE_LOAD_FILE_SYNC.
+   *
+   * @struct @constructor @final @private
+   */
+  goog.DebugLoader_ = function() {
+    /** @private @const {!Object<string, !goog.Dependency>} */
+    this.dependencies_ = {};
+    /** @private @const {!Object<string, string>} */
+    this.idToPath_ = {};
+    /** @private @const {!Object<string, boolean>} */
+    this.written_ = {};
+    /** @private @const {!Array<!goog.Dependency>} */
+    this.loadingDeps_ = [];
+    /** @private {!Array<!goog.Dependency>} */
+    this.depsToLoad_ = [];
+    /** @private {boolean} */
+    this.paused_ = false;
+    /** @private {!goog.DependencyFactory} */
+    this.factory_ = new goog.DependencyFactory(goog.transpiler_);
+    /** @private @const {!Object<string, !Function>} */
+    this.deferredCallbacks_ = {};
+    /** @private @const {!Array<string>} */
+    this.deferredQueue_ = [];
+  };
+
+  /**
+   * @param {!Array<string>} namespaces
+   * @param {function(): undefined} callback Function to call once all the
+   *     namespaces have loaded.
+   */
+  goog.DebugLoader_.prototype.bootstrap = function(namespaces, callback) {
+    var cb = callback;
+    function resolve() {
+      if (cb) {
+        goog.global.setTimeout(cb, 0);
+        cb = null;
+      }
+    }
+
+    if (!namespaces.length) {
+      resolve();
+      return;
+    }
+
+    var deps = [];
+    for (var i = 0; i < namespaces.length; i++) {
+      var path = this.getPathFromDeps_(namespaces[i]);
+      if (!path) {
+        throw new Error('Unregonized namespace: ' + namespaces[i]);
+      }
+      deps.push(this.dependencies_[path]);
+    }
+
+    var require = goog.require;
+    var loaded = 0;
+    for (var i = 0; i < namespaces.length; i++) {
+      require(namespaces[i]);
+      deps[i].onLoad(function() {
+        if (++loaded == namespaces.length) {
+          resolve();
+        }
+      });
+    }
+  };
+
+
+  /**
+   * Loads the Closure Dependency file.
+   *
+   * Exposed a public function so CLOSURE_NO_DEPS can be set to false, base
+   * loaded, setDependencyFactory called, and then this called. i.e. allows
+   * custom loading of the deps file.
+   */
+  goog.DebugLoader_.prototype.loadClosureDeps = function() {
+    // Circumvent addDependency, which would try to transpile deps.js if
+    // transpile is set to always.
+    var relPath = 'deps.js';
+    this.depsToLoad_.push(this.factory_.createDependency(
+        goog.normalizePath_(goog.basePath + relPath), relPath, [], [], {},
+        false));
+    this.loadDeps_();
+  };
+
+
+  /**
+   * Notifies the debug loader when a dependency has been requested.
+   *
+   * @param {string} absPathOrId Path of the dependency or goog id.
+   * @param {boolean=} opt_force
+   */
+  goog.DebugLoader_.prototype.requested = function(absPathOrId, opt_force) {
+    var path = this.getPathFromDeps_(absPathOrId);
+    if (path &&
+        (opt_force || this.areDepsLoaded_(this.dependencies_[path].requires))) {
+      var callback = this.deferredCallbacks_[path];
+      if (callback) {
+        delete this.deferredCallbacks_[path];
+        callback();
+      }
+    }
+  };
+
+
+  /**
+   * Sets the dependency factory, which can be used to create custom
+   * goog.Dependency implementations to control how dependencies are loaded.
+   *
+   * @param {!goog.DependencyFactory} factory
+   */
+  goog.DebugLoader_.prototype.setDependencyFactory = function(factory) {
+    this.factory_ = factory;
+  };
+
+
+  /**
+   * Travserses the dependency graph and queues the given dependency, and all of
+   * its transitive dependencies, for loading and then starts loading if not
+   * paused.
+   *
+   * @param {string} namespace
+   * @private
+   */
+  goog.DebugLoader_.prototype.load_ = function(namespace) {
+    if (!this.getPathFromDeps_(namespace)) {
+      var errorMessage = 'goog.require could not find: ' + namespace;
+
+      goog.logToConsole_(errorMessage);
+      throw Error(errorMessage);
+    } else {
+      var loader = this;
+
+      var deps = [];
+
+      /** @param {string} namespace */
+      var visit = function(namespace) {
+        var path = loader.getPathFromDeps_(namespace);
+
+        if (!path) {
+          throw new Error('Bad dependency path or symbol: ' + namespace);
+        }
+
+        if (loader.written_[path]) {
+          return;
+        }
+
+        loader.written_[path] = true;
+
+        var dep = loader.dependencies_[path];
+        for (var i = 0; i < dep.requires.length; i++) {
+          if (!goog.isProvided_(dep.requires[i])) {
+            visit(dep.requires[i]);
+          }
+        }
+
+        deps.push(dep);
+      };
+
+      visit(namespace);
+
+      var wasLoading = !!this.depsToLoad_.length;
+      this.depsToLoad_ = this.depsToLoad_.concat(deps);
+
+      if (!this.paused_ && !wasLoading) {
+        this.loadDeps_();
+      }
+    }
+  };
+
+
+  /**
+   * Loads any queued dependencies until they are all loaded or paused.
+   *
+   * @private
+   */
+  goog.DebugLoader_.prototype.loadDeps_ = function() {
+    var loader = this;
+    var paused = this.paused_;
+
+    while (this.depsToLoad_.length && !paused) {
+      (function() {
+        var loadCallDone = false;
+        var dep = loader.depsToLoad_.shift();
+
+        var loaded = false;
+        loader.loading_(dep);
+
+        var controller = {
+          pause: function() {
+            if (loadCallDone) {
+              throw new Error('Cannot call pause after the call to load.');
+            } else {
+              paused = true;
+            }
+          },
+          resume: function() {
+            if (loadCallDone) {
+              loader.resume_();
+            } else {
+              // Some dep called pause and then resume in the same load call.
+              // Just keep running this same loop.
+              paused = false;
+            }
+          },
+          loaded: function() {
+            if (loaded) {
+              throw new Error('Double call to loaded.');
+            }
+
+            loaded = true;
+            loader.loaded_(dep);
+          },
+          pending: function() {
+            // Defensive copy.
+            var pending = [];
+            for (var i = 0; i < loader.loadingDeps_.length; i++) {
+              pending.push(loader.loadingDeps_[i]);
+            }
+            return pending;
+          },
+          /**
+           * @param {goog.ModuleType} type
+           */
+          setModuleState: function(type) {
+            goog.moduleLoaderState_ = {
+              type: type,
+              moduleName: '',
+              declareLegacyNamespace: false
+            };
+          },
+          /** @type {function(string, string, string=)} */
+          registerEs6ModuleExports: function(
+              path, exports, opt_closureNamespace) {
+            if (opt_closureNamespace) {
+              goog.loadedModules_[opt_closureNamespace] = {
+                exports: exports,
+                type: goog.ModuleType.ES6,
+                moduleId: opt_closureNamespace || ''
+              };
+            }
+          },
+          /** @type {function(string, ?)} */
+          registerGoogModuleExports: function(moduleId, exports) {
+            goog.loadedModules_[moduleId] = {
+              exports: exports,
+              type: goog.ModuleType.GOOG,
+              moduleId: moduleId
+            };
+          },
+          clearModuleState: function() {
+            goog.moduleLoaderState_ = null;
+          },
+          defer: function(callback) {
+            if (loadCallDone) {
+              throw new Error(
+                  'Cannot register with defer after the call to load.');
+            }
+            loader.defer_(dep, callback);
+          },
+          areDepsLoaded: function() {
+            return loader.areDepsLoaded_(dep.requires);
+          }
+        };
+
+        try {
+          dep.load(controller);
+        } finally {
+          loadCallDone = true;
+        }
+      })();
+    }
+
+    if (paused) {
+      this.pause_();
+    }
+  };
+
+
+  /** @private */
+  goog.DebugLoader_.prototype.pause_ = function() {
+    this.paused_ = true;
+  };
+
+
+  /** @private */
+  goog.DebugLoader_.prototype.resume_ = function() {
+    if (this.paused_) {
+      this.paused_ = false;
+      this.loadDeps_();
+    }
+  };
+
+
+  /**
+   * Marks the given dependency as loading (load has been called but it has not
+   * yet marked itself as finished). Useful for dependencies that want to know
+   * what else is loading. Example: goog.modules cannot eval if there are
+   * loading dependencies.
+   *
+   * @param {!goog.Dependency} dep
+   * @private
+   */
+  goog.DebugLoader_.prototype.loading_ = function(dep) {
+    this.loadingDeps_.push(dep);
+  };
+
+
+  /**
+   * Marks the given dependency as having finished loading and being available
+   * for require.
+   *
+   * @param {!goog.Dependency} dep
+   * @private
+   */
+  goog.DebugLoader_.prototype.loaded_ = function(dep) {
+    for (var i = 0; i < this.loadingDeps_.length; i++) {
+      if (this.loadingDeps_[i] == dep) {
+        this.loadingDeps_.splice(i, 1);
+        break;
+      }
+    }
+
+    for (var i = 0; i < this.deferredQueue_.length; i++) {
+      if (this.deferredQueue_[i] == dep.path) {
+        this.deferredQueue_.splice(i, 1);
+        break;
+      }
+    }
+
+    if (this.loadingDeps_.length == this.deferredQueue_.length &&
+        !this.depsToLoad_.length) {
+      // Something has asked to load these, but they may not be directly
+      // required again later, so load them now that we know we're done loading
+      // everything else. e.g. a goog module entry point.
+      while (this.deferredQueue_.length) {
+        this.requested(this.deferredQueue_.shift(), true);
+      }
+    }
+
+    dep.loaded();
+  };
+
+
+  /**
+   * @param {!Array<string>} pathsOrIds
+   * @return {boolean}
+   * @private
+   */
+  goog.DebugLoader_.prototype.areDepsLoaded_ = function(pathsOrIds) {
+    for (var i = 0; i < pathsOrIds.length; i++) {
+      var path = this.getPathFromDeps_(pathsOrIds[i]);
+      if (!path ||
+          (!(path in this.deferredCallbacks_) &&
+           !goog.isProvided_(pathsOrIds[i]))) {
+        return false;
+      }
+    }
+
+    return true;
+  };
+
+
+  /**
+   * @param {string} absPathOrId
+   * @return {?string}
+   * @private
+   */
+  goog.DebugLoader_.prototype.getPathFromDeps_ = function(absPathOrId) {
+    if (absPathOrId in this.idToPath_) {
+      return this.idToPath_[absPathOrId];
+    } else if (absPathOrId in this.dependencies_) {
+      return absPathOrId;
+    } else {
+      return null;
+    }
+  };
+
+
+  /**
+   * @param {!goog.Dependency} dependency
+   * @param {!Function} callback
+   * @private
+   */
+  goog.DebugLoader_.prototype.defer_ = function(dependency, callback) {
+    this.deferredCallbacks_[dependency.path] = callback;
+    this.deferredQueue_.push(dependency.path);
+  };
+
+
+  /**
+   * Interface for goog.Dependency implementations to have some control over
+   * loading of dependencies.
+   *
+   * @record
+   */
+  goog.LoadController = function() {};
+
+
+  /**
+   * Tells the controller to halt loading of more dependencies.
+   */
+  goog.LoadController.prototype.pause = function() {};
+
+
+  /**
+   * Tells the controller to resume loading of more dependencies if paused.
+   */
+  goog.LoadController.prototype.resume = function() {};
+
+
+  /**
+   * Tells the controller that this dependency has finished loading.
+   *
+   * This causes this to be removed from pending() and any load callbacks to
+   * fire.
+   */
+  goog.LoadController.prototype.loaded = function() {};
+
+
+  /**
+   * List of dependencies on which load has been called but which have not
+   * called loaded on their controller. This includes the current dependency.
+   *
+   * @return {!Array<!goog.Dependency>}
+   */
+  goog.LoadController.prototype.pending = function() {};
+
+
+  /**
+   * Registers an object as an ES6 module's exports so that goog.modules may
+   * require it by path.
+   *
+   * @param {string} path Full path of the module.
+   * @param {?} exports
+   * @param {string=} opt_closureNamespace Closure namespace to associate with
+   *     this module.
+   */
+  goog.LoadController.prototype.registerEs6ModuleExports = function(
+      path, exports, opt_closureNamespace) {};
+
+
+  /**
+   * Sets the current module state.
+   *
+   * @param {goog.ModuleType} type Type of module.
+   */
+  goog.LoadController.prototype.setModuleState = function(type) {};
+
+
+  /**
+   * Clears the current module state.
+   */
+  goog.LoadController.prototype.clearModuleState = function() {};
+
+
+  /**
+   * Registers a callback to call once the dependency is actually requested
+   * via goog.require + all of the immediate dependencies have been loaded or
+   * all other files have been loaded. Allows for lazy loading until
+   * require'd without pausing dependency loading, which is needed on old IE.
+   *
+   * @param {!Function} callback
+   */
+  goog.LoadController.prototype.defer = function(callback) {};
+
+
+  /**
+   * @return {boolean}
+   */
+  goog.LoadController.prototype.areDepsLoaded = function() {};
+
+
+  /**
+   * Basic super class for all dependencies Closure Library can load.
+   *
+   * This default implementation is designed to load untranspiled, non-module
+   * scripts in a web broswer.
+   *
+   * For transpiled non-goog.module files {@see goog.TranspiledDependency}.
+   * For goog.modules see {@see goog.GoogModuleDependency}.
+   * For untranspiled ES6 modules {@see goog.Es6ModuleDependency}.
+   *
+   * @param {string} path Absolute path of this script.
+   * @param {string} relativePath Path of this script relative to goog.basePath.
+   * @param {!Array<string>} provides goog.provided or goog.module symbols
+   *     in this file.
+   * @param {!Array<string>} requires goog symbols or relative paths to Closure
+   *     this depends on.
+   * @param {!Object<string, string>} loadFlags
+   * @struct @constructor
+   */
+  goog.Dependency = function(
+      path, relativePath, provides, requires, loadFlags) {
+    /** @const */
+    this.path = path;
+    /** @const */
+    this.relativePath = relativePath;
+    /** @const */
+    this.provides = provides;
+    /** @const */
+    this.requires = requires;
+    /** @const */
+    this.loadFlags = loadFlags;
+    /** @private {boolean} */
+    this.loaded_ = false;
+    /** @private {!Array<function()>} */
+    this.loadCallbacks_ = [];
+  };
+
+
+  /**
+   * @return {string} The pathname part of this dependency's path if it is a
+   *     URI.
+   */
+  goog.Dependency.prototype.getPathName = function() {
+    var pathName = this.path;
+    var protocolIndex = pathName.indexOf('://');
+    if (protocolIndex >= 0) {
+      pathName = pathName.substring(protocolIndex + 3);
+      var slashIndex = pathName.indexOf('/');
+      if (slashIndex >= 0) {
+        pathName = pathName.substring(slashIndex + 1);
+      }
+    }
+    return pathName;
+  };
+
+
+  /**
+   * @param {function()} callback Callback to fire as soon as this has loaded.
+   * @final
+   */
+  goog.Dependency.prototype.onLoad = function(callback) {
+    if (this.loaded_) {
+      callback();
+    } else {
+      this.loadCallbacks_.push(callback);
+    }
+  };
+
+
+  /**
+   * Marks this dependency as loaded and fires any callbacks registered with
+   * onLoad.
+   * @final
+   */
+  goog.Dependency.prototype.loaded = function() {
+    this.loaded_ = true;
+    var callbacks = this.loadCallbacks_;
+    this.loadCallbacks_ = [];
+    for (var i = 0; i < callbacks.length; i++) {
+      callbacks[i]();
+    }
+  };
+
+
+  /**
+   * Whether or not document.written / appended script tags should be deferred.
+   *
+   * @private {boolean}
+   */
+  goog.Dependency.defer_ = false;
+
+
+  /**
+   * Map of script ready / state change callbacks. Old IE cannot handle putting
+   * these properties on goog.global.
+   *
+   * @private @const {!Object<string, function(?):undefined>}
+   */
+  goog.Dependency.callbackMap_ = {};
+
+
+  /**
+   * @param {function(...?):?} callback
+   * @return {string}
+   * @private
+   */
+  goog.Dependency.registerCallback_ = function(callback) {
+    var key = Math.random().toString(32);
+    goog.Dependency.callbackMap_[key] = callback;
+    return key;
+  };
+
+
+  /**
+   * @param {string} key
+   * @private
+   */
+  goog.Dependency.unregisterCallback_ = function(key) {
+    delete goog.Dependency.callbackMap_[key];
+  };
+
+
+  /**
+   * @param {string} key
+   * @param {...?} var_args
+   * @private
+   * @suppress {unusedPrivateMembers}
+   */
+  goog.Dependency.callback_ = function(key, var_args) {
+    if (key in goog.Dependency.callbackMap_) {
+      var callback = goog.Dependency.callbackMap_[key];
+      var args = [];
+      for (var i = 1; i < arguments.length; i++) {
+        args.push(arguments[i]);
+      }
+      callback.apply(undefined, args);
+    } else {
+      var errorMessage = 'Callback key ' + key +
+          ' does not exist (was base.js loaded more than once?).';
+      throw Error(errorMessage);
+    }
+  };
+
+
+  /**
+   * Starts loading this dependency. This dependency can pause loading if it
+   * needs to and resume it later via the controller interface.
+   *
+   * When this is loaded it should call controller.loaded(). Note that this will
+   * end up calling the loaded method of this dependency; there is no need to
+   * call it explicitly.
+   *
+   * @param {!goog.LoadController} controller
+   */
+  goog.Dependency.prototype.load = function(controller) {
+    if (goog.global.CLOSURE_IMPORT_SCRIPT) {
+      if (goog.global.CLOSURE_IMPORT_SCRIPT(this.path)) {
+        controller.loaded();
+      } else {
+        controller.pause();
+      }
+      return;
+    }
+
+    if (!goog.inHtmlDocument_()) {
+      goog.logToConsole_(
+          'Cannot use default debug loader outside of HTML documents.');
+      if (this.relativePath == 'deps.js') {
+        // Some old code is relying on base.js auto loading deps.js failing with
+        // no error before later setting CLOSURE_IMPORT_SCRIPT.
+        // CLOSURE_IMPORT_SCRIPT should be set *before* base.js is loaded, or
+        // CLOSURE_NO_DEPS set to true.
+        goog.logToConsole_(
+            'Consider setting CLOSURE_IMPORT_SCRIPT before loading base.js, ' +
+            'or setting CLOSURE_NO_DEPS to true.');
+        controller.loaded();
+      } else {
+        controller.pause();
+      }
+      return;
+    }
+
+    /** @type {!HTMLDocument} */
+    var doc = goog.global.document;
+
+    // If the user tries to require a new symbol after document load,
+    // something has gone terribly wrong. Doing a document.write would
+    // wipe out the page. This does not apply to the CSP-compliant method
+    // of writing script tags.
+    if (doc.readyState == 'complete' &&
+        !goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) {
+      // Certain test frameworks load base.js multiple times, which tries
+      // to write deps.js each time. If that happens, just fail silently.
+      // These frameworks wipe the page between each load of base.js, so this
+      // is OK.
+      var isDeps = /\bdeps.js$/.test(this.path);
+      if (isDeps) {
+        controller.loaded();
+        return;
+      } else {
+        throw Error('Cannot write "' + this.path + '" after document load');
+      }
+    }
+
+    if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING &&
+        goog.isDocumentLoading_()) {
+      var key = goog.Dependency.registerCallback_(function(script) {
+        if (!goog.DebugLoader_.IS_OLD_IE_ || script.readyState == 'complete') {
+          goog.Dependency.unregisterCallback_(key);
+          controller.loaded();
+        }
+      });
+      var nonceAttr = !goog.DebugLoader_.IS_OLD_IE_ && goog.getScriptNonce() ?
+          ' nonce="' + goog.getScriptNonce() + '"' :
+          '';
+      var event =
+          goog.DebugLoader_.IS_OLD_IE_ ? 'onreadystatechange' : 'onload';
+      var defer = goog.Dependency.defer_ ? 'defer' : '';
+      doc.write(
+          '<script src="' + this.path + '" ' + event +
+          '="goog.Dependency.callback_(\'' + key +
+          '\', this)" type="text/javascript" ' + defer + nonceAttr + '><' +
+          '/script>');
+    } else {
+      var scriptEl =
+          /** @type {!HTMLScriptElement} */ (doc.createElement('script'));
+      scriptEl.defer = goog.Dependency.defer_;
+      scriptEl.async = false;
+      scriptEl.type = 'text/javascript';
+
+      // If CSP nonces are used, propagate them to dynamically created scripts.
+      // This is necessary to allow nonce-based CSPs without 'strict-dynamic'.
+      var nonce = goog.getScriptNonce();
+      if (nonce) {
+        scriptEl.setAttribute('nonce', nonce);
+      }
+
+      if (goog.DebugLoader_.IS_OLD_IE_) {
+        // Execution order is not guaranteed on old IE, halt loading and write
+        // these scripts one at a time, after each loads.
+        controller.pause();
+        scriptEl.onreadystatechange = function() {
+          if (scriptEl.readyState == 'loaded' ||
+              scriptEl.readyState == 'complete') {
+            controller.loaded();
+            controller.resume();
+          }
+        };
+      } else {
+        scriptEl.onload = function() {
+          scriptEl.onload = null;
+          controller.loaded();
+        };
+      }
+
+      scriptEl.src = this.path;
+      doc.head.appendChild(scriptEl);
+    }
+  };
+
+
+  /**
+   * @param {string} path Absolute path of this script.
+   * @param {string} relativePath Path of this script relative to goog.basePath.
+   * @param {!Array<string>} provides Should be an empty array.
+   *     TODO(johnplaisted) add support for adding closure namespaces to ES6
+   *     modules for interop purposes.
+   * @param {!Array<string>} requires goog symbols or relative paths to Closure
+   *     this depends on.
+   * @param {!Object<string, string>} loadFlags
+   * @struct @constructor
+   * @extends {goog.Dependency}
+   */
+  goog.Es6ModuleDependency = function(
+      path, relativePath, provides, requires, loadFlags) {
+    goog.Es6ModuleDependency.base(
+        this, 'constructor', path, relativePath, provides, requires, loadFlags);
+  };
+  goog.inherits(goog.Es6ModuleDependency, goog.Dependency);
+
+
+  /** @override */
+  goog.Es6ModuleDependency.prototype.load = function(controller) {
+    if (goog.global.CLOSURE_IMPORT_SCRIPT) {
+      if (goog.global.CLOSURE_IMPORT_SCRIPT(this.path)) {
+        controller.loaded();
+      } else {
+        controller.pause();
+      }
+      return;
+    }
+
+    if (!goog.inHtmlDocument_()) {
+      goog.logToConsole_(
+          'Cannot use default debug loader outside of HTML documents.');
+      controller.pause();
+      return;
+    }
+
+    /** @type {!HTMLDocument} */
+    var doc = goog.global.document;
+
+    var dep = this;
+
+    // TODO(johnplaisted): Does document.writing really speed up anything? Any
+    // difference between this and just waiting for interactive mode and then
+    // appending?
+    function write(src, contents) {
+      if (contents) {
+        doc.write(
+            '<script type="module" crossorigin>' + contents + '</' +
+            'script>');
+      } else {
+        doc.write(
+            '<script type="module" crossorigin src="' + src + '"></' +
+            'script>');
+      }
+    }
+
+    function append(src, contents) {
+      var scriptEl =
+          /** @type {!HTMLScriptElement} */ (doc.createElement('script'));
+      scriptEl.defer = true;
+      scriptEl.async = false;
+      scriptEl.type = 'module';
+      scriptEl.setAttribute('crossorigin', true);
+
+      // If CSP nonces are used, propagate them to dynamically created scripts.
+      // This is necessary to allow nonce-based CSPs without 'strict-dynamic'.
+      var nonce = goog.getScriptNonce();
+      if (nonce) {
+        scriptEl.setAttribute('nonce', nonce);
+      }
+
+      if (contents) {
+        scriptEl.textContent = contents;
+      } else {
+        scriptEl.src = src;
+      }
+
+      doc.head.appendChild(scriptEl);
+    }
+
+    var create;
+
+    if (goog.isDocumentLoading_()) {
+      create = write;
+      // We can ONLY call document.write if we are guaranteed that any
+      // non-module script tags document.written after this are deferred.
+      // Small optimization, in theory document.writing is faster.
+      goog.Dependency.defer_ = true;
+    } else {
+      create = append;
+    }
+
+    // Write 4 separate tags here:
+    // 1) Sets the module state at the correct time (just before execution).
+    // 2) A src node for this, which just hopefully lets the browser load it a
+    //    little early (no need to parse #3).
+    // 3) Import the module and register it.
+    // 4) Clear the module state at the correct time. Guarnteed to run even
+    //    if there is an error in the module (#3 will not run if there is an
+    //    error in the module).
+    var beforeKey = goog.Dependency.registerCallback_(function() {
+      goog.Dependency.unregisterCallback_(beforeKey);
+      controller.setModuleState(goog.ModuleType.ES6);
+    });
+    create(undefined, 'goog.Dependency.callback_("' + beforeKey + '")');
+
+    // TODO(johnplaisted): Does this really speed up anything?
+    create(this.path, undefined);
+
+    var registerKey = goog.Dependency.registerCallback_(function(exports) {
+      goog.Dependency.unregisterCallback_(registerKey);
+      controller.registerEs6ModuleExports(
+          dep.path, exports, goog.moduleLoaderState_.moduleName);
+    });
+    create(
+        undefined,
+        'import * as m from "' + this.path + '"; goog.Dependency.callback_("' +
+            registerKey + '", m)');
+
+    var afterKey = goog.Dependency.registerCallback_(function() {
+      goog.Dependency.unregisterCallback_(afterKey);
+      controller.clearModuleState();
+      controller.loaded();
+    });
+    create(undefined, 'goog.Dependency.callback_("' + afterKey + '")');
+  };
+
+
+  /**
+   * Superclass of any dependency that needs to be loaded into memory,
+   * transformed, and then eval'd (goog.modules and transpiled files).
+   *
+   * @param {string} path Absolute path of this script.
+   * @param {string} relativePath Path of this script relative to goog.basePath.
+   * @param {!Array<string>} provides goog.provided or goog.module symbols
+   *     in this file.
+   * @param {!Array<string>} requires goog symbols or relative paths to Closure
+   *     this depends on.
+   * @param {!Object<string, string>} loadFlags
+   * @struct @constructor @abstract
+   * @extends {goog.Dependency}
+   */
+  goog.TransformedDependency = function(
+      path, relativePath, provides, requires, loadFlags) {
+    goog.TransformedDependency.base(
+        this, 'constructor', path, relativePath, provides, requires, loadFlags);
+    /** @private {?string} */
+    this.contents_ = null;
+
+    /**
+     * Whether to lazily make the synchronous XHR (when goog.require'd) or make
+     * the synchronous XHR when initially loading. On FireFox 61 there is a bug
+     * where an ES6 module cannot make a synchronous XHR (rather, it can, but if
+     * it does then no other ES6 modules will load after).
+     *
+     * tl;dr we lazy load due to bugs on older browsers and eager load due to
+     * bugs on newer ones.
+     *
+     * https://bugzilla.mozilla.org/show_bug.cgi?id=1477090
+     *
+     * @private @const {boolean}
+     */
+    this.lazyFetch_ = !goog.inHtmlDocument_() ||
+        !('noModule' in goog.global.document.createElement('script'));
+  };
+  goog.inherits(goog.TransformedDependency, goog.Dependency);
+
+
+  /** @override */
+  goog.TransformedDependency.prototype.load = function(controller) {
+    var dep = this;
+
+    function fetch() {
+      dep.contents_ = goog.loadFileSync_(dep.path);
+
+      if (dep.contents_) {
+        dep.contents_ = dep.transform(dep.contents_);
+        if (dep.contents_) {
+          dep.contents_ += '\n//# sourceURL=' + dep.path;
+        }
+      }
+    }
+
+    if (goog.global.CLOSURE_IMPORT_SCRIPT) {
+      fetch();
+      if (this.contents_ &&
+          goog.global.CLOSURE_IMPORT_SCRIPT('', this.contents_)) {
+        this.contents_ = null;
+        controller.loaded();
+      } else {
+        controller.pause();
+      }
+      return;
+    }
+
+
+    var isEs6 = this.loadFlags['module'] == goog.ModuleType.ES6;
+
+    if (!this.lazyFetch_) {
+      fetch();
+    }
+
+    function load() {
+      if (dep.lazyFetch_) {
+        fetch();
+      }
+
+      if (!dep.contents_) {
+        // loadFileSync_ or transform are responsible. Assume they logged an
+        // error.
+        return;
+      }
+
+      if (isEs6) {
+        controller.setModuleState(goog.ModuleType.ES6);
+      }
+
+      var namespace;
+
+      try {
+        var contents = dep.contents_;
+        dep.contents_ = null;
+        goog.globalEval(contents);
+        if (isEs6) {
+          namespace = goog.moduleLoaderState_.moduleName;
+        }
+      } finally {
+        if (isEs6) {
+          controller.clearModuleState();
+        }
+      }
+
+      if (isEs6) {
+        // Due to circular dependencies this may not be available for require
+        // right now.
+        goog.global['$jscomp']['require']['ensure'](
+            [dep.getPathName()], function() {
+              controller.registerEs6ModuleExports(
+                  dep.path,
+                  goog.global['$jscomp']['require'](dep.getPathName()),
+                  namespace);
+            });
+      }
+
+      controller.loaded();
+    }
+
+    // Do not fetch now; in FireFox 47 the synchronous XHR doesn't block all
+    // events. If we fetched now and then document.write'd the contents the
+    // document.write would be an eval and would execute too soon! Instead write
+    // a script tag to fetch and eval synchronously at the correct time.
+    function fetchInOwnScriptThenLoad() {
+      /** @type {!HTMLDocument} */
+      var doc = goog.global.document;
+
+      var key = goog.Dependency.registerCallback_(function() {
+        goog.Dependency.unregisterCallback_(key);
+        load();
+      });
+
+      doc.write(
+          '<script type="text/javascript">' +
+          goog.protectScriptTag_('goog.Dependency.callback_("' + key + '");') +
+          '</' +
+          'script>');
+    }
+
+    // If one thing is pending it is this.
+    var anythingElsePending = controller.pending().length > 1;
+
+    // If anything else is loading we need to lazy load due to bugs in old IE.
+    // Specifically script tags with src and script tags with contents could
+    // execute out of order if document.write is used, so we cannot use
+    // document.write. Do not pause here; it breaks old IE as well.
+    var useOldIeWorkAround =
+        anythingElsePending && goog.DebugLoader_.IS_OLD_IE_;
+
+    // Additionally if we are meant to defer scripts but the page is still
+    // loading (e.g. an ES6 module is loading) then also defer. Or if we are
+    // meant to defer and anything else is pending then defer (those may be
+    // scripts that did not need transformation and are just script tags with
+    // defer set to true, and we need to evaluate after that deferred script).
+    var needsAsyncLoading = goog.Dependency.defer_ &&
+        (anythingElsePending || goog.isDocumentLoading_());
+
+    if (useOldIeWorkAround || needsAsyncLoading) {
+      // Note that we only defer when we have to rather than 100% of the time.
+      // Always defering would work, but then in theory the order of
+      // goog.require calls would then matter. We want to enforce that most of
+      // the time the order of the require calls does not matter.
+      controller.defer(function() {
+        load();
+      });
+      return;
+    }
+    // TODO(johnplaisted): Externs are missing onreadystatechange for
+    // HTMLDocument.
+    /** @type {?} */
+    var doc = goog.global.document;
+
+    var isInternetExplorer =
+        goog.inHtmlDocument_() && 'ActiveXObject' in goog.global;
+
+    // Don't delay in any version of IE. There's bug around this that will
+    // cause out of order script execution. This means that on older IE ES6
+    // modules will load too early (while the document is still loading + the
+    // dom is not available). The other option is to load too late (when the
+    // document is complete and the onload even will never fire). This seems
+    // to be the lesser of two evils as scripts already act like the former.
+    if (isEs6 && goog.inHtmlDocument_() && goog.isDocumentLoading_() &&
+        !isInternetExplorer) {
+      goog.Dependency.defer_ = true;
+      // Transpiled ES6 modules still need to load like regular ES6 modules,
+      // aka only after the document is interactive.
+      controller.pause();
+      var oldCallback = doc.onreadystatechange;
+      doc.onreadystatechange = function() {
+        if (doc.readyState == 'interactive') {
+          doc.onreadystatechange = oldCallback;
+          load();
+          controller.resume();
+        }
+        if (goog.isFunction(oldCallback)) {
+          oldCallback.apply(undefined, arguments);
+        }
+      };
+    } else {
+      // Always eval on old IE.
+      if (goog.DebugLoader_.IS_OLD_IE_ || !goog.inHtmlDocument_() ||
+          !goog.isDocumentLoading_()) {
+        load();
+      } else {
+        fetchInOwnScriptThenLoad();
+      }
+    }
+  };
+
+
+  /**
+   * @param {string} contents
+   * @return {string}
+   * @abstract
+   */
+  goog.TransformedDependency.prototype.transform = function(contents) {};
+
+
+  /**
+   * Any non-goog.module dependency which needs to be transpiled before eval.
+   *
+   * @param {string} path Absolute path of this script.
+   * @param {string} relativePath Path of this script relative to goog.basePath.
+   * @param {!Array<string>} provides goog.provided or goog.module symbols
+   *     in this file.
+   * @param {!Array<string>} requires goog symbols or relative paths to Closure
+   *     this depends on.
+   * @param {!Object<string, string>} loadFlags
+   * @param {!goog.Transpiler} transpiler
+   * @struct @constructor
+   * @extends {goog.TransformedDependency}
+   */
+  goog.TranspiledDependency = function(
+      path, relativePath, provides, requires, loadFlags, transpiler) {
+    goog.TranspiledDependency.base(
+        this, 'constructor', path, relativePath, provides, requires, loadFlags);
+    /** @protected @const*/
+    this.transpiler = transpiler;
+  };
+  goog.inherits(goog.TranspiledDependency, goog.TransformedDependency);
+
+
+  /** @override */
+  goog.TranspiledDependency.prototype.transform = function(contents) {
+    // Transpile with the pathname so that ES6 modules are domain agnostic.
+    return this.transpiler.transpile(contents, this.getPathName());
+  };
+
+
+  /**
+   * A goog.module, transpiled or not. Will always perform some minimal
+   * transformation even when not transpiled to wrap in a goog.loadModule
+   * statement.
+   *
+   * @param {string} path Absolute path of this script.
+   * @param {string} relativePath Path of this script relative to goog.basePath.
+   * @param {!Array<string>} provides goog.provided or goog.module symbols
+   *     in this file.
+   * @param {!Array<string>} requires goog symbols or relative paths to Closure
+   *     this depends on.
+   * @param {!Object<string, string>} loadFlags
+   * @param {boolean} needsTranspile
+   * @param {!goog.Transpiler} transpiler
+   * @struct @constructor
+   * @extends {goog.TransformedDependency}
+   */
+  goog.GoogModuleDependency = function(
+      path, relativePath, provides, requires, loadFlags, needsTranspile,
+      transpiler) {
+    goog.GoogModuleDependency.base(
+        this, 'constructor', path, relativePath, provides, requires, loadFlags);
+    /** @private @const */
+    this.needsTranspile_ = needsTranspile;
+    /** @private @const */
+    this.transpiler_ = transpiler;
+  };
+  goog.inherits(goog.GoogModuleDependency, goog.TransformedDependency);
+
+
+  /** @override */
+  goog.GoogModuleDependency.prototype.transform = function(contents) {
+    if (this.needsTranspile_) {
+      contents = this.transpiler_.transpile(contents, this.getPathName());
+    }
+
+    if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
+      return '' +
+          'goog.loadModule(function(exports) {' +
+          '"use strict";' + contents +
+          '\n' +  // terminate any trailing single line comment.
+          ';return exports' +
+          '});' +
+          '\n//# sourceURL=' + this.path + '\n';
+    } else {
+      return '' +
+          'goog.loadModule(' +
+          goog.global.JSON.stringify(
+              contents + '\n//# sourceURL=' + this.path + '\n') +
+          ');';
+    }
+  };
+
+
+  /**
+   * Whether the browser is IE9 or earlier, which needs special handling
+   * for deferred modules.
+   * @const @private {boolean}
+   */
+  goog.DebugLoader_.IS_OLD_IE_ = !!(
+      !goog.global.atob && goog.global.document && goog.global.document['all']);
+
+
+  /**
+   * @param {string} relPath
+   * @param {!Array<string>|undefined} provides
+   * @param {!Array<string>} requires
+   * @param {boolean|!Object<string>=} opt_loadFlags
+   * @see goog.addDependency
+   */
+  goog.DebugLoader_.prototype.addDependency = function(
+      relPath, provides, requires, opt_loadFlags) {
+    provides = provides || [];
+    relPath = relPath.replace(/\\/g, '/');
+    var path = goog.normalizePath_(goog.basePath + relPath);
+    if (!opt_loadFlags || typeof opt_loadFlags === 'boolean') {
+      opt_loadFlags = opt_loadFlags ? {'module': goog.ModuleType.GOOG} : {};
+    }
+    var dep = this.factory_.createDependency(
+        path, relPath, provides, requires, opt_loadFlags,
+        goog.transpiler_.needsTranspile(
+            opt_loadFlags['lang'] || 'es3', opt_loadFlags['module']));
+    this.dependencies_[path] = dep;
+    for (var i = 0; i < provides.length; i++) {
+      this.idToPath_[provides[i]] = path;
+    }
+    this.idToPath_[relPath] = path;
+  };
+
+
+  /**
+   * Creates goog.Dependency instances for the debug loader to load.
+   *
+   * Should be overridden to have the debug loader use custom subclasses of
+   * goog.Dependency.
+   *
+   * @param {!goog.Transpiler} transpiler
+   * @struct @constructor
+   */
+  goog.DependencyFactory = function(transpiler) {
+    /** @protected @const */
+    this.transpiler = transpiler;
+  };
+
+
+  /**
+   * @param {string} path Absolute path of the file.
+   * @param {string} relativePath Path relative to closure’s base.js.
+   * @param {!Array<string>} provides Array of provided goog.provide/module ids.
+   * @param {!Array<string>} requires Array of required goog.provide/module /
+   *     relative ES6 module paths.
+   * @param {!Object<string, string>} loadFlags
+   * @param {boolean} needsTranspile True if the file needs to be transpiled
+   *     per the goog.Transpiler.
+   * @return {!goog.Dependency}
+   */
+  goog.DependencyFactory.prototype.createDependency = function(
+      path, relativePath, provides, requires, loadFlags, needsTranspile) {
+
+    if (loadFlags['module'] == goog.ModuleType.GOOG) {
+      return new goog.GoogModuleDependency(
+          path, relativePath, provides, requires, loadFlags, needsTranspile,
+          this.transpiler);
+    } else if (needsTranspile) {
+      return new goog.TranspiledDependency(
+          path, relativePath, provides, requires, loadFlags, this.transpiler);
+    } else {
+      if (loadFlags['module'] == goog.ModuleType.ES6) {
+        return new goog.Es6ModuleDependency(
+            path, relativePath, provides, requires, loadFlags);
+      } else {
+        return new goog.Dependency(
+            path, relativePath, provides, requires, loadFlags);
+      }
+    }
+  };
+
+
+  /** @private @const */
+  goog.debugLoader_ = new goog.DebugLoader_();
+
+
+  /**
+   * Loads the Closure Dependency file.
+   *
+   * Exposed a public function so CLOSURE_NO_DEPS can be set to false, base
+   * loaded, setDependencyFactory called, and then this called. i.e. allows
+   * custom loading of the deps file.
+   */
+  goog.loadClosureDeps = function() {
+    goog.debugLoader_.loadClosureDeps();
+  };
+
+
+  /**
+   * Sets the dependency factory, which can be used to create custom
+   * goog.Dependency implementations to control how dependencies are loaded.
+   *
+   * Note: if you wish to call this function and provide your own implemnetation
+   * it is a wise idea to set CLOSURE_NO_DEPS to true, otherwise the dependency
+   * file and all of its goog.addDependency calls will use the default factory.
+   * You can call goog.loadClosureDeps to load the Closure dependency file
+   * later, after your factory is injected.
+   *
+   * @param {!goog.DependencyFactory} factory
+   */
+  goog.setDependencyFactory = function(factory) {
+    goog.debugLoader_.setDependencyFactory(factory);
+  };
+
+
+  if (!goog.global.CLOSURE_NO_DEPS) {
+    goog.debugLoader_.loadClosureDeps();
+  }
+
+
+  /**
+   * Bootstraps the given namespaces and calls the callback once they are
+   * available either via goog.require. This is a replacement for using
+   * `goog.require` to bootstrap Closure JavaScript. Previously a `goog.require`
+   * in an HTML file would guarantee that the require'd namespace was available
+   * in the next immediate script tag. With ES6 modules this no longer a
+   * guarantee.
+   *
+   * @param {!Array<string>} namespaces
+   * @param {function(): ?} callback Function to call once all the namespaces
+   *     have loaded. Always called asynchronously.
+   */
+  goog.bootstrap = function(namespaces, callback) {
+    goog.debugLoader_.bootstrap(namespaces, callback);
+  };
+}

File diff suppressed because it is too large
+ 1 - 278
public/lib/closure/deps.js


+ 18 - 10
public/lib/closure/vec/float32array.js

@@ -36,9 +36,12 @@ goog.provide('goog.vec.Float32Array');
  *     The length of the array, or an array to initialize the contents of the
  *     new Float32Array.
  * @constructor
+ * @implements {IArrayLike<number>}
+ * @final
  */
 goog.vec.Float32Array = function(p0) {
-  this.length = p0.length || p0;
+  /** @type {number} */
+  this.length = /** @type {number} */ (p0.length || p0);
   for (var i = 0; i < this.length; i++) {
     this[i] = p0[i] || 0;
   }
@@ -65,7 +68,7 @@ goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT = 4;
 
 /**
  * Sets elements of the array.
- * @param {Array.<number>|Float32Array} values The array of values.
+ * @param {Array<number>|Float32Array} values The array of values.
  * @param {number=} opt_offset The offset in this array to start.
  */
 goog.vec.Float32Array.prototype.set = function(values, opt_offset) {
@@ -79,6 +82,7 @@ goog.vec.Float32Array.prototype.set = function(values, opt_offset) {
 /**
  * Creates a string representation of this array.
  * @return {string} The string version of this array.
+ * @override
  */
 goog.vec.Float32Array.prototype.toString = Array.prototype.join;
 
@@ -97,13 +101,17 @@ goog.vec.Float32Array.prototype.toString = Array.prototype.join;
  * goog.vec.Float32Array as Float32Array.
  */
 if (typeof Float32Array == 'undefined') {
-  goog.exportProperty(goog.vec.Float32Array, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float32Array.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float32Array.prototype, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float32Array.prototype, 'set',
-                      goog.vec.Float32Array.prototype.set);
-  goog.exportProperty(goog.vec.Float32Array.prototype, 'toString',
-                      goog.vec.Float32Array.prototype.toString);
+  goog.exportProperty(
+      goog.vec.Float32Array, 'BYTES_PER_ELEMENT',
+      goog.vec.Float32Array.BYTES_PER_ELEMENT);
+  goog.exportProperty(
+      goog.vec.Float32Array.prototype, 'BYTES_PER_ELEMENT',
+      goog.vec.Float32Array.prototype.BYTES_PER_ELEMENT);
+  goog.exportProperty(
+      goog.vec.Float32Array.prototype, 'set',
+      goog.vec.Float32Array.prototype.set);
+  goog.exportProperty(
+      goog.vec.Float32Array.prototype, 'toString',
+      goog.vec.Float32Array.prototype.toString);
   goog.exportSymbol('Float32Array', goog.vec.Float32Array);
 }

+ 11 - 0
public/lib/closure/vec/float32array_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Float32ArrayTest');</script>
+<title>Closure Unit Tests - goog.vec.Float32ArrayTest</title></head><body></body></html>

+ 55 - 0
public/lib/closure/vec/float32array_test.js

@@ -0,0 +1,55 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Float32ArrayTest');
+goog.setTestOnly('goog.vec.Float32ArrayTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Float32Array');
+
+function testConstructorInitializesElementsToZero() {
+  var f = new goog.vec.Float32Array(3);
+  assertEquals(3, f.length);
+  assertEquals(0, f[0]);
+  assertEquals(0, f[1]);
+  assertEquals(0, f[2]);
+  assertEquals(4, f.BYTES_PER_ELEMENT);
+  assertEquals(4, goog.vec.Float32Array.BYTES_PER_ELEMENT);
+}
+
+function testConstructorWithArrayAsArgument() {
+  var f0 = new goog.vec.Float32Array([0, 0, 1, 0]);
+  var f1 = new goog.vec.Float32Array(4);
+  f1[0] = 0;
+  f1[1] = 0;
+  f1[2] = 1;
+  f1[3] = 0;
+  assertObjectEquals(f0, f1);
+}
+
+function testSet() {
+  var f0 = new goog.vec.Float32Array(4);
+  var f1 = new goog.vec.Float32Array(4);
+  f0.set([1, 2, 3, 4]);
+  f1[0] = 1;
+  f1[1] = 2;
+  f1[2] = 3;
+  f1[3] = 4;
+  assertObjectEquals(f0, f1);
+}
+
+function testSetWithOffset() {
+  var f0 = new goog.vec.Float32Array(4);
+  var f1 = new goog.vec.Float32Array(4);
+  f0.set([5], 1);
+  f1[0] = 0;
+  f1[1] = 5;
+  f1[2] = 0;
+  f1[3] = 0;
+  assertObjectEquals(f0, f1);
+}
+
+function testToString() {
+  var f = new goog.vec.Float32Array([4, 3, 2, 1]);
+  assertEquals('4,3,2,1', f.toString());
+}

+ 25 - 10
public/lib/closure/vec/float64array.js

@@ -36,9 +36,12 @@ goog.provide('goog.vec.Float64Array');
  *     The length of the array, or an array to initialize the contents of the
  *     new Float64Array.
  * @constructor
+ * @implements {IArrayLike<number>}
+ * @final
  */
 goog.vec.Float64Array = function(p0) {
-  this.length = p0.length || p0;
+  /** @type {number} */
+  this.length = /** @type {number} */ (p0.length || p0);
   for (var i = 0; i < this.length; i++) {
     this[i] = p0[i] || 0;
   }
@@ -65,7 +68,7 @@ goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT = 8;
 
 /**
  * Sets elements of the array.
- * @param {Array.<number>|Float64Array} values The array of values.
+ * @param {Array<number>|Float64Array} values The array of values.
  * @param {number=} opt_offset The offset in this array to start.
  */
 goog.vec.Float64Array.prototype.set = function(values, opt_offset) {
@@ -79,6 +82,7 @@ goog.vec.Float64Array.prototype.set = function(values, opt_offset) {
 /**
  * Creates a string representation of this array.
  * @return {string} The string version of this array.
+ * @override
  */
 goog.vec.Float64Array.prototype.toString = Array.prototype.join;
 
@@ -97,13 +101,24 @@ goog.vec.Float64Array.prototype.toString = Array.prototype.join;
  * goog.vec.Float64Array as Float64Array.
  */
 if (typeof Float64Array == 'undefined') {
-  goog.exportProperty(goog.vec.Float64Array, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float64Array.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float64Array.prototype, 'BYTES_PER_ELEMENT',
-                      goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT);
-  goog.exportProperty(goog.vec.Float64Array.prototype, 'set',
-                      goog.vec.Float64Array.prototype.set);
-  goog.exportProperty(goog.vec.Float64Array.prototype, 'toString',
-                      goog.vec.Float64Array.prototype.toString);
+  try {
+    goog.exportProperty(
+        goog.vec.Float64Array, 'BYTES_PER_ELEMENT',
+        goog.vec.Float64Array.BYTES_PER_ELEMENT);
+  } catch (float64ArrayError) {
+    // Do nothing.  This code is in place to fix b/7225850, in which an error
+    // is incorrectly thrown for Google TV on an old Chrome.
+    // TODO(user): remove after that version is retired.
+  }
+
+  goog.exportProperty(
+      goog.vec.Float64Array.prototype, 'BYTES_PER_ELEMENT',
+      goog.vec.Float64Array.prototype.BYTES_PER_ELEMENT);
+  goog.exportProperty(
+      goog.vec.Float64Array.prototype, 'set',
+      goog.vec.Float64Array.prototype.set);
+  goog.exportProperty(
+      goog.vec.Float64Array.prototype, 'toString',
+      goog.vec.Float64Array.prototype.toString);
   goog.exportSymbol('Float64Array', goog.vec.Float64Array);
 }

+ 11 - 0
public/lib/closure/vec/float64array_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Float64ArrayTest');</script>
+<title>Closure Unit Tests - goog.vec.Float64ArrayTest</title></head><body></body></html>

+ 55 - 0
public/lib/closure/vec/float64array_test.js

@@ -0,0 +1,55 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Float64ArrayTest');
+goog.setTestOnly('goog.vec.Float64ArrayTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Float64Array');
+
+function testConstructorInitializesElementsToZero() {
+  var f = new goog.vec.Float64Array(3);
+  assertEquals(3, f.length);
+  assertEquals(0, f[0]);
+  assertEquals(0, f[1]);
+  assertEquals(0, f[2]);
+  assertEquals(8, f.BYTES_PER_ELEMENT);
+  assertEquals(8, goog.vec.Float64Array.BYTES_PER_ELEMENT);
+}
+
+function testConstructorWithArrayAsArgument() {
+  var f0 = new goog.vec.Float64Array([0, 0, 1, 0]);
+  var f1 = new goog.vec.Float64Array(4);
+  f1[0] = 0;
+  f1[1] = 0;
+  f1[2] = 1;
+  f1[3] = 0;
+  assertObjectEquals(f0, f1);
+}
+
+function testSet() {
+  var f0 = new goog.vec.Float64Array(4);
+  var f1 = new goog.vec.Float64Array(4);
+  f0.set([1, 2, 3, 4]);
+  f1[0] = 1;
+  f1[1] = 2;
+  f1[2] = 3;
+  f1[3] = 4;
+  assertObjectEquals(f0, f1);
+}
+
+function testSetWithOffset() {
+  var f0 = new goog.vec.Float64Array(4);
+  var f1 = new goog.vec.Float64Array(4);
+  f0.set([5], 1);
+  f1[0] = 0;
+  f1[1] = 5;
+  f1[2] = 0;
+  f1[3] = 0;
+  assertObjectEquals(f0, f1);
+}
+
+function testToString() {
+  var f = new goog.vec.Float64Array([4, 3, 2, 1]);
+  assertEquals('4,3,2,1', f.toString());
+}

+ 337 - 47
public/lib/closure/vec/mat3.js

@@ -20,11 +20,12 @@
  * where noted. Matrix operations follow the mathematical form when multiplying
  * vectors as follows: resultVec = matrix * vec.
  *
+ * The matrices are stored in column-major order.
+ *
  */
 goog.provide('goog.vec.Mat3');
 
 goog.require('goog.vec');
-goog.require('goog.vec.Vec3');
 
 
 /** @typedef {goog.vec.Float32} */ goog.vec.Mat3.Float32;
@@ -68,10 +69,7 @@ goog.vec.Mat3.createFloat64 = function() {
  */
 goog.vec.Mat3.createNumber = function() {
   var a = new Array(9);
-  goog.vec.Mat3.setFromValues(a,
-                              0, 0, 0,
-                              0, 0, 0,
-                              0, 0, 0);
+  goog.vec.Mat3.setFromValues(a, 0, 0, 0, 0, 0, 0, 0, 0, 0);
   return a;
 };
 
@@ -120,10 +118,7 @@ goog.vec.Mat3.createFloat64Identity = function() {
  */
 goog.vec.Mat3.createNumberIdentity = function() {
   var a = new Array(9);
-  goog.vec.Mat3.setFromValues(a,
-                              1, 0, 0,
-                              0, 1, 0,
-                              0, 0, 1);
+  goog.vec.Mat3.setFromValues(a, 1, 0, 0, 0, 1, 0, 0, 0, 1);
   return a;
 };
 
@@ -303,9 +298,12 @@ goog.vec.Mat3.getElement = function(mat, row, column) {
  * @param {number} row The row index.
  * @param {number} column The column index.
  * @param {number} value The value to set at the requested row, column.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setElement = function(mat, row, column, value) {
   mat[row + column * 3] = value;
+  return mat;
 };
 
 
@@ -324,7 +322,7 @@ goog.vec.Mat3.setElement = function(mat, row, column, value) {
  * @param {number} v02 The values at (0, 2).
  * @param {number} v12 The values at (1, 2).
  * @param {number} v22 The values at (2, 2).
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.setFromValues = function(
@@ -348,7 +346,7 @@ goog.vec.Mat3.setFromValues = function(
  * @param {goog.vec.Mat3.AnyType} mat The matrix to receive the values.
  * @param {goog.vec.Mat3.AnyType} values The column major ordered
  *     array of values to store in the matrix.
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.setFromArray = function(mat, values) {
@@ -371,7 +369,7 @@ goog.vec.Mat3.setFromArray = function(mat, values) {
  * @param {goog.vec.Mat3.AnyType} mat The matrix to receive the values.
  * @param {goog.vec.Mat3.AnyType} values The row major ordered array
  *     of values to store in the matrix.
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.setFromRowMajorArray = function(mat, values) {
@@ -395,7 +393,7 @@ goog.vec.Mat3.setFromRowMajorArray = function(mat, values) {
  * @param {number} v00 The values for (0, 0).
  * @param {number} v11 The values for (1, 1).
  * @param {number} v22 The values for (2, 2).
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.setDiagonalValues = function(mat, v00, v11, v22) {
@@ -411,28 +409,34 @@ goog.vec.Mat3.setDiagonalValues = function(mat, v00, v11, v22) {
  *
  * @param {goog.vec.Mat3.AnyType} mat The matrix to receive the values.
  * @param {goog.vec.Vec3.AnyType} vec The vector containing the values.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setDiagonal = function(mat, vec) {
   mat[0] = vec[0];
   mat[4] = vec[1];
   mat[8] = vec[2];
+  return mat;
 };
 
 
 /**
  * Sets the specified column with the supplied values.
  *
- * @param {goog.vec.Mat3.AnyType} mat The matrix to recieve the values.
+ * @param {goog.vec.Mat3.AnyType} mat The matrix to receive the values.
  * @param {number} column The column index to set the values on.
  * @param {number} v0 The value for row 0.
  * @param {number} v1 The value for row 1.
  * @param {number} v2 The value for row 2.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setColumnValues = function(mat, column, v0, v1, v2) {
   var i = column * 3;
   mat[i] = v0;
   mat[i + 1] = v1;
   mat[i + 2] = v2;
+  return mat;
 };
 
 
@@ -442,12 +446,15 @@ goog.vec.Mat3.setColumnValues = function(mat, column, v0, v1, v2) {
  * @param {goog.vec.Mat3.AnyType} mat The matrix to receive the values.
  * @param {number} column The column index to set the values on.
  * @param {goog.vec.Vec3.AnyType} vec The vector elements for the column.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setColumn = function(mat, column, vec) {
   var i = column * 3;
   mat[i] = vec[0];
   mat[i + 1] = vec[1];
   mat[i + 2] = vec[2];
+  return mat;
 };
 
 
@@ -459,12 +466,15 @@ goog.vec.Mat3.setColumn = function(mat, column, vec) {
  * @param {number} column The column to get the values from.
  * @param {goog.vec.Vec3.AnyType} vec The vector elements to receive the
  *     column.
+ * @return {goog.vec.Vec3.AnyType} return vec so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.getColumn = function(mat, column, vec) {
   var i = column * 3;
   vec[0] = mat[i];
   vec[1] = mat[i + 1];
   vec[2] = mat[i + 2];
+  return vec;
 };
 
 
@@ -475,11 +485,14 @@ goog.vec.Mat3.getColumn = function(mat, column, vec) {
  * @param {goog.vec.Vec3.AnyType} vec0 The values for column 0.
  * @param {goog.vec.Vec3.AnyType} vec1 The values for column 1.
  * @param {goog.vec.Vec3.AnyType} vec2 The values for column 2.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setColumns = function(mat, vec0, vec1, vec2) {
   goog.vec.Mat3.setColumn(mat, 0, vec0);
   goog.vec.Mat3.setColumn(mat, 1, vec1);
   goog.vec.Mat3.setColumn(mat, 2, vec2);
+  return mat;
 };
 
 
@@ -507,11 +520,14 @@ goog.vec.Mat3.getColumns = function(mat, vec0, vec1, vec2) {
  * @param {number} v0 The value for column 0.
  * @param {number} v1 The value for column 1.
  * @param {number} v2 The value for column 2.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setRowValues = function(mat, row, v0, v1, v2) {
   mat[row] = v0;
   mat[row + 3] = v1;
   mat[row + 6] = v2;
+  return mat;
 };
 
 
@@ -521,11 +537,14 @@ goog.vec.Mat3.setRowValues = function(mat, row, v0, v1, v2) {
  * @param {goog.vec.Mat3.AnyType} mat The matrix to receive the row values.
  * @param {number} row The index of the row.
  * @param {goog.vec.Vec3.AnyType} vec The vector containing the values.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setRow = function(mat, row, vec) {
   mat[row] = vec[0];
   mat[row + 3] = vec[1];
   mat[row + 6] = vec[2];
+  return mat;
 };
 
 
@@ -535,11 +554,14 @@ goog.vec.Mat3.setRow = function(mat, row, vec) {
  * @param {goog.vec.Mat3.AnyType} mat The matrix supplying the values.
  * @param {number} row The index of the row supplying the values.
  * @param {goog.vec.Vec3.AnyType} vec The vector to receive the row.
+ * @return {goog.vec.Vec3.AnyType} return vec so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.getRow = function(mat, row, vec) {
   vec[0] = mat[row];
   vec[1] = mat[row + 3];
   vec[2] = mat[row + 6];
+  return vec;
 };
 
 
@@ -550,11 +572,14 @@ goog.vec.Mat3.getRow = function(mat, row, vec) {
  * @param {goog.vec.Vec3.AnyType} vec0 The values for row 0.
  * @param {goog.vec.Vec3.AnyType} vec1 The values for row 1.
  * @param {goog.vec.Vec3.AnyType} vec2 The values for row 2.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat3.setRows = function(mat, vec0, vec1, vec2) {
   goog.vec.Mat3.setRow(mat, 0, vec0);
   goog.vec.Mat3.setRow(mat, 1, vec1);
   goog.vec.Mat3.setRow(mat, 2, vec2);
+  return mat;
 };
 
 
@@ -577,7 +602,7 @@ goog.vec.Mat3.getRows = function(mat, vec0, vec1, vec2) {
  * Makes the given 3x3 matrix the zero matrix.
  *
  * @param {goog.vec.Mat3.AnyType} mat The matrix.
- * @return {!goog.vec.Mat3.AnyType} return mat so operations can be chained.
+ * @return {goog.vec.Mat3.AnyType} return mat so operations can be chained.
  */
 goog.vec.Mat3.makeZero = function(mat) {
   mat[0] = 0;
@@ -597,7 +622,7 @@ goog.vec.Mat3.makeZero = function(mat) {
  * Makes the given 3x3 matrix the identity matrix.
  *
  * @param {goog.vec.Mat3.AnyType} mat The matrix.
- * @return {!goog.vec.Mat3.AnyType} return mat so operations can be chained.
+ * @return {goog.vec.Mat3.AnyType} return mat so operations can be chained.
  */
 goog.vec.Mat3.makeIdentity = function(mat) {
   mat[0] = 1;
@@ -621,7 +646,7 @@ goog.vec.Mat3.makeIdentity = function(mat) {
  * @param {goog.vec.Mat3.AnyType} mat1 The second addend.
  * @param {goog.vec.Mat3.AnyType} resultMat The matrix to
  *     receive the results (may be either mat0 or mat1).
- * @return {!goog.vec.Mat3.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.addMat = function(mat0, mat1, resultMat) {
@@ -646,7 +671,7 @@ goog.vec.Mat3.addMat = function(mat0, mat1, resultMat) {
  * @param {goog.vec.Mat3.AnyType} mat1 The subtrahend.
  * @param {goog.vec.Mat3.AnyType} resultMat The matrix to receive
  *     the results (may be either mat0 or mat1).
- * @return {!goog.vec.Mat3.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.subMat = function(mat0, mat1, resultMat) {
@@ -671,7 +696,7 @@ goog.vec.Mat3.subMat = function(mat0, mat1, resultMat) {
  * @param {number} scalar The scalar value to multiple to each element of mat.
  * @param {goog.vec.Mat3.AnyType} resultMat The matrix to receive
  *     the results (may be mat).
- * @return {!goog.vec.Mat3.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.multScalar = function(mat, scalar, resultMat) {
@@ -696,7 +721,7 @@ goog.vec.Mat3.multScalar = function(mat, scalar, resultMat) {
  * @param {goog.vec.Mat3.AnyType} mat1 The second (right hand) matrix.
  * @param {goog.vec.Mat3.AnyType} resultMat The matrix to receive
  *     the results (may be either mat0 or mat1).
- * @return {!goog.vec.Mat3.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.multMat = function(mat0, mat1, resultMat) {
@@ -727,7 +752,7 @@ goog.vec.Mat3.multMat = function(mat0, mat1, resultMat) {
  * @param {goog.vec.Mat3.AnyType} mat The matrix to transpose.
  * @param {goog.vec.Mat3.AnyType} resultMat The matrix to receive
  *     the results (may be mat).
- * @return {!goog.vec.Mat3.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.transpose = function(mat, resultMat) {
@@ -801,10 +826,10 @@ goog.vec.Mat3.invert = function(mat0, resultMat) {
  * @return {boolean} True if the the two matrices are equivalent.
  */
 goog.vec.Mat3.equals = function(mat0, mat1) {
-  return mat0.length == mat1.length &&
-      mat0[0] == mat1[0] && mat0[1] == mat1[1] && mat0[2] == mat1[2] &&
-      mat0[3] == mat1[3] && mat0[4] == mat1[4] && mat0[5] == mat1[5] &&
-      mat0[6] == mat1[6] && mat0[7] == mat1[7] && mat0[8] == mat1[8];
+  return mat0.length == mat1.length && mat0[0] == mat1[0] &&
+      mat0[1] == mat1[1] && mat0[2] == mat1[2] && mat0[3] == mat1[3] &&
+      mat0[4] == mat1[4] && mat0[5] == mat1[5] && mat0[6] == mat1[6] &&
+      mat0[7] == mat1[7] && mat0[8] == mat1[8];
 };
 
 
@@ -812,11 +837,11 @@ goog.vec.Mat3.equals = function(mat0, mat1) {
  * Transforms the given vector with the given matrix storing the resulting,
  * transformed matrix into resultVec.
  *
- * @param {!goog.vec.Mat3.AnyType} mat The matrix supplying the transformation.
+ * @param {goog.vec.Mat3.AnyType} mat The matrix supplying the transformation.
  * @param {goog.vec.Vec3.AnyType} vec The vector to transform.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to
  *     receive the results (may be vec).
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Mat3.multVec3 = function(mat, vec, resultVec) {
@@ -832,34 +857,32 @@ goog.vec.Mat3.multVec3 = function(mat, vec, resultVec) {
  * Makes the given 3x3 matrix a translation matrix with x and y
  * translation values.
  *
- * @param {!goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
  * @param {number} x The translation along the x axis.
  * @param {number} y The translation along the y axis.
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained.
  */
 goog.vec.Mat3.makeTranslate = function(mat, x, y) {
   goog.vec.Mat3.makeIdentity(mat);
-  goog.vec.Mat3.setColumnValues(mat, 2, x, y, 1);
-  return mat;
+  return goog.vec.Mat3.setColumnValues(mat, 2, x, y, 1);
 };
 
 
 /**
  * Makes the given 3x3 matrix a scale matrix with x, y, and z scale factors.
  *
- * @param {!goog.vec.Mat3.AnyType} mat The 3x3 (9-element) matrix
+ * @param {goog.vec.Mat3.AnyType} mat The 3x3 (9-element) matrix
  *     array to receive the new scale matrix.
  * @param {number} x The scale along the x axis.
  * @param {number} y The scale along the y axis.
  * @param {number} z The scale along the z axis.
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained.
  */
 goog.vec.Mat3.makeScale = function(mat, x, y, z) {
   goog.vec.Mat3.makeIdentity(mat);
-  goog.vec.Mat3.setDiagonalValues(mat, x, y, z);
-  return mat;
+  return goog.vec.Mat3.setDiagonalValues(mat, x, y, z);
 };
 
 
@@ -867,12 +890,12 @@ goog.vec.Mat3.makeScale = function(mat, x, y, z) {
  * Makes the given 3x3 matrix a rotation matrix with the given rotation
  * angle about the axis defined by the vector (ax, ay, az).
  *
- * @param {!goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
  * @param {number} angle The rotation angle in radians.
  * @param {number} ax The x component of the rotation axis.
  * @param {number} ay The y component of the rotation axis.
  * @param {number} az The z component of the rotation axis.
- * @return {!goog.vec.Mat3.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
  *     chained.
  */
 goog.vec.Mat3.makeRotate = function(mat, angle, ax, ay, az) {
@@ -880,17 +903,284 @@ goog.vec.Mat3.makeRotate = function(mat, angle, ax, ay, az) {
   var d = 1 - c;
   var s = Math.sin(angle);
 
-  goog.vec.Mat3.setFromValues(mat,
-      ax * ax * d + c,
-      ax * ay * d + az * s,
-      ax * az * d - ay * s,
+  return goog.vec.Mat3.setFromValues(
+      mat, ax * ax * d + c, ax * ay * d + az * s, ax * az * d - ay * s,
+
+      ax * ay * d - az * s, ay * ay * d + c, ay * az * d + ax * s,
+
+      ax * az * d + ay * s, ay * az * d - ax * s, az * az * d + c);
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.makeRotateX = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+  return goog.vec.Mat3.setFromValues(mat, 1, 0, 0, 0, c, s, 0, -s, c);
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.makeRotateY = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+  return goog.vec.Mat3.setFromValues(mat, c, 0, -s, 0, 1, 0, s, 0, c);
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.makeRotateZ = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+  return goog.vec.Mat3.setFromValues(mat, c, s, 0, -s, c, 0, 0, 0, 1);
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x,y,z axis.  Equivalent to:
+ * goog.vec.Mat3.multMat(
+ *     mat,
+ *     goog.vec.Mat3.makeRotate(goog.vec.Mat3.create(), angle, x, y, z),
+ *     mat);
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @param {number} x The x component of the rotation axis.
+ * @param {number} y The y component of the rotation axis.
+ * @param {number} z The z component of the rotation axis.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.rotate = function(mat, angle, x, y, z) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var cosAngle = Math.cos(angle);
+  var sinAngle = Math.sin(angle);
+  var diffCosAngle = 1 - cosAngle;
+  var r00 = x * x * diffCosAngle + cosAngle;
+  var r10 = x * y * diffCosAngle + z * sinAngle;
+  var r20 = x * z * diffCosAngle - y * sinAngle;
+
+  var r01 = x * y * diffCosAngle - z * sinAngle;
+  var r11 = y * y * diffCosAngle + cosAngle;
+  var r21 = y * z * diffCosAngle + x * sinAngle;
+
+  var r02 = x * z * diffCosAngle + y * sinAngle;
+  var r12 = y * z * diffCosAngle - x * sinAngle;
+  var r22 = z * z * diffCosAngle + cosAngle;
+
+  return goog.vec.Mat3.setFromValues(
+      mat, m00 * r00 + m01 * r10 + m02 * r20, m10 * r00 + m11 * r10 + m12 * r20,
+      m20 * r00 + m21 * r10 + m22 * r20,
+
+      m00 * r01 + m01 * r11 + m02 * r21, m10 * r01 + m11 * r11 + m12 * r21,
+      m20 * r01 + m21 * r11 + m22 * r21,
+
+      m00 * r02 + m01 * r12 + m02 * r22, m10 * r02 + m11 * r12 + m12 * r22,
+      m20 * r02 + m21 * r12 + m22 * r22);
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis.  Equivalent to:
+ * goog.vec.Mat3.multMat(
+ *     mat,
+ *     goog.vec.Mat3.makeRotateX(goog.vec.Mat3.create(), angle),
+ *     mat);
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.rotateX = function(mat, angle) {
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[3] = m01 * c + m02 * s;
+  mat[4] = m11 * c + m12 * s;
+  mat[5] = m21 * c + m22 * s;
+  mat[6] = m01 * -s + m02 * c;
+  mat[7] = m11 * -s + m12 * c;
+  mat[8] = m21 * -s + m22 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis.  Equivalent to:
+ * goog.vec.Mat3.multMat(
+ *     mat,
+ *     goog.vec.Mat3.makeRotateY(goog.vec.Mat3.create(), angle),
+ *     mat);
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.rotateY = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m02 * -s;
+  mat[1] = m10 * c + m12 * -s;
+  mat[2] = m20 * c + m22 * -s;
+  mat[6] = m00 * s + m02 * c;
+  mat[7] = m10 * s + m12 * c;
+  mat[8] = m20 * s + m22 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the z axis.  Equivalent to:
+ * goog.vec.Mat3.multMat(
+ *     mat,
+ *     goog.vec.Mat3.makeRotateZ(goog.vec.Mat3.create(), angle),
+ *     mat);
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.rotateZ = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m01 * s;
+  mat[1] = m10 * c + m11 * s;
+  mat[2] = m20 * c + m21 * s;
+  mat[3] = m00 * -s + m01 * c;
+  mat[4] = m10 * -s + m11 * c;
+  mat[5] = m20 * -s + m21 * c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix given Euler angles using
+ * the ZXZ convention.
+ * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
+ * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {number} theta1 The angle of rotation around the Z axis in radians.
+ * @param {number} theta2 The angle of rotation around the X axis in radians.
+ * @param {number} theta3 The angle of rotation around the Z axis in radians.
+ * @return {goog.vec.Mat3.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat3.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
+  var c1 = Math.cos(theta1);
+  var s1 = Math.sin(theta1);
+
+  var c2 = Math.cos(theta2);
+  var s2 = Math.sin(theta2);
+
+  var c3 = Math.cos(theta3);
+  var s3 = Math.sin(theta3);
+
+  mat[0] = c1 * c3 - c2 * s1 * s3;
+  mat[1] = c2 * c1 * s3 + c3 * s1;
+  mat[2] = s3 * s2;
+
+  mat[3] = -c1 * s3 - c3 * c2 * s1;
+  mat[4] = c1 * c2 * c3 - s1 * s3;
+  mat[5] = c3 * s2;
 
-      ax * ay * d - az * s,
-      ay * ay * d + c,
-      ay * az * d + ax * s,
+  mat[6] = s2 * s1;
+  mat[7] = -c1 * s2;
+  mat[8] = c2;
 
-      ax * az * d + ay * s,
-      ay * az * d - ax * s,
-      az * az * d + c);
   return mat;
 };
+
+
+/**
+ * Decomposes a rotation matrix into Euler angles using the ZXZ convention so
+ * that rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {goog.vec.Mat3.AnyType} mat The matrix.
+ * @param {goog.vec.Vec3.AnyType} euler The ZXZ Euler angles in
+ *     radians as [theta1, theta2, theta3].
+ * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
+ *     of the default [0, pi].
+ * @return {goog.vec.Vec3.AnyType} return euler so that operations can be
+ *     chained together.
+ */
+goog.vec.Mat3.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
+  // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
+  var sinTheta2 = Math.sqrt(mat[2] * mat[2] + mat[5] * mat[5]);
+
+  // By default we explicitely constrain theta2 to be in [0, pi],
+  // so sinTheta2 is always positive. We can change the behavior and specify
+  // theta2 to be negative in [-pi, 0] with opt_Theta2IsNegative.
+  var signTheta2 = opt_theta2IsNegative ? -1 : 1;
+
+  if (sinTheta2 > goog.vec.EPSILON) {
+    euler[2] = Math.atan2(mat[2] * signTheta2, mat[5] * signTheta2);
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[8]);
+    euler[0] = Math.atan2(mat[6] * signTheta2, -mat[7] * signTheta2);
+  } else {
+    // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+    // We assume theta1 = 0 as some applications do not allow the camera to roll
+    // (i.e. have theta1 != 0).
+    euler[0] = 0;
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[8]);
+    euler[2] = Math.atan2(mat[1], mat[0]);
+  }
+
+  // Atan2 outputs angles in [-pi, pi] so we bring them back to [0, 2 * pi].
+  euler[0] = (euler[0] + Math.PI * 2) % (Math.PI * 2);
+  euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
+  // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
+  // signTheta2.
+  euler[1] =
+      ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) * signTheta2;
+
+  return euler;
+};

+ 11 - 0
public/lib/closure/vec/mat3_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Mat3Test');</script>
+<title>Closure Unit Tests - goog.vec.Mat3Test</title></head><body></body></html>

+ 451 - 0
public/lib/closure/vec/mat3_test.js

@@ -0,0 +1,451 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Mat3Test');
+goog.setTestOnly('goog.vec.Mat3Test');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Mat3');
+
+var randomMat3 = goog.vec.Mat3.createFloat32FromValues(
+    0.8025078773498535,
+    0.7559120655059814,
+    0.15274643898010254,
+    0.19196106493473053,
+    0.0890120416879654,
+    0.15422114729881287,
+    0.09754583984613419,
+    0.44862601161003113,
+    0.9196512699127197);
+
+function testDeprecatedConstructor() {
+  var m0 = goog.vec.Mat3.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+
+  var m1 = goog.vec.Mat3.createFromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+
+  var m2 = goog.vec.Mat3.createFromArray(m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m2);
+
+  var m3 = goog.vec.Mat3.createFromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m3);
+
+  var m4 = goog.vec.Mat3.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m4);
+}
+
+function testConstructor() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+
+  var m1 = goog.vec.Mat3.createFloat32FromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+
+  var m2 = goog.vec.Mat3.createFloat32FromArray(m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m2);
+
+  var m3 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m3);
+
+  var m4 = goog.vec.Mat3.createFloat32Identity();
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m4);
+
+  var n0 = goog.vec.Mat3.createFloat64();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], n0);
+
+  var n1 = goog.vec.Mat3.createFloat64FromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], n1);
+
+  var n2 = goog.vec.Mat3.createFloat64FromArray(n1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], n1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], n2);
+
+  var n3 = goog.vec.Mat3.createFloat64FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], n3);
+
+  var n4 = goog.vec.Mat3.createFloat64Identity();
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], n4);
+}
+
+function testSet() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  goog.vec.Mat3.setFromArray(m0, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  goog.vec.Mat3.setFromValues(m0, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+  assertElementsEquals([2, 3, 4, 5, 6, 7, 8, 9, 10], m0);
+}
+
+function testSetDiagonal() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setDiagonalValues(m0, 1, 2, 3);
+  assertElementsEquals([1, 0, 0, 0, 2, 0, 0, 0, 3], m0);
+
+  goog.vec.Mat3.setDiagonal(m0, [4, 5, 6]);
+  assertElementsEquals([4, 0, 0, 0, 5, 0, 0, 0, 6], m0);
+}
+
+function testSetGetColumn() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setColumn(m0, 0, [1, 2, 3]);
+  goog.vec.Mat3.setColumn(m0, 1, [4, 5, 6]);
+  goog.vec.Mat3.setColumn(m0, 2, [7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  var v0 = [0, 0, 0];
+  goog.vec.Mat3.getColumn(m0, 0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+  goog.vec.Mat3.getColumn(m0, 1, v0);
+  assertElementsEquals([4, 5, 6], v0);
+  goog.vec.Mat3.getColumn(m0, 2, v0);
+  assertElementsEquals([7, 8, 9], v0);
+}
+
+function testSetGetColumns() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setColumns(m0, [1, 2, 3], [4, 5, 6], [7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  var v0 = [0, 0, 0], v1 = [0, 0, 0], v2 = [0, 0, 0];
+  goog.vec.Mat3.getColumns(m0, v0, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([7, 8, 9], v2);
+}
+
+function testSetGetRow() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setRow(m0, 0, [1, 2, 3]);
+  goog.vec.Mat3.setRow(m0, 1, [4, 5, 6]);
+  goog.vec.Mat3.setRow(m0, 2, [7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+
+  var v0 = [0, 0, 0];
+  goog.vec.Mat3.getRow(m0, 0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+  goog.vec.Mat3.getRow(m0, 1, v0);
+  assertElementsEquals([4, 5, 6], v0);
+  goog.vec.Mat3.getRow(m0, 2, v0);
+  assertElementsEquals([7, 8, 9], v0);
+}
+
+function testSetGetRows() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setRows(m0, [1, 2, 3], [4, 5, 6], [7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+
+  var v0 = [0, 0, 0], v1 = [0, 0, 0], v2 = [0, 0, 0];
+  goog.vec.Mat3.getRows(m0, v0, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([7, 8, 9], v2);
+}
+
+function testSetRowMajorArray() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setFromRowMajorArray(
+      m0, [1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+}
+
+function testMakeZero() {
+  var m0 = goog.vec.Mat3.createFromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  goog.vec.Mat3.makeZero(m0);
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+}
+
+function testMakeIdentity() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.makeIdentity(m0);
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m0);
+}
+
+function testSetGetElement() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  for (var r = 0; r < 3; r++) {
+    for (var c = 0; c < 3; c++) {
+      var value = c * 3 + r + 1;
+      goog.vec.Mat3.setElement(m0, r, c, value);
+      assertEquals(value, goog.vec.Mat3.getElement(m0, r, c));
+    }
+  }
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+}
+
+function testAddMat() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.Mat3.createFloat32FromValues(3, 4, 5, 6, 7, 8, 9, 1, 2);
+  var m2 = goog.vec.Mat3.create();
+  goog.vec.Mat3.addMat(m0, m1, m2);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([4, 6, 8, 10, 12, 14, 16, 9, 11], m2);
+
+  goog.vec.Mat3.addMat(m0, m1, m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([4, 6, 8, 10, 12, 14, 16, 9, 11], m0);
+}
+
+function testSubMat() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.Mat3.createFloat32FromValues(3, 4, 5, 6, 7, 8, 9, 1, 2);
+  var m2 = goog.vec.Mat3.create();
+
+  goog.vec.Mat3.subMat(m0, m1, m2);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([-2, -2, -2, -2, -2, -2, -2, 7, 7], m2);
+
+  goog.vec.Mat3.subMat(m1, m0, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([2, 2, 2, 2, 2, 2, 2, -7, -7], m1);
+}
+
+function testMultScalar() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.Mat3.createFloat32();
+
+  goog.vec.Mat3.multScalar(m0, 5, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([5, 10, 15, 20, 25, 30, 35, 40, 45], m1);
+
+  goog.vec.Mat3.multScalar(m0, 5, m0);
+  assertElementsEquals([5, 10, 15, 20, 25, 30, 35, 40, 45], m0);
+}
+
+function testMultMat() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m2 = goog.vec.Mat3.create();
+
+  goog.vec.Mat3.multMat(m0, m1, m2);
+  assertElementsEquals([30, 36, 42, 66, 81, 96, 102, 126, 150], m2);
+
+  goog.vec.Mat3.addMat(m0, m1, m1);
+  goog.vec.Mat3.multMat(m0, m1, m1);
+  assertElementsEquals([60, 72, 84, 132, 162, 192, 204, 252, 300], m1);
+}
+
+function testTranspose() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.transpose(m0, m1);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m1);
+  goog.vec.Mat3.transpose(m1, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+}
+
+function testInvert() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertFalse(goog.vec.Mat3.invert(m0, m0));
+  assertElementsEquals([1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.Mat3.setFromValues(m0, 1, 2, 3, 1, 3, 4, 3, 4, 5);
+  assertTrue(goog.vec.Mat3.invert(m0, m0));
+  assertElementsEquals([0.5, -1.0, 0.5, -3.5, 2.0, 0.5, 2.5, -1.0, -0.5], m0);
+
+  goog.vec.Mat3.makeScale(m0, .01, .01, .01);
+  assertTrue(goog.vec.Mat3.invert(m0, m0));
+  var m1 = goog.vec.Mat3.create();
+  goog.vec.Mat3.makeScale(m1, 100, 100, 100);
+  assertElementsEquals(m1, m0);
+}
+
+function testEquals() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.Mat3.createFromArray(m0);
+  assertTrue(goog.vec.Mat3.equals(m0, m1));
+  assertTrue(goog.vec.Mat3.equals(m1, m0));
+  for (var i = 0; i < 9; i++) {
+    m1[i] = 15;
+    assertFalse(goog.vec.Mat3.equals(m0, m1));
+    assertFalse(goog.vec.Mat3.equals(m1, m0));
+    m1[i] = i + 1;
+  }
+}
+
+function testMultVec3() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.Mat3.multVec3(m0, v0, v1);
+  assertElementsEquals([30, 36, 42], v1);
+  goog.vec.Mat3.multVec3(m0, v0, v0);
+  assertElementsEquals([30, 36, 42], v0);
+}
+
+function testSetValues() {
+  var a0 = goog.vec.Mat3.createFloat32();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], a0);
+  goog.vec.Mat3.setFromValues(a0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a0);
+
+  var a1 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.setDiagonalValues(a1, 1, 2, 3);
+  assertElementsEquals([1, 0, 0, 0, 2, 0, 0, 0, 3], a1);
+
+  goog.vec.Mat3.setColumnValues(a1, 0, 2, 3, 4);
+  goog.vec.Mat3.setColumnValues(a1, 1, 5, 6, 7);
+  goog.vec.Mat3.setColumnValues(a1, 2, 8, 9, 1);
+  assertElementsEquals([2, 3, 4, 5, 6, 7, 8, 9, 1], a1);
+
+  goog.vec.Mat3.setRowValues(a1, 0, 1, 4, 7);
+  goog.vec.Mat3.setRowValues(a1, 1, 2, 5, 8);
+  goog.vec.Mat3.setRowValues(a1, 2, 3, 6, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a1);
+}
+
+function testMakeTranslate() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.makeTranslate(m0, 3, 4);
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 3, 4, 1], m0);
+}
+
+function testMakeScale() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.makeScale(m0, 3, 4, 5);
+  assertElementsEquals([3, 0, 0, 0, 4, 0, 0, 0, 5], m0);
+}
+
+function testMakeRotate() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.makeRotate(m0, Math.PI / 2, 0, 0, 1);
+  var v0 = [0, 1, 0, -1, 0, 0, 0, 0, 1];
+  assertElementsRoughlyEqual(m0, v0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.makeRotate(m1, -Math.PI / 4, 0, 0, 1);
+  goog.vec.Mat3.multMat(m0, m1, m1);
+  var v1 = [0.7071068, 0.7071068, 0, -0.7071068, 0.7071068, 0, 0, 0, 1];
+  assertElementsRoughlyEqual(m1, v1, goog.vec.EPSILON);
+}
+
+function testMakeRotateX() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFloat32();
+
+  goog.vec.Mat3.makeRotateX(m0, Math.PI / 7);
+  goog.vec.Mat3.makeRotate(m1, Math.PI / 7, 1, 0, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateY() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFloat32();
+
+  goog.vec.Mat3.makeRotateY(m0, Math.PI / 7);
+  goog.vec.Mat3.makeRotate(m1, Math.PI / 7, 0, 1, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateZ() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFloat32();
+
+  goog.vec.Mat3.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.Mat3.makeRotate(m1, Math.PI / 7, 0, 0, 1);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotate() {
+  var m0 = goog.vec.Mat3.createIdentity();
+  goog.vec.Mat3.rotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, -1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  goog.vec.Mat3.rotate(m0, -Math.PI / 4, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0,
+       -0.7071068, 0.7071068, 0,
+       0, 0, 1],
+      m0, goog.vec.EPSILON);
+}
+
+function testRotateX() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFloat32FromArray(randomMat3);
+
+  goog.vec.Mat3.makeRotateX(m0, Math.PI / 7);
+  goog.vec.Mat3.multMat(m1, m0, m0);
+  goog.vec.Mat3.rotateX(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFloat32FromArray(randomMat3);
+
+  goog.vec.Mat3.makeRotateY(m0, Math.PI / 7);
+  goog.vec.Mat3.multMat(m1, m0, m0);
+  goog.vec.Mat3.rotateY(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var m1 = goog.vec.Mat3.createFloat32FromArray(randomMat3);
+
+  goog.vec.Mat3.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.Mat3.multMat(m1, m0, m0);
+  goog.vec.Mat3.rotateZ(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeEulerZXZ() {
+  var m0 = goog.vec.Mat3.createFloat32();
+  var roll = 0.200982 * 2 * Math.PI;
+  var tilt = 0.915833 * Math.PI;
+  var yaw = 0.839392 * 2 * Math.PI;
+
+  goog.vec.Mat3.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.Mat3.rotate(m0, tilt, 1, 0, 0);
+  goog.vec.Mat3.rotate(m0, yaw, 0, 0, 1);
+
+  var m1 = goog.vec.Mat3.createFloat32();
+  goog.vec.Mat3.makeEulerZXZ(m1, roll, tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+
+  var euler = [0, 0, 0];
+  goog.vec.Mat3.toEulerZXZ(m0, euler);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+
+  // Test negative tilt now.
+  goog.vec.Mat3.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.Mat3.rotate(m0, -tilt, 1, 0, 0);
+  goog.vec.Mat3.rotate(m0, yaw, 0, 0, 1);
+
+  goog.vec.Mat3.makeEulerZXZ(m1, roll, -tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+  var euler = [0, 0, 0];
+  goog.vec.Mat3.toEulerZXZ(m0, euler, true);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(-tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+}
+
+function testEulerZXZExtrema() {
+  var m0 = goog.vec.Mat3.createFloat32FromArray(
+      [1, 0, 0, 0, 0, -1, 0, 1, 0]);
+  var m1 = goog.vec.Mat3.createFloat32FromArray(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+  var euler = [0, 0, 0];
+  goog.vec.Mat3.toEulerZXZ(m0, euler);
+  assertElementsRoughlyEqual(
+      [Math.PI, Math.PI / 2, Math.PI], euler, goog.vec.EPSILON);
+  goog.vec.Mat3.makeEulerZXZ(m1, euler[0], euler[1], euler[2]);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}

+ 1052 - 0
public/lib/closure/vec/mat3d.js

@@ -0,0 +1,1052 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat3f.js by running:            //
+//   swap_type.sh mat3d.js > mat3f.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 3x3 double (64bit)
+ * matrices.  The matrices are stored in column-major order.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.mat3d');
+goog.provide('goog.vec.mat3d.Type');
+
+goog.require('goog.vec');
+goog.require('goog.vec.vec3d.Type');
+
+
+/** @typedef {goog.vec.Float64} */ goog.vec.mat3d.Type;
+
+
+/**
+ * Creates a mat3d with all elements initialized to zero.
+ *
+ * @return {!goog.vec.mat3d.Type} The new mat3d.
+ */
+goog.vec.mat3d.create = function() {
+  return new Float64Array(9);
+};
+
+
+/**
+ * Creates a mat3d identity matrix.
+ *
+ * @return {!goog.vec.mat3d.Type} The new mat3d.
+ */
+goog.vec.mat3d.createIdentity = function() {
+  var mat = goog.vec.mat3d.create();
+  mat[0] = mat[4] = mat[8] = 1;
+  return mat;
+};
+
+
+/**
+ * Initializes the matrix from the set of values. Note the values supplied are
+ * in column major order.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the
+ *     values.
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setFromValues = function(
+    mat, v00, v10, v20, v01, v11, v21, v02, v12, v22) {
+  mat[0] = v00;
+  mat[1] = v10;
+  mat[2] = v20;
+  mat[3] = v01;
+  mat[4] = v11;
+  mat[5] = v21;
+  mat[6] = v02;
+  mat[7] = v12;
+  mat[8] = v22;
+  return mat;
+};
+
+
+/**
+ * Initializes mat3d mat from mat3d src.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The destination matrix.
+ * @param {!goog.vec.mat3d.Type} src The source matrix.
+ * @return {!goog.vec.mat3d.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setFromMat3d = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  return mat;
+};
+
+
+/**
+ * Initializes mat3d mat from mat3f src (typed as a Float32Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.mat3d.Type} mat The destination matrix.
+ * @param {Float32Array} src The source matrix.
+ * @return {!goog.vec.mat3d.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setFromMat3f = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  return mat;
+};
+
+
+/**
+ * Initializes mat3d mat from Array src.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The destination matrix.
+ * @param {Array<number>} src The source matrix.
+ * @return {!goog.vec.mat3d.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setFromArray = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  return mat;
+};
+
+
+/**
+ * Retrieves the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @return {number} The element value at the requested row, column indices.
+ */
+goog.vec.mat3d.getElement = function(mat, row, column) {
+  return mat[row + column * 3];
+};
+
+
+/**
+ * Sets the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @param {number} value The value to set at the requested row, column.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setElement = function(mat, row, column, value) {
+  mat[row + column * 3] = value;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given values.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {number} v00 The values for (0, 0).
+ * @param {number} v11 The values for (1, 1).
+ * @param {number} v22 The values for (2, 2).
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setDiagonalValues = function(mat, v00, v11, v22) {
+  mat[0] = v00;
+  mat[4] = v11;
+  mat[8] = v22;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given vector.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec3d.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setDiagonal = function(mat, vec) {
+  mat[0] = vec[0];
+  mat[4] = vec[1];
+  mat[8] = vec[2];
+  return mat;
+};
+
+
+/**
+ * Sets the specified column with the supplied values.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {number} v0 The value for row 0.
+ * @param {number} v1 The value for row 1.
+ * @param {number} v2 The value for row 2.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setColumnValues = function(mat, column, v0, v1, v2) {
+  var i = column * 3;
+  mat[i] = v0;
+  mat[i + 1] = v1;
+  mat[i + 2] = v2;
+  return mat;
+};
+
+
+/**
+ * Sets the specified column with the value from the supplied array.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {!goog.vec.vec3d.Type} vec The vector elements for the column.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setColumn = function(mat, column, vec) {
+  var i = column * 3;
+  mat[i] = vec[0];
+  mat[i + 1] = vec[1];
+  mat[i + 2] = vec[2];
+  return mat;
+};
+
+
+/**
+ * Retrieves the specified column from the matrix into the given vector
+ * array.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix supplying the values.
+ * @param {number} column The column to get the values from.
+ * @param {!goog.vec.vec3d.Type} vec The vector elements to receive the
+ *     column.
+ * @return {!goog.vec.vec3d.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.getColumn = function(mat, column, vec) {
+  var i = column * 3;
+  vec[0] = mat[i];
+  vec[1] = mat[i + 1];
+  vec[2] = mat[i + 2];
+  return vec;
+};
+
+
+/**
+ * Sets the columns of the matrix from the set of vector elements.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec3d.Type} vec0 The values for column 0.
+ * @param {!goog.vec.vec3d.Type} vec1 The values for column 1.
+ * @param {!goog.vec.vec3d.Type} vec2 The values for column 2.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setColumns = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3d.setColumn(mat, 0, vec0);
+  goog.vec.mat3d.setColumn(mat, 1, vec1);
+  goog.vec.mat3d.setColumn(mat, 2, vec2);
+  return /** @type {!goog.vec.mat3d.Type} */ (mat);
+};
+
+
+/**
+ * Retrieves the column values from the given matrix into the given vector
+ * elements.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix supplying the columns.
+ * @param {!goog.vec.vec3d.Type} vec0 The vector to receive column 0.
+ * @param {!goog.vec.vec3d.Type} vec1 The vector to receive column 1.
+ * @param {!goog.vec.vec3d.Type} vec2 The vector to receive column 2.
+ */
+goog.vec.mat3d.getColumns = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3d.getColumn(mat, 0, vec0);
+  goog.vec.mat3d.getColumn(mat, 1, vec1);
+  goog.vec.mat3d.getColumn(mat, 2, vec2);
+};
+
+
+/**
+ * Sets the row values from the supplied values.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {number} row The index of the row to receive the values.
+ * @param {number} v0 The value for column 0.
+ * @param {number} v1 The value for column 1.
+ * @param {number} v2 The value for column 2.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setRowValues = function(mat, row, v0, v1, v2) {
+  mat[row] = v0;
+  mat[row + 3] = v1;
+  mat[row + 6] = v2;
+  return mat;
+};
+
+
+/**
+ * Sets the row values from the supplied vector.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the row values.
+ * @param {number} row The index of the row.
+ * @param {!goog.vec.vec3d.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setRow = function(mat, row, vec) {
+  mat[row] = vec[0];
+  mat[row + 3] = vec[1];
+  mat[row + 6] = vec[2];
+  return mat;
+};
+
+
+/**
+ * Retrieves the row values into the given vector.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix supplying the values.
+ * @param {number} row The index of the row supplying the values.
+ * @param {!goog.vec.vec3d.Type} vec The vector to receive the row.
+ * @return {!goog.vec.vec3d.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.getRow = function(mat, row, vec) {
+  vec[0] = mat[row];
+  vec[1] = mat[row + 3];
+  vec[2] = mat[row + 6];
+  return vec;
+};
+
+
+/**
+ * Sets the rows of the matrix from the supplied vectors.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec3d.Type} vec0 The values for row 0.
+ * @param {!goog.vec.vec3d.Type} vec1 The values for row 1.
+ * @param {!goog.vec.vec3d.Type} vec2 The values for row 2.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.setRows = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3d.setRow(mat, 0, vec0);
+  goog.vec.mat3d.setRow(mat, 1, vec1);
+  goog.vec.mat3d.setRow(mat, 2, vec2);
+  return /** @type {!goog.vec.mat3d.Type} */ (mat);
+};
+
+
+/**
+ * Retrieves the rows of the matrix into the supplied vectors.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to supplying the values.
+ * @param {!goog.vec.vec3d.Type} vec0 The vector to receive row 0.
+ * @param {!goog.vec.vec3d.Type} vec1 The vector to receive row 1.
+ * @param {!goog.vec.vec3d.Type} vec2 The vector to receive row 2.
+ */
+goog.vec.mat3d.getRows = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3d.getRow(mat, 0, vec0);
+  goog.vec.mat3d.getRow(mat, 1, vec1);
+  goog.vec.mat3d.getRow(mat, 2, vec2);
+};
+
+
+/**
+ * Makes the given 3x3 matrix the zero matrix.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @return {!goog.vec.mat3d.Type} return mat so operations can be chained.
+ */
+goog.vec.mat3d.makeZero = function(mat) {
+  mat[0] = 0;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix the identity matrix.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @return {!goog.vec.mat3d.Type} return mat so operations can be chained.
+ */
+goog.vec.mat3d.makeIdentity = function(mat) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 1;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 1;
+  return mat;
+};
+
+
+/**
+ * Performs a per-component addition of the matrices mat0 and mat1, storing
+ * the result into resultMat.
+ *
+ * @param {!goog.vec.mat3d.Type} mat0 The first addend.
+ * @param {!goog.vec.mat3d.Type} mat1 The second addend.
+ * @param {!goog.vec.mat3d.Type} resultMat The matrix to
+ *     receive the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat3d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.addMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] + mat1[0];
+  resultMat[1] = mat0[1] + mat1[1];
+  resultMat[2] = mat0[2] + mat1[2];
+  resultMat[3] = mat0[3] + mat1[3];
+  resultMat[4] = mat0[4] + mat1[4];
+  resultMat[5] = mat0[5] + mat1[5];
+  resultMat[6] = mat0[6] + mat1[6];
+  resultMat[7] = mat0[7] + mat1[7];
+  resultMat[8] = mat0[8] + mat1[8];
+  return resultMat;
+};
+
+
+/**
+ * Performs a per-component subtraction of the matrices mat0 and mat1,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat3d.Type} mat0 The minuend.
+ * @param {!goog.vec.mat3d.Type} mat1 The subtrahend.
+ * @param {!goog.vec.mat3d.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat3d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.subMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] - mat1[0];
+  resultMat[1] = mat0[1] - mat1[1];
+  resultMat[2] = mat0[2] - mat1[2];
+  resultMat[3] = mat0[3] - mat1[3];
+  resultMat[4] = mat0[4] - mat1[4];
+  resultMat[5] = mat0[5] - mat1[5];
+  resultMat[6] = mat0[6] - mat1[6];
+  resultMat[7] = mat0[7] - mat1[7];
+  resultMat[8] = mat0[8] - mat1[8];
+  return resultMat;
+};
+
+
+/**
+ * Multiplies matrix mat0 with the given scalar, storing the result
+ * into resultMat.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} scalar The scalar value to multiple to each element of mat.
+ * @param {!goog.vec.mat3d.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat3d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.multScalar = function(mat, scalar, resultMat) {
+  resultMat[0] = mat[0] * scalar;
+  resultMat[1] = mat[1] * scalar;
+  resultMat[2] = mat[2] * scalar;
+  resultMat[3] = mat[3] * scalar;
+  resultMat[4] = mat[4] * scalar;
+  resultMat[5] = mat[5] * scalar;
+  resultMat[6] = mat[6] * scalar;
+  resultMat[7] = mat[7] * scalar;
+  resultMat[8] = mat[8] * scalar;
+  return resultMat;
+};
+
+
+/**
+ * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat3d.Type} mat0 The first (left hand) matrix.
+ * @param {!goog.vec.mat3d.Type} mat1 The second (right hand) matrix.
+ * @param {!goog.vec.mat3d.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat3d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.multMat = function(mat0, mat1, resultMat) {
+  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2];
+  var a01 = mat0[3], a11 = mat0[4], a21 = mat0[5];
+  var a02 = mat0[6], a12 = mat0[7], a22 = mat0[8];
+
+  var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2];
+  var b01 = mat1[3], b11 = mat1[4], b21 = mat1[5];
+  var b02 = mat1[6], b12 = mat1[7], b22 = mat1[8];
+
+  resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20;
+  resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20;
+  resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20;
+  resultMat[3] = a00 * b01 + a01 * b11 + a02 * b21;
+  resultMat[4] = a10 * b01 + a11 * b11 + a12 * b21;
+  resultMat[5] = a20 * b01 + a21 * b11 + a22 * b21;
+  resultMat[6] = a00 * b02 + a01 * b12 + a02 * b22;
+  resultMat[7] = a10 * b02 + a11 * b12 + a12 * b22;
+  resultMat[8] = a20 * b02 + a21 * b12 + a22 * b22;
+  return resultMat;
+};
+
+
+/**
+ * Transposes the given matrix mat storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix to transpose.
+ * @param {!goog.vec.mat3d.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat3d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.transpose = function(mat, resultMat) {
+  if (resultMat == mat) {
+    var a10 = mat[1], a20 = mat[2], a21 = mat[5];
+    resultMat[1] = mat[3];
+    resultMat[2] = mat[6];
+    resultMat[3] = a10;
+    resultMat[5] = mat[7];
+    resultMat[6] = a20;
+    resultMat[7] = a21;
+  } else {
+    resultMat[0] = mat[0];
+    resultMat[1] = mat[3];
+    resultMat[2] = mat[6];
+    resultMat[3] = mat[1];
+    resultMat[4] = mat[4];
+    resultMat[5] = mat[7];
+    resultMat[6] = mat[2];
+    resultMat[7] = mat[5];
+    resultMat[8] = mat[8];
+  }
+  return resultMat;
+};
+
+
+/**
+ * Computes the inverse of mat0 storing the result into resultMat. If the
+ * inverse is defined, this function returns true, false otherwise.
+ *
+ * @param {!goog.vec.mat3d.Type} mat0 The matrix to invert.
+ * @param {!goog.vec.mat3d.Type} resultMat The matrix to receive
+ *     the result (may be mat0).
+ * @return {boolean} True if the inverse is defined. If false is returned,
+ *     resultMat is not modified.
+ */
+goog.vec.mat3d.invert = function(mat0, resultMat) {
+  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2];
+  var a01 = mat0[3], a11 = mat0[4], a21 = mat0[5];
+  var a02 = mat0[6], a12 = mat0[7], a22 = mat0[8];
+
+  var t00 = a11 * a22 - a12 * a21;
+  var t10 = a12 * a20 - a10 * a22;
+  var t20 = a10 * a21 - a11 * a20;
+  var det = a00 * t00 + a01 * t10 + a02 * t20;
+  if (det == 0) {
+    return false;
+  }
+
+  var idet = 1 / det;
+  resultMat[0] = t00 * idet;
+  resultMat[3] = (a02 * a21 - a01 * a22) * idet;
+  resultMat[6] = (a01 * a12 - a02 * a11) * idet;
+
+  resultMat[1] = t10 * idet;
+  resultMat[4] = (a00 * a22 - a02 * a20) * idet;
+  resultMat[7] = (a02 * a10 - a00 * a12) * idet;
+
+  resultMat[2] = t20 * idet;
+  resultMat[5] = (a01 * a20 - a00 * a21) * idet;
+  resultMat[8] = (a00 * a11 - a01 * a10) * idet;
+  return true;
+};
+
+
+/**
+ * Returns true if the components of mat0 are equal to the components of mat1.
+ *
+ * @param {!goog.vec.mat3d.Type} mat0 The first matrix.
+ * @param {!goog.vec.mat3d.Type} mat1 The second matrix.
+ * @return {boolean} True if the the two matrices are equivalent.
+ */
+goog.vec.mat3d.equals = function(mat0, mat1) {
+  return mat0.length == mat1.length && mat0[0] == mat1[0] &&
+      mat0[1] == mat1[1] && mat0[2] == mat1[2] && mat0[3] == mat1[3] &&
+      mat0[4] == mat1[4] && mat0[5] == mat1[5] && mat0[6] == mat1[6] &&
+      mat0[7] == mat1[7] && mat0[8] == mat1[8];
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed matrix into resultVec.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3d.Type} vec The vector to transform.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec3d.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.multVec3 = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  resultVec[0] = x * mat[0] + y * mat[3] + z * mat[6];
+  resultVec[1] = x * mat[1] + y * mat[4] + z * mat[7];
+  resultVec[2] = x * mat[2] + y * mat[5] + z * mat[8];
+  return resultVec;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a translation matrix with x and y
+ * translation values.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeTranslate = function(mat, x, y) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 1;
+  mat[5] = 0;
+  mat[6] = x;
+  mat[7] = y;
+  mat[8] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a scale matrix with x, y, and z scale factors.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The 3x3 (9-element) matrix
+ *     array to receive the new scale matrix.
+ * @param {number} x The scale along the x axis.
+ * @param {number} y The scale along the y axis.
+ * @param {number} z The scale along the z axis.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeScale = function(mat, x, y, z) {
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = y;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = z;
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the axis defined by the vector (ax, ay, az).
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @param {number} ax The x component of the rotation axis.
+ * @param {number} ay The y component of the rotation axis.
+ * @param {number} az The z component of the rotation axis.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeRotate = function(mat, angle, ax, ay, az) {
+  var c = Math.cos(angle);
+  var d = 1 - c;
+  var s = Math.sin(angle);
+
+  mat[0] = ax * ax * d + c;
+  mat[1] = ax * ay * d + az * s;
+  mat[2] = ax * az * d - ay * s;
+  mat[3] = ax * ay * d - az * s;
+  mat[4] = ay * ay * d + c;
+  mat[5] = ay * az * d + ax * s;
+  mat[6] = ax * az * d + ay * s;
+  mat[7] = ay * az * d - ax * s;
+  mat[8] = az * az * d + c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeRotateX = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = c;
+  mat[5] = s;
+  mat[6] = 0;
+  mat[7] = -s;
+  mat[8] = c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeRotateY = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = 0;
+  mat[2] = -s;
+  mat[3] = 0;
+  mat[4] = 1;
+  mat[5] = 0;
+  mat[6] = s;
+  mat[7] = 0;
+  mat[8] = c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeRotateZ = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = s;
+  mat[2] = 0;
+  mat[3] = -s;
+  mat[4] = c;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x,y,z axis.  Equivalent to:
+ * goog.vec.mat3d.multMat(
+ *     mat,
+ *     goog.vec.mat3d.makeRotate(goog.vec.mat3d.create(), angle, x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @param {number} x The x component of the rotation axis.
+ * @param {number} y The y component of the rotation axis.
+ * @param {number} z The z component of the rotation axis.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.rotate = function(mat, angle, x, y, z) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var cosAngle = Math.cos(angle);
+  var sinAngle = Math.sin(angle);
+  var diffCosAngle = 1 - cosAngle;
+  var r00 = x * x * diffCosAngle + cosAngle;
+  var r10 = x * y * diffCosAngle + z * sinAngle;
+  var r20 = x * z * diffCosAngle - y * sinAngle;
+
+  var r01 = x * y * diffCosAngle - z * sinAngle;
+  var r11 = y * y * diffCosAngle + cosAngle;
+  var r21 = y * z * diffCosAngle + x * sinAngle;
+
+  var r02 = x * z * diffCosAngle + y * sinAngle;
+  var r12 = y * z * diffCosAngle - x * sinAngle;
+  var r22 = z * z * diffCosAngle + cosAngle;
+
+  mat[0] = m00 * r00 + m01 * r10 + m02 * r20;
+  mat[1] = m10 * r00 + m11 * r10 + m12 * r20;
+  mat[2] = m20 * r00 + m21 * r10 + m22 * r20;
+  mat[3] = m00 * r01 + m01 * r11 + m02 * r21;
+  mat[4] = m10 * r01 + m11 * r11 + m12 * r21;
+  mat[5] = m20 * r01 + m21 * r11 + m22 * r21;
+  mat[6] = m00 * r02 + m01 * r12 + m02 * r22;
+  mat[7] = m10 * r02 + m11 * r12 + m12 * r22;
+  mat[8] = m20 * r02 + m21 * r12 + m22 * r22;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis.  Equivalent to:
+ * goog.vec.mat3d.multMat(
+ *     mat,
+ *     goog.vec.mat3d.makeRotateX(goog.vec.mat3d.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.rotateX = function(mat, angle) {
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[3] = m01 * c + m02 * s;
+  mat[4] = m11 * c + m12 * s;
+  mat[5] = m21 * c + m22 * s;
+  mat[6] = m01 * -s + m02 * c;
+  mat[7] = m11 * -s + m12 * c;
+  mat[8] = m21 * -s + m22 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis.  Equivalent to:
+ * goog.vec.mat3d.multMat(
+ *     mat,
+ *     goog.vec.mat3d.makeRotateY(goog.vec.mat3d.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.rotateY = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m02 * -s;
+  mat[1] = m10 * c + m12 * -s;
+  mat[2] = m20 * c + m22 * -s;
+  mat[6] = m00 * s + m02 * c;
+  mat[7] = m10 * s + m12 * c;
+  mat[8] = m20 * s + m22 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the z axis.  Equivalent to:
+ * goog.vec.mat3d.multMat(
+ *     mat,
+ *     goog.vec.mat3d.makeRotateZ(goog.vec.mat3d.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.rotateZ = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m01 * s;
+  mat[1] = m10 * c + m11 * s;
+  mat[2] = m20 * c + m21 * s;
+  mat[3] = m00 * -s + m01 * c;
+  mat[4] = m10 * -s + m11 * c;
+  mat[5] = m20 * -s + m21 * c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix given Euler angles using
+ * the ZXZ convention.
+ * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
+ * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {number} theta1 The angle of rotation around the Z axis in radians.
+ * @param {number} theta2 The angle of rotation around the X axis in radians.
+ * @param {number} theta3 The angle of rotation around the Z axis in radians.
+ * @return {!goog.vec.mat3d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3d.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
+  var c1 = Math.cos(theta1);
+  var s1 = Math.sin(theta1);
+
+  var c2 = Math.cos(theta2);
+  var s2 = Math.sin(theta2);
+
+  var c3 = Math.cos(theta3);
+  var s3 = Math.sin(theta3);
+
+  mat[0] = c1 * c3 - c2 * s1 * s3;
+  mat[1] = c2 * c1 * s3 + c3 * s1;
+  mat[2] = s3 * s2;
+
+  mat[3] = -c1 * s3 - c3 * c2 * s1;
+  mat[4] = c1 * c2 * c3 - s1 * s3;
+  mat[5] = c3 * s2;
+
+  mat[6] = s2 * s1;
+  mat[7] = -c1 * s2;
+  mat[8] = c2;
+
+  return mat;
+};
+
+
+/**
+ * Decomposes a rotation matrix into Euler angles using the ZXZ convention so
+ * that rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {!goog.vec.mat3d.Type} mat The matrix.
+ * @param {!goog.vec.vec3d.Type} euler The ZXZ Euler angles in
+ *     radians as [theta1, theta2, theta3].
+ * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
+ *     of the default [0, pi].
+ * @return {!goog.vec.vec3d.Type} return euler so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3d.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
+  // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
+  var sinTheta2 = Math.sqrt(mat[2] * mat[2] + mat[5] * mat[5]);
+
+  // By default we explicitely constrain theta2 to be in [0, pi],
+  // so sinTheta2 is always positive. We can change the behavior and specify
+  // theta2 to be negative in [-pi, 0] with opt_Theta2IsNegative.
+  var signTheta2 = opt_theta2IsNegative ? -1 : 1;
+
+  if (sinTheta2 > goog.vec.EPSILON) {
+    euler[2] = Math.atan2(mat[2] * signTheta2, mat[5] * signTheta2);
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[8]);
+    euler[0] = Math.atan2(mat[6] * signTheta2, -mat[7] * signTheta2);
+  } else {
+    // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+    // We assume theta1 = 0 as some applications do not allow the camera to roll
+    // (i.e. have theta1 != 0).
+    euler[0] = 0;
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[8]);
+    euler[2] = Math.atan2(mat[1], mat[0]);
+  }
+
+  // Atan2 outputs angles in [-pi, pi] so we bring them back to [0, 2 * pi].
+  euler[0] = (euler[0] + Math.PI * 2) % (Math.PI * 2);
+  euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
+  // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
+  // signTheta2.
+  euler[1] =
+      ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) * signTheta2;
+
+  return euler;
+};

+ 11 - 0
public/lib/closure/vec/mat3d_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.mat3dTest');</script>
+<title>Closure Unit Tests - goog.vec.mat3dTest</title></head><body></body></html>

+ 422 - 0
public/lib/closure/vec/mat3d_test.js

@@ -0,0 +1,422 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat3f_test.js by running:       //
+//   swap_type.sh mat3d_test.js > mat3f_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.mat3dTest');
+goog.setTestOnly('goog.vec.mat3dTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.mat3d');
+
+var randommat3d = goog.vec.mat3d.setFromValues(goog.vec.mat3d.create(),
+    0.8025078773498535,
+    0.7559120655059814,
+    0.15274643898010254,
+    0.19196106493473053,
+    0.0890120416879654,
+    0.15422114729881287,
+    0.09754583984613419,
+    0.44862601161003113,
+    0.9196512699127197);
+
+function testCreate() {
+  var m = goog.vec.mat3d.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m);
+}
+
+function testCreateIdentity() {
+  var m = goog.vec.mat3d.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m);
+}
+
+function testSet() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.setFromArray(
+      goog.vec.mat3d.create(), [1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  goog.vec.mat3d.setFromArray(m0, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  goog.vec.mat3d.setFromValues(m0, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+  assertElementsEquals([2, 3, 4, 5, 6, 7, 8, 9, 10], m0);
+}
+
+function testSetDiagonal() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.setDiagonalValues(m0, 1, 2, 3);
+  assertElementsEquals([1, 0, 0, 0, 2, 0, 0, 0, 3], m0);
+
+  goog.vec.mat3d.setDiagonal(m0, [4, 5, 6]);
+  assertElementsEquals([4, 0, 0, 0, 5, 0, 0, 0, 6], m0);
+}
+
+function testSetGetColumn() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.setColumn(m0, 0, [1, 2, 3]);
+  goog.vec.mat3d.setColumn(m0, 1, [4, 5, 6]);
+  goog.vec.mat3d.setColumn(m0, 2, [7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  var v0 = [0, 0, 0];
+  goog.vec.mat3d.getColumn(m0, 0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+  goog.vec.mat3d.getColumn(m0, 1, v0);
+  assertElementsEquals([4, 5, 6], v0);
+  goog.vec.mat3d.getColumn(m0, 2, v0);
+  assertElementsEquals([7, 8, 9], v0);
+}
+
+function testSetGetColumns() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.setColumns(m0, [1, 2, 3], [4, 5, 6], [7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  var v0 = [0, 0, 0], v1 = [0, 0, 0], v2 = [0, 0, 0];
+  goog.vec.mat3d.getColumns(m0, v0, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([7, 8, 9], v2);
+}
+
+function testSetGetRow() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.setRow(m0, 0, [1, 2, 3]);
+  goog.vec.mat3d.setRow(m0, 1, [4, 5, 6]);
+  goog.vec.mat3d.setRow(m0, 2, [7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+
+  var v0 = [0, 0, 0];
+  goog.vec.mat3d.getRow(m0, 0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+  goog.vec.mat3d.getRow(m0, 1, v0);
+  assertElementsEquals([4, 5, 6], v0);
+  goog.vec.mat3d.getRow(m0, 2, v0);
+  assertElementsEquals([7, 8, 9], v0);
+}
+
+function testSetGetRows() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.setRows(m0, [1, 2, 3], [4, 5, 6], [7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+
+  var v0 = [0, 0, 0], v1 = [0, 0, 0], v2 = [0, 0, 0];
+  goog.vec.mat3d.getRows(m0, v0, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([7, 8, 9], v2);
+}
+
+function testMakeZero() {
+  var m0 = goog.vec.mat3d.setFromArray(
+      goog.vec.mat3d.create(), [1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  goog.vec.mat3d.makeZero(m0);
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+}
+
+function testMakeIdentity() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeIdentity(m0);
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m0);
+}
+
+function testSetGetElement() {
+  var m0 = goog.vec.mat3d.create();
+  for (var r = 0; r < 3; r++) {
+    for (var c = 0; c < 3; c++) {
+      var value = c * 3 + r + 1;
+      goog.vec.mat3d.setElement(m0, r, c, value);
+      assertEquals(value, goog.vec.mat3d.getElement(m0, r, c));
+    }
+  }
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+}
+
+function testAddMat() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 3, 4, 5, 6, 7, 8, 9, 1, 2);
+  var m2 = goog.vec.mat3d.create();
+  goog.vec.mat3d.addMat(m0, m1, m2);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([4, 6, 8, 10, 12, 14, 16, 9, 11], m2);
+
+  goog.vec.mat3d.addMat(m0, m1, m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([4, 6, 8, 10, 12, 14, 16, 9, 11], m0);
+}
+
+function testSubMat() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 3, 4, 5, 6, 7, 8, 9, 1, 2);
+  var m2 = goog.vec.mat3d.create();
+
+  goog.vec.mat3d.subMat(m0, m1, m2);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([-2, -2, -2, -2, -2, -2, -2, 7, 7], m2);
+
+  goog.vec.mat3d.subMat(m1, m0, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([2, 2, 2, 2, 2, 2, 2, -7, -7], m1);
+}
+
+function testMultScalar() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3d.create();
+
+  goog.vec.mat3d.multScalar(m0, 5, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([5, 10, 15, 20, 25, 30, 35, 40, 45], m1);
+
+  goog.vec.mat3d.multScalar(m0, 5, m0);
+  assertElementsEquals([5, 10, 15, 20, 25, 30, 35, 40, 45], m0);
+}
+
+function testMultMat() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m2 = goog.vec.mat3d.create();
+
+  goog.vec.mat3d.multMat(m0, m1, m2);
+  assertElementsEquals([30, 36, 42, 66, 81, 96, 102, 126, 150], m2);
+
+  goog.vec.mat3d.addMat(m0, m1, m1);
+  goog.vec.mat3d.multMat(m0, m1, m1);
+  assertElementsEquals([60, 72, 84, 132, 162, 192, 204, 252, 300], m1);
+}
+
+function testTranspose() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3d.create();
+  goog.vec.mat3d.transpose(m0, m1);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m1);
+  goog.vec.mat3d.transpose(m1, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+}
+
+function testInvert() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertFalse(goog.vec.mat3d.invert(m0, m0));
+  assertElementsEquals([1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.mat3d.setFromValues(m0, 1, 2, 3, 1, 3, 4, 3, 4, 5);
+  assertTrue(goog.vec.mat3d.invert(m0, m0));
+  assertElementsEquals([0.5, -1.0, 0.5, -3.5, 2.0, 0.5, 2.5, -1.0, -0.5], m0);
+
+  goog.vec.mat3d.makeScale(m0, .01, .01, .01);
+  assertTrue(goog.vec.mat3d.invert(m0, m0));
+  var m1 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeScale(m1, 100, 100, 100);
+  assertElementsEquals(m1, m0);
+}
+
+function testEquals() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3d.setFromArray(goog.vec.mat3d.create(), m0);
+  assertTrue(goog.vec.mat3d.equals(m0, m1));
+  assertTrue(goog.vec.mat3d.equals(m1, m0));
+  for (var i = 0; i < 9; i++) {
+    m1[i] = 15;
+    assertFalse(goog.vec.mat3d.equals(m0, m1));
+    assertFalse(goog.vec.mat3d.equals(m1, m0));
+    m1[i] = i + 1;
+  }
+}
+
+function testMultVec3() {
+  var m0 = goog.vec.mat3d.setFromValues(
+      goog.vec.mat3d.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.mat3d.multVec3(m0, v0, v1);
+  assertElementsEquals([30, 36, 42], v1);
+  goog.vec.mat3d.multVec3(m0, v0, v0);
+  assertElementsEquals([30, 36, 42], v0);
+}
+
+function testSetValues() {
+  var a0 = goog.vec.mat3d.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], a0);
+  goog.vec.mat3d.setFromValues(a0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a0);
+
+  var a1 = goog.vec.mat3d.create();
+  goog.vec.mat3d.setDiagonalValues(a1, 1, 2, 3);
+  assertElementsEquals([1, 0, 0, 0, 2, 0, 0, 0, 3], a1);
+
+  goog.vec.mat3d.setColumnValues(a1, 0, 2, 3, 4);
+  goog.vec.mat3d.setColumnValues(a1, 1, 5, 6, 7);
+  goog.vec.mat3d.setColumnValues(a1, 2, 8, 9, 1);
+  assertElementsEquals([2, 3, 4, 5, 6, 7, 8, 9, 1], a1);
+
+  goog.vec.mat3d.setRowValues(a1, 0, 1, 4, 7);
+  goog.vec.mat3d.setRowValues(a1, 1, 2, 5, 8);
+  goog.vec.mat3d.setRowValues(a1, 2, 3, 6, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a1);
+}
+
+function testMakeTranslate() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeTranslate(m0, 3, 4);
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 3, 4, 1], m0);
+}
+
+function testMakeScale() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeScale(m0, 3, 4, 5);
+  assertElementsEquals([3, 0, 0, 0, 4, 0, 0, 0, 5], m0);
+}
+
+function testMakeRotate() {
+  var m0 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeRotate(m0, Math.PI / 2, 0, 0, 1);
+  var v0 = [0, 1, 0, -1, 0, 0, 0, 0, 1];
+  assertElementsRoughlyEqual(m0, v0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeRotate(m1, -Math.PI / 4, 0, 0, 1);
+  goog.vec.mat3d.multMat(m0, m1, m1);
+  var v1 = [0.7071068, 0.7071068, 0, -0.7071068, 0.7071068, 0, 0, 0, 1];
+  assertElementsRoughlyEqual(m1, v1, goog.vec.EPSILON);
+}
+
+function testMakeRotateX() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.create();
+
+  goog.vec.mat3d.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat3d.makeRotate(m1, Math.PI / 7, 1, 0, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateY() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.create();
+
+  goog.vec.mat3d.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat3d.makeRotate(m1, Math.PI / 7, 0, 1, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateZ() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.create();
+
+  goog.vec.mat3d.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat3d.makeRotate(m1, Math.PI / 7, 0, 0, 1);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotate() {
+  var m0 = goog.vec.mat3d.makeIdentity(goog.vec.mat3d.create());
+  goog.vec.mat3d.rotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, -1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  goog.vec.mat3d.rotate(m0, -Math.PI / 4, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0,
+       -0.7071068, 0.7071068, 0,
+       0, 0, 1],
+      m0, goog.vec.EPSILON);
+}
+
+function testRotateX() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.setFromArray(goog.vec.mat3d.create(), randommat3d);
+
+  goog.vec.mat3d.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat3d.multMat(m1, m0, m0);
+  goog.vec.mat3d.rotateX(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.setFromArray(goog.vec.mat3d.create(), randommat3d);
+
+  goog.vec.mat3d.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat3d.multMat(m1, m0, m0);
+  goog.vec.mat3d.rotateY(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var m0 = goog.vec.mat3d.create();
+  var m1 = goog.vec.mat3d.setFromArray(goog.vec.mat3d.create(), randommat3d);
+
+  goog.vec.mat3d.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat3d.multMat(m1, m0, m0);
+  goog.vec.mat3d.rotateZ(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeEulerZXZ() {
+  var m0 = goog.vec.mat3d.create();
+  var roll = 0.200982 * 2 * Math.PI;
+  var tilt = 0.915833 * Math.PI;
+  var yaw = 0.839392 * 2 * Math.PI;
+
+  goog.vec.mat3d.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat3d.rotate(m0, tilt, 1, 0, 0);
+  goog.vec.mat3d.rotate(m0, yaw, 0, 0, 1);
+
+  var m1 = goog.vec.mat3d.create();
+  goog.vec.mat3d.makeEulerZXZ(m1, roll, tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+
+  var euler = [0, 0, 0];
+  goog.vec.mat3d.toEulerZXZ(m0, euler);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+
+  // Test negative tilt now.
+  goog.vec.mat3d.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat3d.rotate(m0, -tilt, 1, 0, 0);
+  goog.vec.mat3d.rotate(m0, yaw, 0, 0, 1);
+
+  goog.vec.mat3d.makeEulerZXZ(m1, roll, -tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat3d.toEulerZXZ(m0, euler, true);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(-tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+}
+
+function testEulerZXZExtrema() {
+  var m0 = goog.vec.mat3d.setFromArray(goog.vec.mat3d.create(),
+      [1, 0, 0, 0, 0, -1, 0, 1, 0]);
+  var m1 = goog.vec.mat3d.setFromArray(goog.vec.mat3d.create(),
+      [0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat3d.toEulerZXZ(m0, euler);
+  assertElementsRoughlyEqual(
+      [Math.PI, Math.PI / 2, Math.PI], euler, goog.vec.EPSILON);
+  goog.vec.mat3d.makeEulerZXZ(m1, euler[0], euler[1], euler[2]);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}

+ 1052 - 0
public/lib/closure/vec/mat3f.js

@@ -0,0 +1,1052 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat3d.js by running:            //
+//   swap_type.sh mat3f.js > mat3d.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 3x3 float (32bit)
+ * matrices.  The matrices are stored in column-major order.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.mat3f');
+goog.provide('goog.vec.mat3f.Type');
+
+goog.require('goog.vec');
+goog.require('goog.vec.vec3f.Type');
+
+
+/** @typedef {goog.vec.Float32} */ goog.vec.mat3f.Type;
+
+
+/**
+ * Creates a mat3f with all elements initialized to zero.
+ *
+ * @return {!goog.vec.mat3f.Type} The new mat3f.
+ */
+goog.vec.mat3f.create = function() {
+  return new Float32Array(9);
+};
+
+
+/**
+ * Creates a mat3f identity matrix.
+ *
+ * @return {!goog.vec.mat3f.Type} The new mat3f.
+ */
+goog.vec.mat3f.createIdentity = function() {
+  var mat = goog.vec.mat3f.create();
+  mat[0] = mat[4] = mat[8] = 1;
+  return mat;
+};
+
+
+/**
+ * Initializes the matrix from the set of values. Note the values supplied are
+ * in column major order.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the
+ *     values.
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setFromValues = function(
+    mat, v00, v10, v20, v01, v11, v21, v02, v12, v22) {
+  mat[0] = v00;
+  mat[1] = v10;
+  mat[2] = v20;
+  mat[3] = v01;
+  mat[4] = v11;
+  mat[5] = v21;
+  mat[6] = v02;
+  mat[7] = v12;
+  mat[8] = v22;
+  return mat;
+};
+
+
+/**
+ * Initializes mat3f mat from mat3f src.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The destination matrix.
+ * @param {!goog.vec.mat3f.Type} src The source matrix.
+ * @return {!goog.vec.mat3f.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setFromMat3f = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  return mat;
+};
+
+
+/**
+ * Initializes mat3f mat from mat3d src (typed as a Float64Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.mat3f.Type} mat The destination matrix.
+ * @param {Float64Array} src The source matrix.
+ * @return {!goog.vec.mat3f.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setFromMat3d = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  return mat;
+};
+
+
+/**
+ * Initializes mat3f mat from Array src.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The destination matrix.
+ * @param {Array<number>} src The source matrix.
+ * @return {!goog.vec.mat3f.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setFromArray = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  return mat;
+};
+
+
+/**
+ * Retrieves the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @return {number} The element value at the requested row, column indices.
+ */
+goog.vec.mat3f.getElement = function(mat, row, column) {
+  return mat[row + column * 3];
+};
+
+
+/**
+ * Sets the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @param {number} value The value to set at the requested row, column.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setElement = function(mat, row, column, value) {
+  mat[row + column * 3] = value;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given values.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {number} v00 The values for (0, 0).
+ * @param {number} v11 The values for (1, 1).
+ * @param {number} v22 The values for (2, 2).
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setDiagonalValues = function(mat, v00, v11, v22) {
+  mat[0] = v00;
+  mat[4] = v11;
+  mat[8] = v22;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given vector.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec3f.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setDiagonal = function(mat, vec) {
+  mat[0] = vec[0];
+  mat[4] = vec[1];
+  mat[8] = vec[2];
+  return mat;
+};
+
+
+/**
+ * Sets the specified column with the supplied values.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {number} v0 The value for row 0.
+ * @param {number} v1 The value for row 1.
+ * @param {number} v2 The value for row 2.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setColumnValues = function(mat, column, v0, v1, v2) {
+  var i = column * 3;
+  mat[i] = v0;
+  mat[i + 1] = v1;
+  mat[i + 2] = v2;
+  return mat;
+};
+
+
+/**
+ * Sets the specified column with the value from the supplied array.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {!goog.vec.vec3f.Type} vec The vector elements for the column.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setColumn = function(mat, column, vec) {
+  var i = column * 3;
+  mat[i] = vec[0];
+  mat[i + 1] = vec[1];
+  mat[i + 2] = vec[2];
+  return mat;
+};
+
+
+/**
+ * Retrieves the specified column from the matrix into the given vector
+ * array.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix supplying the values.
+ * @param {number} column The column to get the values from.
+ * @param {!goog.vec.vec3f.Type} vec The vector elements to receive the
+ *     column.
+ * @return {!goog.vec.vec3f.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.getColumn = function(mat, column, vec) {
+  var i = column * 3;
+  vec[0] = mat[i];
+  vec[1] = mat[i + 1];
+  vec[2] = mat[i + 2];
+  return vec;
+};
+
+
+/**
+ * Sets the columns of the matrix from the set of vector elements.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec3f.Type} vec0 The values for column 0.
+ * @param {!goog.vec.vec3f.Type} vec1 The values for column 1.
+ * @param {!goog.vec.vec3f.Type} vec2 The values for column 2.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setColumns = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3f.setColumn(mat, 0, vec0);
+  goog.vec.mat3f.setColumn(mat, 1, vec1);
+  goog.vec.mat3f.setColumn(mat, 2, vec2);
+  return /** @type {!goog.vec.mat3f.Type} */ (mat);
+};
+
+
+/**
+ * Retrieves the column values from the given matrix into the given vector
+ * elements.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix supplying the columns.
+ * @param {!goog.vec.vec3f.Type} vec0 The vector to receive column 0.
+ * @param {!goog.vec.vec3f.Type} vec1 The vector to receive column 1.
+ * @param {!goog.vec.vec3f.Type} vec2 The vector to receive column 2.
+ */
+goog.vec.mat3f.getColumns = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3f.getColumn(mat, 0, vec0);
+  goog.vec.mat3f.getColumn(mat, 1, vec1);
+  goog.vec.mat3f.getColumn(mat, 2, vec2);
+};
+
+
+/**
+ * Sets the row values from the supplied values.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {number} row The index of the row to receive the values.
+ * @param {number} v0 The value for column 0.
+ * @param {number} v1 The value for column 1.
+ * @param {number} v2 The value for column 2.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setRowValues = function(mat, row, v0, v1, v2) {
+  mat[row] = v0;
+  mat[row + 3] = v1;
+  mat[row + 6] = v2;
+  return mat;
+};
+
+
+/**
+ * Sets the row values from the supplied vector.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the row values.
+ * @param {number} row The index of the row.
+ * @param {!goog.vec.vec3f.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setRow = function(mat, row, vec) {
+  mat[row] = vec[0];
+  mat[row + 3] = vec[1];
+  mat[row + 6] = vec[2];
+  return mat;
+};
+
+
+/**
+ * Retrieves the row values into the given vector.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix supplying the values.
+ * @param {number} row The index of the row supplying the values.
+ * @param {!goog.vec.vec3f.Type} vec The vector to receive the row.
+ * @return {!goog.vec.vec3f.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.getRow = function(mat, row, vec) {
+  vec[0] = mat[row];
+  vec[1] = mat[row + 3];
+  vec[2] = mat[row + 6];
+  return vec;
+};
+
+
+/**
+ * Sets the rows of the matrix from the supplied vectors.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec3f.Type} vec0 The values for row 0.
+ * @param {!goog.vec.vec3f.Type} vec1 The values for row 1.
+ * @param {!goog.vec.vec3f.Type} vec2 The values for row 2.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.setRows = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3f.setRow(mat, 0, vec0);
+  goog.vec.mat3f.setRow(mat, 1, vec1);
+  goog.vec.mat3f.setRow(mat, 2, vec2);
+  return /** @type {!goog.vec.mat3f.Type} */ (mat);
+};
+
+
+/**
+ * Retrieves the rows of the matrix into the supplied vectors.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to supplying the values.
+ * @param {!goog.vec.vec3f.Type} vec0 The vector to receive row 0.
+ * @param {!goog.vec.vec3f.Type} vec1 The vector to receive row 1.
+ * @param {!goog.vec.vec3f.Type} vec2 The vector to receive row 2.
+ */
+goog.vec.mat3f.getRows = function(mat, vec0, vec1, vec2) {
+  goog.vec.mat3f.getRow(mat, 0, vec0);
+  goog.vec.mat3f.getRow(mat, 1, vec1);
+  goog.vec.mat3f.getRow(mat, 2, vec2);
+};
+
+
+/**
+ * Makes the given 3x3 matrix the zero matrix.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @return {!goog.vec.mat3f.Type} return mat so operations can be chained.
+ */
+goog.vec.mat3f.makeZero = function(mat) {
+  mat[0] = 0;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix the identity matrix.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @return {!goog.vec.mat3f.Type} return mat so operations can be chained.
+ */
+goog.vec.mat3f.makeIdentity = function(mat) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 1;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 1;
+  return mat;
+};
+
+
+/**
+ * Performs a per-component addition of the matrices mat0 and mat1, storing
+ * the result into resultMat.
+ *
+ * @param {!goog.vec.mat3f.Type} mat0 The first addend.
+ * @param {!goog.vec.mat3f.Type} mat1 The second addend.
+ * @param {!goog.vec.mat3f.Type} resultMat The matrix to
+ *     receive the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat3f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.addMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] + mat1[0];
+  resultMat[1] = mat0[1] + mat1[1];
+  resultMat[2] = mat0[2] + mat1[2];
+  resultMat[3] = mat0[3] + mat1[3];
+  resultMat[4] = mat0[4] + mat1[4];
+  resultMat[5] = mat0[5] + mat1[5];
+  resultMat[6] = mat0[6] + mat1[6];
+  resultMat[7] = mat0[7] + mat1[7];
+  resultMat[8] = mat0[8] + mat1[8];
+  return resultMat;
+};
+
+
+/**
+ * Performs a per-component subtraction of the matrices mat0 and mat1,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat3f.Type} mat0 The minuend.
+ * @param {!goog.vec.mat3f.Type} mat1 The subtrahend.
+ * @param {!goog.vec.mat3f.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat3f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.subMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] - mat1[0];
+  resultMat[1] = mat0[1] - mat1[1];
+  resultMat[2] = mat0[2] - mat1[2];
+  resultMat[3] = mat0[3] - mat1[3];
+  resultMat[4] = mat0[4] - mat1[4];
+  resultMat[5] = mat0[5] - mat1[5];
+  resultMat[6] = mat0[6] - mat1[6];
+  resultMat[7] = mat0[7] - mat1[7];
+  resultMat[8] = mat0[8] - mat1[8];
+  return resultMat;
+};
+
+
+/**
+ * Multiplies matrix mat0 with the given scalar, storing the result
+ * into resultMat.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} scalar The scalar value to multiple to each element of mat.
+ * @param {!goog.vec.mat3f.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat3f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.multScalar = function(mat, scalar, resultMat) {
+  resultMat[0] = mat[0] * scalar;
+  resultMat[1] = mat[1] * scalar;
+  resultMat[2] = mat[2] * scalar;
+  resultMat[3] = mat[3] * scalar;
+  resultMat[4] = mat[4] * scalar;
+  resultMat[5] = mat[5] * scalar;
+  resultMat[6] = mat[6] * scalar;
+  resultMat[7] = mat[7] * scalar;
+  resultMat[8] = mat[8] * scalar;
+  return resultMat;
+};
+
+
+/**
+ * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat3f.Type} mat0 The first (left hand) matrix.
+ * @param {!goog.vec.mat3f.Type} mat1 The second (right hand) matrix.
+ * @param {!goog.vec.mat3f.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat3f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.multMat = function(mat0, mat1, resultMat) {
+  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2];
+  var a01 = mat0[3], a11 = mat0[4], a21 = mat0[5];
+  var a02 = mat0[6], a12 = mat0[7], a22 = mat0[8];
+
+  var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2];
+  var b01 = mat1[3], b11 = mat1[4], b21 = mat1[5];
+  var b02 = mat1[6], b12 = mat1[7], b22 = mat1[8];
+
+  resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20;
+  resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20;
+  resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20;
+  resultMat[3] = a00 * b01 + a01 * b11 + a02 * b21;
+  resultMat[4] = a10 * b01 + a11 * b11 + a12 * b21;
+  resultMat[5] = a20 * b01 + a21 * b11 + a22 * b21;
+  resultMat[6] = a00 * b02 + a01 * b12 + a02 * b22;
+  resultMat[7] = a10 * b02 + a11 * b12 + a12 * b22;
+  resultMat[8] = a20 * b02 + a21 * b12 + a22 * b22;
+  return resultMat;
+};
+
+
+/**
+ * Transposes the given matrix mat storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix to transpose.
+ * @param {!goog.vec.mat3f.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat3f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.transpose = function(mat, resultMat) {
+  if (resultMat == mat) {
+    var a10 = mat[1], a20 = mat[2], a21 = mat[5];
+    resultMat[1] = mat[3];
+    resultMat[2] = mat[6];
+    resultMat[3] = a10;
+    resultMat[5] = mat[7];
+    resultMat[6] = a20;
+    resultMat[7] = a21;
+  } else {
+    resultMat[0] = mat[0];
+    resultMat[1] = mat[3];
+    resultMat[2] = mat[6];
+    resultMat[3] = mat[1];
+    resultMat[4] = mat[4];
+    resultMat[5] = mat[7];
+    resultMat[6] = mat[2];
+    resultMat[7] = mat[5];
+    resultMat[8] = mat[8];
+  }
+  return resultMat;
+};
+
+
+/**
+ * Computes the inverse of mat0 storing the result into resultMat. If the
+ * inverse is defined, this function returns true, false otherwise.
+ *
+ * @param {!goog.vec.mat3f.Type} mat0 The matrix to invert.
+ * @param {!goog.vec.mat3f.Type} resultMat The matrix to receive
+ *     the result (may be mat0).
+ * @return {boolean} True if the inverse is defined. If false is returned,
+ *     resultMat is not modified.
+ */
+goog.vec.mat3f.invert = function(mat0, resultMat) {
+  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2];
+  var a01 = mat0[3], a11 = mat0[4], a21 = mat0[5];
+  var a02 = mat0[6], a12 = mat0[7], a22 = mat0[8];
+
+  var t00 = a11 * a22 - a12 * a21;
+  var t10 = a12 * a20 - a10 * a22;
+  var t20 = a10 * a21 - a11 * a20;
+  var det = a00 * t00 + a01 * t10 + a02 * t20;
+  if (det == 0) {
+    return false;
+  }
+
+  var idet = 1 / det;
+  resultMat[0] = t00 * idet;
+  resultMat[3] = (a02 * a21 - a01 * a22) * idet;
+  resultMat[6] = (a01 * a12 - a02 * a11) * idet;
+
+  resultMat[1] = t10 * idet;
+  resultMat[4] = (a00 * a22 - a02 * a20) * idet;
+  resultMat[7] = (a02 * a10 - a00 * a12) * idet;
+
+  resultMat[2] = t20 * idet;
+  resultMat[5] = (a01 * a20 - a00 * a21) * idet;
+  resultMat[8] = (a00 * a11 - a01 * a10) * idet;
+  return true;
+};
+
+
+/**
+ * Returns true if the components of mat0 are equal to the components of mat1.
+ *
+ * @param {!goog.vec.mat3f.Type} mat0 The first matrix.
+ * @param {!goog.vec.mat3f.Type} mat1 The second matrix.
+ * @return {boolean} True if the the two matrices are equivalent.
+ */
+goog.vec.mat3f.equals = function(mat0, mat1) {
+  return mat0.length == mat1.length && mat0[0] == mat1[0] &&
+      mat0[1] == mat1[1] && mat0[2] == mat1[2] && mat0[3] == mat1[3] &&
+      mat0[4] == mat1[4] && mat0[5] == mat1[5] && mat0[6] == mat1[6] &&
+      mat0[7] == mat1[7] && mat0[8] == mat1[8];
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed matrix into resultVec.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3f.Type} vec The vector to transform.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec3f.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.multVec3 = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  resultVec[0] = x * mat[0] + y * mat[3] + z * mat[6];
+  resultVec[1] = x * mat[1] + y * mat[4] + z * mat[7];
+  resultVec[2] = x * mat[2] + y * mat[5] + z * mat[8];
+  return resultVec;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a translation matrix with x and y
+ * translation values.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeTranslate = function(mat, x, y) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 1;
+  mat[5] = 0;
+  mat[6] = x;
+  mat[7] = y;
+  mat[8] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a scale matrix with x, y, and z scale factors.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The 3x3 (9-element) matrix
+ *     array to receive the new scale matrix.
+ * @param {number} x The scale along the x axis.
+ * @param {number} y The scale along the y axis.
+ * @param {number} z The scale along the z axis.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeScale = function(mat, x, y, z) {
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = y;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = z;
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the axis defined by the vector (ax, ay, az).
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @param {number} ax The x component of the rotation axis.
+ * @param {number} ay The y component of the rotation axis.
+ * @param {number} az The z component of the rotation axis.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeRotate = function(mat, angle, ax, ay, az) {
+  var c = Math.cos(angle);
+  var d = 1 - c;
+  var s = Math.sin(angle);
+
+  mat[0] = ax * ax * d + c;
+  mat[1] = ax * ay * d + az * s;
+  mat[2] = ax * az * d - ay * s;
+  mat[3] = ax * ay * d - az * s;
+  mat[4] = ay * ay * d + c;
+  mat[5] = ay * az * d + ax * s;
+  mat[6] = ax * az * d + ay * s;
+  mat[7] = ay * az * d - ax * s;
+  mat[8] = az * az * d + c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeRotateX = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = c;
+  mat[5] = s;
+  mat[6] = 0;
+  mat[7] = -s;
+  mat[8] = c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeRotateY = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = 0;
+  mat[2] = -s;
+  mat[3] = 0;
+  mat[4] = 1;
+  mat[5] = 0;
+  mat[6] = s;
+  mat[7] = 0;
+  mat[8] = c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeRotateZ = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = s;
+  mat[2] = 0;
+  mat[3] = -s;
+  mat[4] = c;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x,y,z axis.  Equivalent to:
+ * goog.vec.mat3f.multMat(
+ *     mat,
+ *     goog.vec.mat3f.makeRotate(goog.vec.mat3f.create(), angle, x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @param {number} x The x component of the rotation axis.
+ * @param {number} y The y component of the rotation axis.
+ * @param {number} z The z component of the rotation axis.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.rotate = function(mat, angle, x, y, z) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var cosAngle = Math.cos(angle);
+  var sinAngle = Math.sin(angle);
+  var diffCosAngle = 1 - cosAngle;
+  var r00 = x * x * diffCosAngle + cosAngle;
+  var r10 = x * y * diffCosAngle + z * sinAngle;
+  var r20 = x * z * diffCosAngle - y * sinAngle;
+
+  var r01 = x * y * diffCosAngle - z * sinAngle;
+  var r11 = y * y * diffCosAngle + cosAngle;
+  var r21 = y * z * diffCosAngle + x * sinAngle;
+
+  var r02 = x * z * diffCosAngle + y * sinAngle;
+  var r12 = y * z * diffCosAngle - x * sinAngle;
+  var r22 = z * z * diffCosAngle + cosAngle;
+
+  mat[0] = m00 * r00 + m01 * r10 + m02 * r20;
+  mat[1] = m10 * r00 + m11 * r10 + m12 * r20;
+  mat[2] = m20 * r00 + m21 * r10 + m22 * r20;
+  mat[3] = m00 * r01 + m01 * r11 + m02 * r21;
+  mat[4] = m10 * r01 + m11 * r11 + m12 * r21;
+  mat[5] = m20 * r01 + m21 * r11 + m22 * r21;
+  mat[6] = m00 * r02 + m01 * r12 + m02 * r22;
+  mat[7] = m10 * r02 + m11 * r12 + m12 * r22;
+  mat[8] = m20 * r02 + m21 * r12 + m22 * r22;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis.  Equivalent to:
+ * goog.vec.mat3f.multMat(
+ *     mat,
+ *     goog.vec.mat3f.makeRotateX(goog.vec.mat3f.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.rotateX = function(mat, angle) {
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[3] = m01 * c + m02 * s;
+  mat[4] = m11 * c + m12 * s;
+  mat[5] = m21 * c + m22 * s;
+  mat[6] = m01 * -s + m02 * c;
+  mat[7] = m11 * -s + m12 * c;
+  mat[8] = m21 * -s + m22 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis.  Equivalent to:
+ * goog.vec.mat3f.multMat(
+ *     mat,
+ *     goog.vec.mat3f.makeRotateY(goog.vec.mat3f.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.rotateY = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m02 = mat[6], m12 = mat[7], m22 = mat[8];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m02 * -s;
+  mat[1] = m10 * c + m12 * -s;
+  mat[2] = m20 * c + m22 * -s;
+  mat[6] = m00 * s + m02 * c;
+  mat[7] = m10 * s + m12 * c;
+  mat[8] = m20 * s + m22 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the z axis.  Equivalent to:
+ * goog.vec.mat3f.multMat(
+ *     mat,
+ *     goog.vec.mat3f.makeRotateZ(goog.vec.mat3f.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.rotateZ = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2];
+  var m01 = mat[3], m11 = mat[4], m21 = mat[5];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m01 * s;
+  mat[1] = m10 * c + m11 * s;
+  mat[2] = m20 * c + m21 * s;
+  mat[3] = m00 * -s + m01 * c;
+  mat[4] = m10 * -s + m11 * c;
+  mat[5] = m20 * -s + m21 * c;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 3x3 matrix a rotation matrix given Euler angles using
+ * the ZXZ convention.
+ * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
+ * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {number} theta1 The angle of rotation around the Z axis in radians.
+ * @param {number} theta2 The angle of rotation around the X axis in radians.
+ * @param {number} theta3 The angle of rotation around the Z axis in radians.
+ * @return {!goog.vec.mat3f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat3f.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
+  var c1 = Math.cos(theta1);
+  var s1 = Math.sin(theta1);
+
+  var c2 = Math.cos(theta2);
+  var s2 = Math.sin(theta2);
+
+  var c3 = Math.cos(theta3);
+  var s3 = Math.sin(theta3);
+
+  mat[0] = c1 * c3 - c2 * s1 * s3;
+  mat[1] = c2 * c1 * s3 + c3 * s1;
+  mat[2] = s3 * s2;
+
+  mat[3] = -c1 * s3 - c3 * c2 * s1;
+  mat[4] = c1 * c2 * c3 - s1 * s3;
+  mat[5] = c3 * s2;
+
+  mat[6] = s2 * s1;
+  mat[7] = -c1 * s2;
+  mat[8] = c2;
+
+  return mat;
+};
+
+
+/**
+ * Decomposes a rotation matrix into Euler angles using the ZXZ convention so
+ * that rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {!goog.vec.mat3f.Type} mat The matrix.
+ * @param {!goog.vec.vec3f.Type} euler The ZXZ Euler angles in
+ *     radians as [theta1, theta2, theta3].
+ * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
+ *     of the default [0, pi].
+ * @return {!goog.vec.vec3f.Type} return euler so that operations can be
+ *     chained together.
+ */
+goog.vec.mat3f.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
+  // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
+  var sinTheta2 = Math.sqrt(mat[2] * mat[2] + mat[5] * mat[5]);
+
+  // By default we explicitely constrain theta2 to be in [0, pi],
+  // so sinTheta2 is always positive. We can change the behavior and specify
+  // theta2 to be negative in [-pi, 0] with opt_Theta2IsNegative.
+  var signTheta2 = opt_theta2IsNegative ? -1 : 1;
+
+  if (sinTheta2 > goog.vec.EPSILON) {
+    euler[2] = Math.atan2(mat[2] * signTheta2, mat[5] * signTheta2);
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[8]);
+    euler[0] = Math.atan2(mat[6] * signTheta2, -mat[7] * signTheta2);
+  } else {
+    // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+    // We assume theta1 = 0 as some applications do not allow the camera to roll
+    // (i.e. have theta1 != 0).
+    euler[0] = 0;
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[8]);
+    euler[2] = Math.atan2(mat[1], mat[0]);
+  }
+
+  // Atan2 outputs angles in [-pi, pi] so we bring them back to [0, 2 * pi].
+  euler[0] = (euler[0] + Math.PI * 2) % (Math.PI * 2);
+  euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
+  // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
+  // signTheta2.
+  euler[1] =
+      ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) * signTheta2;
+
+  return euler;
+};

+ 11 - 0
public/lib/closure/vec/mat3f_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.mat3fTest');</script>
+<title>Closure Unit Tests - goog.vec.mat3fTest</title></head><body></body></html>

+ 422 - 0
public/lib/closure/vec/mat3f_test.js

@@ -0,0 +1,422 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat3d_test.js by running:       //
+//   swap_type.sh mat3f_test.js > mat3d_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.mat3fTest');
+goog.setTestOnly('goog.vec.mat3fTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.mat3f');
+
+var randommat3f = goog.vec.mat3f.setFromValues(goog.vec.mat3f.create(),
+    0.8025078773498535,
+    0.7559120655059814,
+    0.15274643898010254,
+    0.19196106493473053,
+    0.0890120416879654,
+    0.15422114729881287,
+    0.09754583984613419,
+    0.44862601161003113,
+    0.9196512699127197);
+
+function testCreate() {
+  var m = goog.vec.mat3f.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m);
+}
+
+function testCreateIdentity() {
+  var m = goog.vec.mat3f.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m);
+}
+
+function testSet() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.setFromArray(
+      goog.vec.mat3f.create(), [1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  goog.vec.mat3f.setFromArray(m0, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  goog.vec.mat3f.setFromValues(m0, 2, 3, 4, 5, 6, 7, 8, 9, 10);
+  assertElementsEquals([2, 3, 4, 5, 6, 7, 8, 9, 10], m0);
+}
+
+function testSetDiagonal() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.setDiagonalValues(m0, 1, 2, 3);
+  assertElementsEquals([1, 0, 0, 0, 2, 0, 0, 0, 3], m0);
+
+  goog.vec.mat3f.setDiagonal(m0, [4, 5, 6]);
+  assertElementsEquals([4, 0, 0, 0, 5, 0, 0, 0, 6], m0);
+}
+
+function testSetGetColumn() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.setColumn(m0, 0, [1, 2, 3]);
+  goog.vec.mat3f.setColumn(m0, 1, [4, 5, 6]);
+  goog.vec.mat3f.setColumn(m0, 2, [7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  var v0 = [0, 0, 0];
+  goog.vec.mat3f.getColumn(m0, 0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+  goog.vec.mat3f.getColumn(m0, 1, v0);
+  assertElementsEquals([4, 5, 6], v0);
+  goog.vec.mat3f.getColumn(m0, 2, v0);
+  assertElementsEquals([7, 8, 9], v0);
+}
+
+function testSetGetColumns() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.setColumns(m0, [1, 2, 3], [4, 5, 6], [7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+
+  var v0 = [0, 0, 0], v1 = [0, 0, 0], v2 = [0, 0, 0];
+  goog.vec.mat3f.getColumns(m0, v0, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([7, 8, 9], v2);
+}
+
+function testSetGetRow() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.setRow(m0, 0, [1, 2, 3]);
+  goog.vec.mat3f.setRow(m0, 1, [4, 5, 6]);
+  goog.vec.mat3f.setRow(m0, 2, [7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+
+  var v0 = [0, 0, 0];
+  goog.vec.mat3f.getRow(m0, 0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+  goog.vec.mat3f.getRow(m0, 1, v0);
+  assertElementsEquals([4, 5, 6], v0);
+  goog.vec.mat3f.getRow(m0, 2, v0);
+  assertElementsEquals([7, 8, 9], v0);
+}
+
+function testSetGetRows() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.setRows(m0, [1, 2, 3], [4, 5, 6], [7, 8, 9]);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m0);
+
+  var v0 = [0, 0, 0], v1 = [0, 0, 0], v2 = [0, 0, 0];
+  goog.vec.mat3f.getRows(m0, v0, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([7, 8, 9], v2);
+}
+
+function testMakeZero() {
+  var m0 = goog.vec.mat3f.setFromArray(
+      goog.vec.mat3f.create(), [1, 2, 3, 4, 5, 6, 7, 8, 9]);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  goog.vec.mat3f.makeZero(m0);
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+}
+
+function testMakeIdentity() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeIdentity(m0);
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 0, 0, 1], m0);
+}
+
+function testSetGetElement() {
+  var m0 = goog.vec.mat3f.create();
+  for (var r = 0; r < 3; r++) {
+    for (var c = 0; c < 3; c++) {
+      var value = c * 3 + r + 1;
+      goog.vec.mat3f.setElement(m0, r, c, value);
+      assertEquals(value, goog.vec.mat3f.getElement(m0, r, c));
+    }
+  }
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+}
+
+function testAddMat() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 3, 4, 5, 6, 7, 8, 9, 1, 2);
+  var m2 = goog.vec.mat3f.create();
+  goog.vec.mat3f.addMat(m0, m1, m2);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([4, 6, 8, 10, 12, 14, 16, 9, 11], m2);
+
+  goog.vec.mat3f.addMat(m0, m1, m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([4, 6, 8, 10, 12, 14, 16, 9, 11], m0);
+}
+
+function testSubMat() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 3, 4, 5, 6, 7, 8, 9, 1, 2);
+  var m2 = goog.vec.mat3f.create();
+
+  goog.vec.mat3f.subMat(m0, m1, m2);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([3, 4, 5, 6, 7, 8, 9, 1, 2], m1);
+  assertElementsEquals([-2, -2, -2, -2, -2, -2, -2, 7, 7], m2);
+
+  goog.vec.mat3f.subMat(m1, m0, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([2, 2, 2, 2, 2, 2, 2, -7, -7], m1);
+}
+
+function testMultScalar() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3f.create();
+
+  goog.vec.mat3f.multScalar(m0, 5, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m0);
+  assertElementsEquals([5, 10, 15, 20, 25, 30, 35, 40, 45], m1);
+
+  goog.vec.mat3f.multScalar(m0, 5, m0);
+  assertElementsEquals([5, 10, 15, 20, 25, 30, 35, 40, 45], m0);
+}
+
+function testMultMat() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m2 = goog.vec.mat3f.create();
+
+  goog.vec.mat3f.multMat(m0, m1, m2);
+  assertElementsEquals([30, 36, 42, 66, 81, 96, 102, 126, 150], m2);
+
+  goog.vec.mat3f.addMat(m0, m1, m1);
+  goog.vec.mat3f.multMat(m0, m1, m1);
+  assertElementsEquals([60, 72, 84, 132, 162, 192, 204, 252, 300], m1);
+}
+
+function testTranspose() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3f.create();
+  goog.vec.mat3f.transpose(m0, m1);
+  assertElementsEquals([1, 4, 7, 2, 5, 8, 3, 6, 9], m1);
+  goog.vec.mat3f.transpose(m1, m1);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], m1);
+}
+
+function testInvert() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertFalse(goog.vec.mat3f.invert(m0, m0));
+  assertElementsEquals([1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.mat3f.setFromValues(m0, 1, 2, 3, 1, 3, 4, 3, 4, 5);
+  assertTrue(goog.vec.mat3f.invert(m0, m0));
+  assertElementsEquals([0.5, -1.0, 0.5, -3.5, 2.0, 0.5, 2.5, -1.0, -0.5], m0);
+
+  goog.vec.mat3f.makeScale(m0, .01, .01, .01);
+  assertTrue(goog.vec.mat3f.invert(m0, m0));
+  var m1 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeScale(m1, 100, 100, 100);
+  assertElementsEquals(m1, m0);
+}
+
+function testEquals() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var m1 = goog.vec.mat3f.setFromArray(goog.vec.mat3f.create(), m0);
+  assertTrue(goog.vec.mat3f.equals(m0, m1));
+  assertTrue(goog.vec.mat3f.equals(m1, m0));
+  for (var i = 0; i < 9; i++) {
+    m1[i] = 15;
+    assertFalse(goog.vec.mat3f.equals(m0, m1));
+    assertFalse(goog.vec.mat3f.equals(m1, m0));
+    m1[i] = i + 1;
+  }
+}
+
+function testMultVec3() {
+  var m0 = goog.vec.mat3f.setFromValues(
+      goog.vec.mat3f.create(), 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.mat3f.multVec3(m0, v0, v1);
+  assertElementsEquals([30, 36, 42], v1);
+  goog.vec.mat3f.multVec3(m0, v0, v0);
+  assertElementsEquals([30, 36, 42], v0);
+}
+
+function testSetValues() {
+  var a0 = goog.vec.mat3f.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0], a0);
+  goog.vec.mat3f.setFromValues(a0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a0);
+
+  var a1 = goog.vec.mat3f.create();
+  goog.vec.mat3f.setDiagonalValues(a1, 1, 2, 3);
+  assertElementsEquals([1, 0, 0, 0, 2, 0, 0, 0, 3], a1);
+
+  goog.vec.mat3f.setColumnValues(a1, 0, 2, 3, 4);
+  goog.vec.mat3f.setColumnValues(a1, 1, 5, 6, 7);
+  goog.vec.mat3f.setColumnValues(a1, 2, 8, 9, 1);
+  assertElementsEquals([2, 3, 4, 5, 6, 7, 8, 9, 1], a1);
+
+  goog.vec.mat3f.setRowValues(a1, 0, 1, 4, 7);
+  goog.vec.mat3f.setRowValues(a1, 1, 2, 5, 8);
+  goog.vec.mat3f.setRowValues(a1, 2, 3, 6, 9);
+  assertElementsEquals([1, 2, 3, 4, 5, 6, 7, 8, 9], a1);
+}
+
+function testMakeTranslate() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeTranslate(m0, 3, 4);
+  assertElementsEquals([1, 0, 0, 0, 1, 0, 3, 4, 1], m0);
+}
+
+function testMakeScale() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeScale(m0, 3, 4, 5);
+  assertElementsEquals([3, 0, 0, 0, 4, 0, 0, 0, 5], m0);
+}
+
+function testMakeRotate() {
+  var m0 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeRotate(m0, Math.PI / 2, 0, 0, 1);
+  var v0 = [0, 1, 0, -1, 0, 0, 0, 0, 1];
+  assertElementsRoughlyEqual(m0, v0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeRotate(m1, -Math.PI / 4, 0, 0, 1);
+  goog.vec.mat3f.multMat(m0, m1, m1);
+  var v1 = [0.7071068, 0.7071068, 0, -0.7071068, 0.7071068, 0, 0, 0, 1];
+  assertElementsRoughlyEqual(m1, v1, goog.vec.EPSILON);
+}
+
+function testMakeRotateX() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.create();
+
+  goog.vec.mat3f.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat3f.makeRotate(m1, Math.PI / 7, 1, 0, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateY() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.create();
+
+  goog.vec.mat3f.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat3f.makeRotate(m1, Math.PI / 7, 0, 1, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateZ() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.create();
+
+  goog.vec.mat3f.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat3f.makeRotate(m1, Math.PI / 7, 0, 0, 1);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotate() {
+  var m0 = goog.vec.mat3f.makeIdentity(goog.vec.mat3f.create());
+  goog.vec.mat3f.rotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, -1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  goog.vec.mat3f.rotate(m0, -Math.PI / 4, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0,
+       -0.7071068, 0.7071068, 0,
+       0, 0, 1],
+      m0, goog.vec.EPSILON);
+}
+
+function testRotateX() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.setFromArray(goog.vec.mat3f.create(), randommat3f);
+
+  goog.vec.mat3f.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat3f.multMat(m1, m0, m0);
+  goog.vec.mat3f.rotateX(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.setFromArray(goog.vec.mat3f.create(), randommat3f);
+
+  goog.vec.mat3f.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat3f.multMat(m1, m0, m0);
+  goog.vec.mat3f.rotateY(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var m0 = goog.vec.mat3f.create();
+  var m1 = goog.vec.mat3f.setFromArray(goog.vec.mat3f.create(), randommat3f);
+
+  goog.vec.mat3f.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat3f.multMat(m1, m0, m0);
+  goog.vec.mat3f.rotateZ(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeEulerZXZ() {
+  var m0 = goog.vec.mat3f.create();
+  var roll = 0.200982 * 2 * Math.PI;
+  var tilt = 0.915833 * Math.PI;
+  var yaw = 0.839392 * 2 * Math.PI;
+
+  goog.vec.mat3f.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat3f.rotate(m0, tilt, 1, 0, 0);
+  goog.vec.mat3f.rotate(m0, yaw, 0, 0, 1);
+
+  var m1 = goog.vec.mat3f.create();
+  goog.vec.mat3f.makeEulerZXZ(m1, roll, tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+
+  var euler = [0, 0, 0];
+  goog.vec.mat3f.toEulerZXZ(m0, euler);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+
+  // Test negative tilt now.
+  goog.vec.mat3f.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat3f.rotate(m0, -tilt, 1, 0, 0);
+  goog.vec.mat3f.rotate(m0, yaw, 0, 0, 1);
+
+  goog.vec.mat3f.makeEulerZXZ(m1, roll, -tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat3f.toEulerZXZ(m0, euler, true);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(-tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+}
+
+function testEulerZXZExtrema() {
+  var m0 = goog.vec.mat3f.setFromArray(goog.vec.mat3f.create(),
+      [1, 0, 0, 0, 0, -1, 0, 1, 0]);
+  var m1 = goog.vec.mat3f.setFromArray(goog.vec.mat3f.create(),
+      [0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat3f.toEulerZXZ(m0, euler);
+  assertElementsRoughlyEqual(
+      [Math.PI, Math.PI / 2, Math.PI], euler, goog.vec.EPSILON);
+  goog.vec.mat3f.makeEulerZXZ(m1, euler[0], euler[1], euler[2]);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}

+ 295 - 147
public/lib/closure/vec/mat4.js

@@ -20,6 +20,8 @@
  * where noted. Matrix operations follow the mathematical form when multiplying
  * vectors as follows: resultVec = matrix * vec.
  *
+ * The matrices are stored in column-major order.
+ *
  */
 goog.provide('goog.vec.Mat4');
 
@@ -34,7 +36,7 @@ goog.require('goog.vec.Vec4');
 /** @typedef {goog.vec.AnyType} */ goog.vec.Mat4.AnyType;
 
 // The following two types are deprecated - use the above types instead.
-/** @typedef {Float32Array} */ goog.vec.Mat4.Type;
+/** @typedef {!Float32Array} */ goog.vec.Mat4.Type;
 /** @typedef {goog.vec.ArrayType} */ goog.vec.Mat4.Mat4Like;
 
 
@@ -69,11 +71,8 @@ goog.vec.Mat4.createFloat64 = function() {
  */
 goog.vec.Mat4.createNumber = function() {
   var a = new Array(16);
-  goog.vec.Mat4.setFromValues(a,
-                              0, 0, 0, 0,
-                              0, 0, 0, 0,
-                              0, 0, 0, 0,
-                              0, 0, 0, 0);
+  goog.vec.Mat4.setFromValues(
+      a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
   return a;
 };
 
@@ -122,11 +121,8 @@ goog.vec.Mat4.createFloat64Identity = function() {
  */
 goog.vec.Mat4.createNumberIdentity = function() {
   var a = new Array(16);
-  goog.vec.Mat4.setFromValues(a,
-                              1, 0, 0, 0,
-                              0, 1, 0, 0,
-                              0, 0, 1, 0,
-                              0, 0, 0, 1);
+  goog.vec.Mat4.setFromValues(
+      a, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
   return a;
 };
 
@@ -179,10 +175,8 @@ goog.vec.Mat4.createFloat32FromArray = function(matrix) {
  * @return {!goog.vec.Mat4.Float32} The new, 16 element array.
  */
 goog.vec.Mat4.createFloat32FromValues = function(
-    v00, v10, v20, v30,
-    v01, v11, v21, v31,
-    v02, v12, v22, v32,
-    v03, v13, v23, v33) {
+    v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13, v23,
+    v33) {
   var newMatrix = goog.vec.Mat4.createFloat32();
   goog.vec.Mat4.setFromValues(
       newMatrix, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
@@ -236,10 +230,8 @@ goog.vec.Mat4.createFloat64FromArray = function(matrix) {
  * @return {!goog.vec.Mat4.Float64} The new, 16 element array.
  */
 goog.vec.Mat4.createFloat64FromValues = function(
-    v00, v10, v20, v30,
-    v01, v11, v21, v31,
-    v02, v12, v22, v32,
-    v03, v13, v23, v33) {
+    v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13, v23,
+    v33) {
   var newMatrix = goog.vec.Mat4.createFloat64();
   goog.vec.Mat4.setFromValues(
       newMatrix, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
@@ -295,13 +287,11 @@ goog.vec.Mat4.createFromArray = function(matrix) {
  * @return {!goog.vec.Mat4.Type} The new, 16 element array.
  */
 goog.vec.Mat4.createFromValues = function(
-    v00, v10, v20, v30,
-    v01, v11, v21, v31,
-    v02, v12, v22, v32,
-    v03, v13, v23, v33) {
+    v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13, v23,
+    v33) {
   return goog.vec.Mat4.createFloat32FromValues(
-      v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
-      v03, v13, v23, v33);
+      v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13, v23,
+      v33);
 };
 
 
@@ -336,9 +326,12 @@ goog.vec.Mat4.getElement = function(mat, row, column) {
  * @param {number} row The row index.
  * @param {number} column The column index.
  * @param {number} value The value to set at the requested row, column.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setElement = function(mat, row, column, value) {
   mat[row + column * 4] = value;
+  return mat;
 };
 
 
@@ -364,10 +357,12 @@ goog.vec.Mat4.setElement = function(mat, row, column, value) {
  * @param {number} v13 The values at (1, 3).
  * @param {number} v23 The values at (2, 3).
  * @param {number} v33 The values at (3, 3).
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setFromValues = function(
-    mat, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
-    v03, v13, v23, v33) {
+    mat, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13,
+    v23, v33) {
   mat[0] = v00;
   mat[1] = v10;
   mat[2] = v20;
@@ -384,6 +379,7 @@ goog.vec.Mat4.setFromValues = function(
   mat[13] = v13;
   mat[14] = v23;
   mat[15] = v33;
+  return mat;
 };
 
 
@@ -393,6 +389,8 @@ goog.vec.Mat4.setFromValues = function(
  * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
  * @param {goog.vec.Mat4.AnyType} values The column major ordered
  *     array of values to store in the matrix.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setFromArray = function(mat, values) {
   mat[0] = values[0];
@@ -411,6 +409,7 @@ goog.vec.Mat4.setFromArray = function(mat, values) {
   mat[13] = values[13];
   mat[14] = values[14];
   mat[15] = values[15];
+  return mat;
 };
 
 
@@ -420,6 +419,8 @@ goog.vec.Mat4.setFromArray = function(mat, values) {
  * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
  * @param {goog.vec.Mat4.AnyType} values The row major ordered array of
  *     values to store in the matrix.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setFromRowMajorArray = function(mat, values) {
   mat[0] = values[0];
@@ -441,6 +442,8 @@ goog.vec.Mat4.setFromRowMajorArray = function(mat, values) {
   mat[13] = values[7];
   mat[14] = values[11];
   mat[15] = values[15];
+
+  return mat;
 };
 
 
@@ -452,12 +455,15 @@ goog.vec.Mat4.setFromRowMajorArray = function(mat, values) {
  * @param {number} v11 The values for (1, 1).
  * @param {number} v22 The values for (2, 2).
  * @param {number} v33 The values for (3, 3).
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setDiagonalValues = function(mat, v00, v11, v22, v33) {
   mat[0] = v00;
   mat[5] = v11;
   mat[10] = v22;
   mat[15] = v33;
+  return mat;
 };
 
 
@@ -466,12 +472,15 @@ goog.vec.Mat4.setDiagonalValues = function(mat, v00, v11, v22, v33) {
  *
  * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
  * @param {goog.vec.Vec4.AnyType} vec The vector containing the values.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setDiagonal = function(mat, vec) {
   mat[0] = vec[0];
   mat[5] = vec[1];
   mat[10] = vec[2];
   mat[15] = vec[3];
+  return mat;
 };
 
 
@@ -483,6 +492,8 @@ goog.vec.Mat4.setDiagonal = function(mat, vec) {
  * @param {number=} opt_diagonal Which diagonal to get. A value of 0 selects the
  *     main diagonal, a positive number selects a super diagonal and a negative
  *     number selects a sub diagonal.
+ * @return {goog.vec.Vec4.AnyType} return vec so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.getDiagonal = function(mat, vec, opt_diagonal) {
   if (!opt_diagonal) {
@@ -497,18 +508,21 @@ goog.vec.Mat4.getDiagonal = function(mat, vec, opt_diagonal) {
       vec[i] = mat[offset + 5 * i];
     }
   }
+  return vec;
 };
 
 
 /**
  * Sets the specified column with the supplied values.
  *
- * @param {goog.vec.Mat4.AnyType} mat The matrix to recieve the values.
+ * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
  * @param {number} column The column index to set the values on.
  * @param {number} v0 The value for row 0.
  * @param {number} v1 The value for row 1.
  * @param {number} v2 The value for row 2.
  * @param {number} v3 The value for row 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setColumnValues = function(mat, column, v0, v1, v2, v3) {
   var i = column * 4;
@@ -516,6 +530,7 @@ goog.vec.Mat4.setColumnValues = function(mat, column, v0, v1, v2, v3) {
   mat[i + 1] = v1;
   mat[i + 2] = v2;
   mat[i + 3] = v3;
+  return mat;
 };
 
 
@@ -525,6 +540,8 @@ goog.vec.Mat4.setColumnValues = function(mat, column, v0, v1, v2, v3) {
  * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the values.
  * @param {number} column The column index to set the values on.
  * @param {goog.vec.Vec4.AnyType} vec The vector of elements for the column.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setColumn = function(mat, column, vec) {
   var i = column * 4;
@@ -532,6 +549,7 @@ goog.vec.Mat4.setColumn = function(mat, column, vec) {
   mat[i + 1] = vec[1];
   mat[i + 2] = vec[2];
   mat[i + 3] = vec[3];
+  return mat;
 };
 
 
@@ -542,6 +560,8 @@ goog.vec.Mat4.setColumn = function(mat, column, vec) {
  * @param {number} column The column to get the values from.
  * @param {goog.vec.Vec4.AnyType} vec The vector of elements to
  *     receive the column.
+ * @return {goog.vec.Vec4.AnyType} return vec so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.getColumn = function(mat, column, vec) {
   var i = column * 4;
@@ -549,6 +569,7 @@ goog.vec.Mat4.getColumn = function(mat, column, vec) {
   vec[1] = mat[i + 1];
   vec[2] = mat[i + 2];
   vec[3] = mat[i + 3];
+  return vec;
 };
 
 
@@ -560,12 +581,15 @@ goog.vec.Mat4.getColumn = function(mat, column, vec) {
  * @param {goog.vec.Vec4.AnyType} vec1 The values for column 1.
  * @param {goog.vec.Vec4.AnyType} vec2 The values for column 2.
  * @param {goog.vec.Vec4.AnyType} vec3 The values for column 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setColumns = function(mat, vec0, vec1, vec2, vec3) {
   goog.vec.Mat4.setColumn(mat, 0, vec0);
   goog.vec.Mat4.setColumn(mat, 1, vec1);
   goog.vec.Mat4.setColumn(mat, 2, vec2);
   goog.vec.Mat4.setColumn(mat, 3, vec3);
+  return mat;
 };
 
 
@@ -595,12 +619,15 @@ goog.vec.Mat4.getColumns = function(mat, vec0, vec1, vec2, vec3) {
  * @param {number} v1 The value for column 1.
  * @param {number} v2 The value for column 2.
  * @param {number} v3 The value for column 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setRowValues = function(mat, row, v0, v1, v2, v3) {
   mat[row] = v0;
   mat[row + 4] = v1;
   mat[row + 8] = v2;
   mat[row + 12] = v3;
+  return mat;
 };
 
 
@@ -610,12 +637,15 @@ goog.vec.Mat4.setRowValues = function(mat, row, v0, v1, v2, v3) {
  * @param {goog.vec.Mat4.AnyType} mat The matrix to receive the row values.
  * @param {number} row The index of the row.
  * @param {goog.vec.Vec4.AnyType} vec The vector containing the values.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setRow = function(mat, row, vec) {
   mat[row] = vec[0];
   mat[row + 4] = vec[1];
   mat[row + 8] = vec[2];
   mat[row + 12] = vec[3];
+  return mat;
 };
 
 
@@ -625,12 +655,15 @@ goog.vec.Mat4.setRow = function(mat, row, vec) {
  * @param {goog.vec.Mat4.AnyType} mat The matrix supplying the values.
  * @param {number} row The index of the row supplying the values.
  * @param {goog.vec.Vec4.AnyType} vec The vector to receive the row.
+ * @return {goog.vec.Vec4.AnyType} return vec so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.getRow = function(mat, row, vec) {
   vec[0] = mat[row];
   vec[1] = mat[row + 4];
   vec[2] = mat[row + 8];
   vec[3] = mat[row + 12];
+  return vec;
 };
 
 
@@ -642,12 +675,15 @@ goog.vec.Mat4.getRow = function(mat, row, vec) {
  * @param {goog.vec.Vec4.AnyType} vec1 The values for row 1.
  * @param {goog.vec.Vec4.AnyType} vec2 The values for row 2.
  * @param {goog.vec.Vec4.AnyType} vec3 The values for row 3.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.setRows = function(mat, vec0, vec1, vec2, vec3) {
   goog.vec.Mat4.setRow(mat, 0, vec0);
   goog.vec.Mat4.setRow(mat, 1, vec1);
   goog.vec.Mat4.setRow(mat, 2, vec2);
   goog.vec.Mat4.setRow(mat, 3, vec3);
+  return mat;
 };
 
 
@@ -699,7 +735,7 @@ goog.vec.Mat4.makeZero = function(mat) {
  * Makes the given 4x4 matrix the identity matrix.
  *
  * @param {goog.vec.Mat4.AnyType} mat The matrix.
- * @return {!goog.vec.Mat4.AnyType} return mat so operations can be chained.
+ * @return {goog.vec.Mat4.AnyType} return mat so operations can be chained.
  */
 goog.vec.Mat4.makeIdentity = function(mat) {
   mat[0] = 1;
@@ -730,7 +766,7 @@ goog.vec.Mat4.makeIdentity = function(mat) {
  * @param {goog.vec.Mat4.AnyType} mat1 The second addend.
  * @param {goog.vec.Mat4.AnyType} resultMat The matrix to
  *     receive the results (may be either mat0 or mat1).
- * @return {!goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.addMat = function(mat0, mat1, resultMat) {
@@ -762,7 +798,7 @@ goog.vec.Mat4.addMat = function(mat0, mat1, resultMat) {
  * @param {goog.vec.Mat4.AnyType} mat1 The subtrahend.
  * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
  *     the results (may be either mat0 or mat1).
- * @return {!goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.subMat = function(mat0, mat1, resultMat) {
@@ -794,7 +830,7 @@ goog.vec.Mat4.subMat = function(mat0, mat1, resultMat) {
  * @param {number} scalar The scalar value to multiply to each element of mat.
  * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
  *     the results (may be mat).
- * @return {!goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.multScalar = function(mat, scalar, resultMat) {
@@ -826,7 +862,7 @@ goog.vec.Mat4.multScalar = function(mat, scalar, resultMat) {
  * @param {goog.vec.Mat4.AnyType} mat1 The second (right hand) matrix.
  * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
  *     the results (may be either mat0 or mat1).
- * @return {!goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.multMat = function(mat0, mat1, resultMat) {
@@ -869,7 +905,7 @@ goog.vec.Mat4.multMat = function(mat0, mat1, resultMat) {
  * @param {goog.vec.Mat4.AnyType} mat The matrix to transpose.
  * @param {goog.vec.Mat4.AnyType} resultMat The matrix to receive
  *     the results (may be mat).
- * @return {!goog.vec.Mat4.AnyType} return resultMat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return resultMat so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.transpose = function(mat, resultMat) {
@@ -1006,23 +1042,12 @@ goog.vec.Mat4.invert = function(mat, resultMat) {
  * @return {boolean} True if the the two matrices are equivalent.
  */
 goog.vec.Mat4.equals = function(mat0, mat1) {
-  return mat0.length == mat1.length &&
-      mat0[0] == mat1[0] &&
-      mat0[1] == mat1[1] &&
-      mat0[2] == mat1[2] &&
-      mat0[3] == mat1[3] &&
-      mat0[4] == mat1[4] &&
-      mat0[5] == mat1[5] &&
-      mat0[6] == mat1[6] &&
-      mat0[7] == mat1[7] &&
-      mat0[8] == mat1[8] &&
-      mat0[9] == mat1[9] &&
-      mat0[10] == mat1[10] &&
-      mat0[11] == mat1[11] &&
-      mat0[12] == mat1[12] &&
-      mat0[13] == mat1[13] &&
-      mat0[14] == mat1[14] &&
-      mat0[15] == mat1[15];
+  return mat0.length == mat1.length && mat0[0] == mat1[0] &&
+      mat0[1] == mat1[1] && mat0[2] == mat1[2] && mat0[3] == mat1[3] &&
+      mat0[4] == mat1[4] && mat0[5] == mat1[5] && mat0[6] == mat1[6] &&
+      mat0[7] == mat1[7] && mat0[8] == mat1[8] && mat0[9] == mat1[9] &&
+      mat0[10] == mat1[10] && mat0[11] == mat1[11] && mat0[12] == mat1[12] &&
+      mat0[13] == mat1[13] && mat0[14] == mat1[14] && mat0[15] == mat1[15];
 };
 
 
@@ -1035,7 +1060,7 @@ goog.vec.Mat4.equals = function(mat0, mat1) {
  * @param {goog.vec.Vec3.AnyType} vec The 3 element vector to transform.
  * @param {goog.vec.Vec3.AnyType} resultVec The 3 element vector to
  *     receive the results (may be vec).
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.multVec3 = function(mat, vec, resultVec) {
@@ -1057,7 +1082,7 @@ goog.vec.Mat4.multVec3 = function(mat, vec, resultVec) {
  * @param {goog.vec.Vec3.AnyType} vec The 3 element vector to transform.
  * @param {goog.vec.Vec3.AnyType} resultVec The 3 element vector to
  *     receive the results (may be vec).
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.multVec3NoTranslate = function(mat, vec, resultVec) {
@@ -1079,7 +1104,7 @@ goog.vec.Mat4.multVec3NoTranslate = function(mat, vec, resultVec) {
  * @param {goog.vec.Vec3.AnyType} vec The 3 element vector to transform.
  * @param {goog.vec.Vec3.AnyType} resultVec The 3 element vector
  *     to receive the results (may be vec).
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {goog.vec.Vec3.AnyType} return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.multVec3Projective = function(mat, vec, resultVec) {
@@ -1100,7 +1125,7 @@ goog.vec.Mat4.multVec3Projective = function(mat, vec, resultVec) {
  * @param {goog.vec.Vec4.AnyType} vec The vector to transform.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to
  *     receive the results (may be vec).
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {goog.vec.Vec4.AnyType} return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Mat4.multVec4 = function(mat, vec, resultVec) {
@@ -1126,8 +1151,7 @@ goog.vec.Mat4.multVec4 = function(mat, vec, resultVec) {
  */
 goog.vec.Mat4.makeTranslate = function(mat, x, y, z) {
   goog.vec.Mat4.makeIdentity(mat);
-  goog.vec.Mat4.setColumnValues(mat, 3, x, y, z, 1);
-  return mat;
+  return goog.vec.Mat4.setColumnValues(mat, 3, x, y, z, 1);
 };
 
 
@@ -1143,8 +1167,7 @@ goog.vec.Mat4.makeTranslate = function(mat, x, y, z) {
  */
 goog.vec.Mat4.makeScale = function(mat, x, y, z) {
   goog.vec.Mat4.makeIdentity(mat);
-  goog.vec.Mat4.setDiagonalValues(mat, x, y, z, 1);
-  return mat;
+  return goog.vec.Mat4.setDiagonalValues(mat, x, y, z, 1);
 };
 
 
@@ -1165,24 +1188,65 @@ goog.vec.Mat4.makeRotate = function(mat, angle, ax, ay, az) {
   var d = 1 - c;
   var s = Math.sin(angle);
 
-  goog.vec.Mat4.setFromValues(mat,
-      ax * ax * d + c,
-      ax * ay * d + az * s,
-      ax * az * d - ay * s,
-      0,
+  return goog.vec.Mat4.setFromValues(
+      mat, ax * ax * d + c, ax * ay * d + az * s, ax * az * d - ay * s, 0,
 
-      ax * ay * d - az * s,
-      ay * ay * d + c,
-      ay * az * d + ax * s,
-      0,
+      ax * ay * d - az * s, ay * ay * d + c, ay * az * d + ax * s, 0,
 
-      ax * az * d + ay * s,
-      ay * az * d - ax * s,
-      az * az * d + c,
-      0,
+      ax * az * d + ay * s, ay * az * d - ax * s, az * az * d + c, 0,
 
       0, 0, 0, 1);
-  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.makeRotateX = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+  return goog.vec.Mat4.setFromValues(
+      mat, 1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.makeRotateY = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+  return goog.vec.Mat4.setFromValues(
+      mat, c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1);
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.makeRotateZ = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+  return goog.vec.Mat4.setFromValues(
+      mat, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
 };
 
 
@@ -1207,18 +1271,13 @@ goog.vec.Mat4.makeFrustum = function(mat, left, right, bottom, top, near, far) {
   var c = -(far + near) / (far - near);
   var d = -(2 * far * near) / (far - near);
 
-  goog.vec.Mat4.setFromValues(mat,
-      x, 0, 0, 0,
-      0, y, 0, 0,
-      a, b, c, -1,
-      0, 0, d, 0
-  );
-  return mat;
+  return goog.vec.Mat4.setFromValues(
+      mat, x, 0, 0, 0, 0, y, 0, 0, a, b, c, -1, 0, 0, d, 0);
 };
 
 
 /**
- * Makse the given 4x4 matrix  perspective projection matrix given a
+ * Makes the given 4x4 matrix  perspective projection matrix given a
  * field of view and aspect ratio.
  *
  * @param {goog.vec.Mat4.AnyType} mat The matrix.
@@ -1235,31 +1294,27 @@ goog.vec.Mat4.makePerspective = function(mat, fovy, aspect, near, far) {
   var dz = far - near;
   var sinAngle = Math.sin(angle);
   if (dz == 0 || sinAngle == 0 || aspect == 0) {
-    return /** @type {!goog.vec.Mat4.AnyType} */ (mat);
+    return mat;
   }
 
   var cot = Math.cos(angle) / sinAngle;
-  goog.vec.Mat4.setFromValues(mat,
-      cot / aspect, 0, 0, 0,
-      0, cot, 0, 0,
-      0, 0, -(far + near) / dz, -1,
-      0, 0, -(2 * near * far) / dz, 0
-  );
-  return /** @type {!goog.vec.Mat4.AnyType} */ (mat);
+  return goog.vec.Mat4.setFromValues(
+      mat, cot / aspect, 0, 0, 0, 0, cot, 0, 0, 0, 0, -(far + near) / dz, -1, 0,
+      0, -(2 * near * far) / dz, 0);
 };
 
 
 /**
  * Makes the given 4x4 matrix an orthographic projection matrix.
  *
- * @param {!goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
  * @param {number} left The coordinate of the left clipping plane.
  * @param {number} right The coordinate of the right clipping plane.
  * @param {number} bottom The coordinate of the bottom clipping plane.
  * @param {number} top The coordinate of the top clipping plane.
  * @param {number} near The distance to the near clipping plane.
  * @param {number} far The distance to the far clipping plane.
- * @return {!goog.vec.Mat4.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
  *     chained.
  */
 goog.vec.Mat4.makeOrtho = function(mat, left, right, bottom, top, near, far) {
@@ -1270,13 +1325,8 @@ goog.vec.Mat4.makeOrtho = function(mat, left, right, bottom, top, near, far) {
   var b = -(top + bottom) / (top - bottom);
   var c = -(far + near) / (far - near);
 
-  goog.vec.Mat4.setFromValues(mat,
-      x, 0, 0, 0,
-      0, y, 0, 0,
-      0, 0, z, 0,
-      a, b, c, 1
-  );
-  return /** @type {!goog.vec.Mat4.AnyType} */ (mat);
+  return goog.vec.Mat4.setFromValues(
+      mat, x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, a, b, c, 1);
 };
 
 
@@ -1320,8 +1370,7 @@ goog.vec.Mat4.makeLookAt = function(mat, eyePt, centerPt, worldUpVec) {
   goog.vec.Mat4.setRow(mat, 1, upVec);
   goog.vec.Mat4.setRow(mat, 2, fwdVec);
   goog.vec.Mat4.setRowValues(mat, 3, 0, 0, 0, 1);
-  goog.vec.Mat4.translate(
-      mat, -eyePt[0], -eyePt[1], -eyePt[2]);
+  goog.vec.Mat4.translate(mat, -eyePt[0], -eyePt[1], -eyePt[2]);
 
   return mat;
 };
@@ -1392,11 +1441,11 @@ goog.vec.Mat4.toLookAt = function(mat, eyePt, fwdVec, worldUpVec) {
  * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
  * rotation_x(theta) means rotation around the X axis of theta radians,
  *
- * @param {!goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
  * @param {number} theta1 The angle of rotation around the Z axis in radians.
  * @param {number} theta2 The angle of rotation around the X axis in radians.
  * @param {number} theta3 The angle of rotation around the Z axis in radians.
- * @return {!goog.vec.Mat4.AnyType} return mat so that operations can be
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
  *     chained.
  */
 goog.vec.Mat4.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
@@ -1440,10 +1489,12 @@ goog.vec.Mat4.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
  * rotation_x(theta) means rotation around the X axis of theta radians.
  *
  * @param {goog.vec.Mat4.AnyType} mat The matrix.
- * @param {goog.vec.Mat4.AnyType} euler The ZXZ Euler angles in
+ * @param {goog.vec.Vec3.AnyType} euler The ZXZ Euler angles in
  *     radians as [theta1, theta2, theta3].
  * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
  *     of the default [0, pi].
+ * @return {goog.vec.Vec4.AnyType} return euler so that operations can be
+ *     chained together.
  */
 goog.vec.Mat4.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
   // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
@@ -1459,8 +1510,9 @@ goog.vec.Mat4.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
     euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
     euler[0] = Math.atan2(mat[8] * signTheta2, -mat[9] * signTheta2);
   } else {
-    // There is also an arbitrary choice for roll = 0 or pan = 0 in this case.
-    // We assume roll = 0 as some applications do not allow the camera to roll.
+    // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+    // We assume theta1 = 0 as some applications do not allow the camera to roll
+    // (i.e. have theta1 != 0).
     euler[0] = 0;
     euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
     euler[2] = Math.atan2(mat[1], mat[0]);
@@ -1471,8 +1523,10 @@ goog.vec.Mat4.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
   euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
   // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
   // signTheta2.
-  euler[1] = ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) *
-      signTheta2;
+  euler[1] =
+      ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) * signTheta2;
+
+  return euler;
 };
 
 
@@ -1491,13 +1545,11 @@ goog.vec.Mat4.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
  *     chained.
  */
 goog.vec.Mat4.translate = function(mat, x, y, z) {
-  goog.vec.Mat4.setColumnValues(
-      mat, 3,
-      mat[0] * x + mat[4] * y + mat[8] * z + mat[12],
+  return goog.vec.Mat4.setColumnValues(
+      mat, 3, mat[0] * x + mat[4] * y + mat[8] * z + mat[12],
       mat[1] * x + mat[5] * y + mat[9] * z + mat[13],
       mat[2] * x + mat[6] * y + mat[10] * z + mat[14],
       mat[3] * x + mat[7] * y + mat[11] * z + mat[15]);
-  return mat;
 };
 
 
@@ -1516,18 +1568,15 @@ goog.vec.Mat4.translate = function(mat, x, y, z) {
  *     chained.
  */
 goog.vec.Mat4.scale = function(mat, x, y, z) {
-  goog.vec.Mat4.setFromValues(
-      mat,
-      mat[0] * x, mat[1] * x, mat[2] * x, mat[3] * x,
-      mat[4] * y, mat[5] * y, mat[6] * y, mat[7] * y,
-      mat[8] * z, mat[9] * z, mat[10] * z, mat[11] * z,
-      mat[12], mat[13], mat[14], mat[15]);
-  return mat;
+  return goog.vec.Mat4.setFromValues(
+      mat, mat[0] * x, mat[1] * x, mat[2] * x, mat[3] * x, mat[4] * y,
+      mat[5] * y, mat[6] * y, mat[7] * y, mat[8] * z, mat[9] * z, mat[10] * z,
+      mat[11] * z, mat[12], mat[13], mat[14], mat[15]);
 };
 
 
 /**
- * Rotation the given matrix by angle about the x,y,z axis.  Equivalent to:
+ * Rotate the given matrix by angle about the x,y,z axis.  Equivalent to:
  * goog.vec.Mat4.multMat(
  *     mat,
  *     goog.vec.Mat4.makeRotate(goog.vec.Mat4.create(), angle, x, y, z),
@@ -1562,54 +1611,153 @@ goog.vec.Mat4.rotate = function(mat, angle, x, y, z) {
   var r12 = y * z * diffCosAngle - x * sinAngle;
   var r22 = z * z * diffCosAngle + cosAngle;
 
-  goog.vec.Mat4.setFromValues(
-      mat,
-      m00 * r00 + m01 * r10 + m02 * r20,
-      m10 * r00 + m11 * r10 + m12 * r20,
-      m20 * r00 + m21 * r10 + m22 * r20,
-      m30 * r00 + m31 * r10 + m32 * r20,
-
-      m00 * r01 + m01 * r11 + m02 * r21,
-      m10 * r01 + m11 * r11 + m12 * r21,
-      m20 * r01 + m21 * r11 + m22 * r21,
-      m30 * r01 + m31 * r11 + m32 * r21,
-
-      m00 * r02 + m01 * r12 + m02 * r22,
-      m10 * r02 + m11 * r12 + m12 * r22,
-      m20 * r02 + m21 * r12 + m22 * r22,
-      m30 * r02 + m31 * r12 + m32 * r22,
+  return goog.vec.Mat4.setFromValues(
+      mat, m00 * r00 + m01 * r10 + m02 * r20, m10 * r00 + m11 * r10 + m12 * r20,
+      m20 * r00 + m21 * r10 + m22 * r20, m30 * r00 + m31 * r10 + m32 * r20,
+
+      m00 * r01 + m01 * r11 + m02 * r21, m10 * r01 + m11 * r11 + m12 * r21,
+      m20 * r01 + m21 * r11 + m22 * r21, m30 * r01 + m31 * r11 + m32 * r21,
+
+      m00 * r02 + m01 * r12 + m02 * r22, m10 * r02 + m11 * r12 + m12 * r22,
+      m20 * r02 + m21 * r12 + m22 * r22, m30 * r02 + m31 * r12 + m32 * r22,
 
       m03, m13, m23, m33);
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis.  Equivalent to:
+ * goog.vec.Mat4.multMat(
+ *     mat,
+ *     goog.vec.Mat4.makeRotateX(goog.vec.Mat4.create(), angle),
+ *     mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.rotateX = function(mat, angle) {
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[4] = m01 * c + m02 * s;
+  mat[5] = m11 * c + m12 * s;
+  mat[6] = m21 * c + m22 * s;
+  mat[7] = m31 * c + m32 * s;
+  mat[8] = m01 * -s + m02 * c;
+  mat[9] = m11 * -s + m12 * c;
+  mat[10] = m21 * -s + m22 * c;
+  mat[11] = m31 * -s + m32 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis.  Equivalent to:
+ * goog.vec.Mat4.multMat(
+ *     mat,
+ *     goog.vec.Mat4.makeRotateY(goog.vec.Mat4.create(), angle),
+ *     mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.rotateY = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m02 * -s;
+  mat[1] = m10 * c + m12 * -s;
+  mat[2] = m20 * c + m22 * -s;
+  mat[3] = m30 * c + m32 * -s;
+  mat[8] = m00 * s + m02 * c;
+  mat[9] = m10 * s + m12 * c;
+  mat[10] = m20 * s + m22 * c;
+  mat[11] = m30 * s + m32 * c;
 
   return mat;
 };
 
 
 /**
- * @type {Array.<goog.vec.Vec3.Type>}
+ * Rotate the given matrix by angle about the z axis.  Equivalent to:
+ * goog.vec.Mat4.multMat(
+ *     mat,
+ *     goog.vec.Mat4.makeRotateZ(goog.vec.Mat4.create(), angle),
+ *     mat);
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.rotateZ = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m01 * s;
+  mat[1] = m10 * c + m11 * s;
+  mat[2] = m20 * c + m21 * s;
+  mat[3] = m30 * c + m31 * s;
+  mat[4] = m00 * -s + m01 * c;
+  mat[5] = m10 * -s + m11 * c;
+  mat[6] = m20 * -s + m21 * c;
+  mat[7] = m30 * -s + m31 * c;
+
+  return mat;
+};
+
+
+/**
+ * Retrieves the translation component of the transformation matrix.
+ *
+ * @param {goog.vec.Mat4.AnyType} mat The transformation matrix.
+ * @param {goog.vec.Vec3.AnyType} translation The vector for storing the
+ *     result.
+ * @return {goog.vec.Mat4.AnyType} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.Mat4.getTranslation = function(mat, translation) {
+  translation[0] = mat[12];
+  translation[1] = mat[13];
+  translation[2] = mat[14];
+  return translation;
+};
+
+
+/**
+ * @type {!Array<!goog.vec.Mat4.Float64>}
  * @private
  */
-goog.vec.Mat4.tmpVec3_ = [
-  goog.vec.Vec3.createFloat64(),
-  goog.vec.Vec3.createFloat64()
-];
+goog.vec.Mat4.tmpVec3_ =
+    [goog.vec.Vec3.createFloat64(), goog.vec.Vec3.createFloat64()];
 
 
 /**
- * @type {Array.<goog.vec.Vec4.Type>}
+ * @type {!Array<!goog.vec.Mat4.Float64>}
  * @private
  */
 goog.vec.Mat4.tmpVec4_ = [
-  goog.vec.Vec4.createFloat64(),
-  goog.vec.Vec4.createFloat64(),
+  goog.vec.Vec4.createFloat64(), goog.vec.Vec4.createFloat64(),
   goog.vec.Vec4.createFloat64()
 ];
 
 
 /**
- * @type {Array.<goog.vec.Mat4.Type>}
+ * @type {!Array<!goog.vec.Mat4.Float64>}
  * @private
  */
-goog.vec.Mat4.tmpMat4_ = [
-  goog.vec.Mat4.createFloat64()
-];
+goog.vec.Mat4.tmpMat4_ = [goog.vec.Mat4.createFloat64()];

+ 11 - 0
public/lib/closure/vec/mat4_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Mat4Test');</script>
+<title>Closure Unit Tests - goog.vec.Mat4Test</title></head><body></body></html>

+ 769 - 0
public/lib/closure/vec/mat4_test.js

@@ -0,0 +1,769 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Mat4Test');
+goog.setTestOnly('goog.vec.Mat4Test');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Mat4');
+goog.require('goog.vec.Vec3');
+goog.require('goog.vec.Vec4');
+
+var randomMat4 = goog.vec.Mat4.createFloat32FromValues(
+    0.8025078773498535,
+    0.7559120655059814,
+    0.15274643898010254,
+    0.19196106493473053,
+    0.0890120416879654,
+    0.15422114729881287,
+    0.09754583984613419,
+    0.44862601161003113,
+    0.9196512699127197,
+    0.5310639142990112,
+    0.8962187170982361,
+    0.280601441860199,
+    0.594650387763977,
+    0.4134795069694519,
+    0.06632178276777267,
+    0.8837796449661255);
+
+function testDeprecatedConstructor() {
+  var m0 = goog.vec.Mat4.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+
+  var m1 = goog.vec.Mat4.createFromArray(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+
+  var m2 = goog.vec.Mat4.clone(m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m2);
+
+  var m3 = goog.vec.Mat4.createFromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m3);
+
+  var m4 = goog.vec.Mat4.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m4);
+}
+
+function testConstructor() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+
+  var m1 = goog.vec.Mat4.createFloat32FromArray(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+
+  var m2 = goog.vec.Mat4.clone(m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m2);
+
+  var m3 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m3);
+
+  var m4 = goog.vec.Mat4.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m4);
+}
+
+function testSet() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32FromArray(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  goog.vec.Mat4.setFromArray(m0, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  goog.vec.Mat4.setFromValues(
+      m0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
+  assertElementsEquals(
+      [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], m0);
+}
+
+function testSetDiagonal() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setDiagonalValues(m0, 1, 2, 3, 4);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4], m0);
+
+  goog.vec.Mat4.setDiagonal(m0, [4, 5, 6, 7]);
+  assertElementsEquals(
+      [4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7], m0);
+}
+
+function testGetDiagonal() {
+  var v0 = goog.vec.Vec4.create();
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setFromArray(
+      m0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
+
+  goog.vec.Mat4.getDiagonal(m0, v0);
+  assertElementsEquals([0, 5, 10, 15], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, 1);
+  assertElementsEquals([4, 9, 14, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, 2);
+  assertElementsEquals([8, 13, 0, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, 3);
+  assertElementsEquals([12, 0, 0, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, 4);
+  assertElementsEquals([0, 0, 0, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, -1);
+  assertElementsEquals([1, 6, 11, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, -2);
+  assertElementsEquals([2, 7, 0, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, -3);
+  assertElementsEquals([3, 0, 0, 0], v0);
+
+  goog.vec.Vec4.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.Mat4.getDiagonal(m0, v0, -4);
+  assertElementsEquals([0, 0, 0, 0], v0);
+}
+
+function testSetGetColumn() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setColumn(m0, 0, [1, 2, 3, 4]);
+  goog.vec.Mat4.setColumn(m0, 1, [5, 6, 7, 8]);
+  goog.vec.Mat4.setColumn(m0, 2, [9, 10, 11, 12]);
+  goog.vec.Mat4.setColumn(m0, 3, [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  var v0 = [0, 0, 0, 0];
+  goog.vec.Mat4.getColumn(m0, 0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  goog.vec.Mat4.getColumn(m0, 1, v0);
+  assertElementsEquals([5, 6, 7, 8], v0);
+  goog.vec.Mat4.getColumn(m0, 2, v0);
+  assertElementsEquals([9, 10, 11, 12], v0);
+  goog.vec.Mat4.getColumn(m0, 3, v0);
+  assertElementsEquals([13, 14, 15, 16], v0);
+}
+
+function testSetGetColumns() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setColumns(
+      m0, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  var v0 = [0, 0, 0, 0], v1 = [0, 0, 0, 0];
+  var v2 = [0, 0, 0, 0], v3 = [0, 0, 0, 0];
+  goog.vec.Mat4.getColumns(m0, v0, v1, v2, v3);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([9, 10, 11, 12], v2);
+  assertElementsEquals([13, 14, 15, 16], v3);
+}
+
+function testSetGetRow() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setRow(m0, 0, [1, 2, 3, 4]);
+  goog.vec.Mat4.setRow(m0, 1, [5, 6, 7, 8]);
+  goog.vec.Mat4.setRow(m0, 2, [9, 10, 11, 12]);
+  goog.vec.Mat4.setRow(m0, 3, [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+
+  var v0 = [0, 0, 0, 0];
+  goog.vec.Mat4.getRow(m0, 0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  goog.vec.Mat4.getRow(m0, 1, v0);
+  assertElementsEquals([5, 6, 7, 8], v0);
+  goog.vec.Mat4.getRow(m0, 2, v0);
+  assertElementsEquals([9, 10, 11, 12], v0);
+  goog.vec.Mat4.getRow(m0, 3, v0);
+  assertElementsEquals([13, 14, 15, 16], v0);
+}
+
+function testSetGetRows() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setRows(
+      m0, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+
+  var v0 = [0, 0, 0, 0], v1 = [0, 0, 0, 0];
+  var v2 = [0, 0, 0, 0], v3 = [0, 0, 0, 0];
+  goog.vec.Mat4.getRows(m0, v0, v1, v2, v3);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([9, 10, 11, 12], v2);
+  assertElementsEquals([13, 14, 15, 16], v3);
+}
+
+function testSetRowMajorArray() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setFromRowMajorArray(
+      m0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+}
+
+function testMakeZero() {
+  var m0 = goog.vec.Mat4.createFloat32FromArray(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  goog.vec.Mat4.makeZero(m0);
+  assertElementsEquals(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+}
+
+function testMakeIdentity() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeIdentity(m0);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m0);
+}
+
+function testSetGetElement() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  for (var r = 0; r < 4; r++) {
+    for (var c = 0; c < 4; c++) {
+      var value = c * 4 + r + 1;
+      goog.vec.Mat4.setElement(m0, r, c, value);
+      assertEquals(value, goog.vec.Mat4.getElement(m0, r, c));
+    }
+  }
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+}
+
+function testAddMat() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.Mat4.createFloat32FromValues(
+      9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8);
+  var m2 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.addMat(m0, m1, m2);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [10, 12, 14, 16, 18, 20, 22, 24, 10, 12, 14, 16, 18, 20, 22, 24], m2);
+
+  goog.vec.Mat4.addMat(m0, m1, m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [10, 12, 14, 16, 18, 20, 22, 24, 10, 12, 14, 16, 18, 20, 22, 24], m0);
+}
+
+function testSubMat() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.Mat4.createFloat32FromValues(
+      9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8);
+  var m2 = goog.vec.Mat4.createFloat32();
+
+  goog.vec.Mat4.subMat(m0, m1, m2);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [-8, -8, -8, -8, -8, -8, -8, -8, 8, 8, 8, 8, 8, 8, 8, 8], m2);
+
+  goog.vec.Mat4.subMat(m1, m0, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [8, 8, 8, 8, 8, 8, 8, 8, -8, -8, -8, -8, -8, -8, -8, -8], m1);
+}
+
+function testMultScalar() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.Mat4.createFloat32();
+
+  goog.vec.Mat4.multScalar(m0, 2, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32], m1);
+
+  goog.vec.Mat4.multScalar(m0, 5, m0);
+  assertElementsEquals(
+      [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80], m0);
+}
+
+function testMultMat() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m2 = goog.vec.Mat4.createFloat32();
+
+  goog.vec.Mat4.multMat(m0, m1, m2);
+  assertElementsEquals(
+      [90, 100, 110, 120, 202, 228, 254, 280,
+       314, 356, 398, 440, 426, 484, 542, 600], m2);
+
+  goog.vec.Mat4.multScalar(m1, 2, m1);
+  goog.vec.Mat4.multMat(m1, m0, m1);
+  assertElementsEquals(
+      [180, 200, 220, 240, 404, 456, 508, 560,
+       628, 712, 796, 880, 852, 968, 1084, 1200], m1);
+}
+
+function testTranspose() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.transpose(m0, m1);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m1);
+
+  goog.vec.Mat4.transpose(m1, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+}
+
+function testDeterminant() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertEquals(0, goog.vec.Mat4.determinant(m0));
+  assertElementsEquals(
+      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.Mat4.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+  assertEquals(160, goog.vec.Mat4.determinant(m0));
+  assertElementsEquals(
+      [1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3], m0);
+}
+
+function testInvert() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertFalse(goog.vec.Mat4.invert(m0, m0));
+  assertElementsEquals(
+      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.Mat4.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+  assertTrue(goog.vec.Mat4.invert(m0, m0));
+  assertElementsRoughlyEqual(
+      [-0.225, 0.025, 0.025, 0.275, 0.025, 0.025, 0.275, -0.225,
+       0.025, 0.275, -0.225, 0.025, 0.275, -0.225, 0.025, 0.025], m0,
+       goog.vec.EPSILON);
+
+  goog.vec.Mat4.makeScale(m0, .01, .01, .01);
+  assertTrue(goog.vec.Mat4.invert(m0, m0));
+  var m1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeScale(m1, 100, 100, 100);
+  assertElementsEquals(m1, m0);
+}
+
+function testEquals() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.Mat4.clone(m0);
+  assertTrue(goog.vec.Mat4.equals(m0, m1));
+  assertTrue(goog.vec.Mat4.equals(m1, m0));
+  for (var i = 0; i < 16; i++) {
+    m1[i] = 18;
+    assertFalse(goog.vec.Mat4.equals(m0, m1));
+    assertFalse(goog.vec.Mat4.equals(m1, m0));
+    m1[i] = i + 1;
+  }
+}
+
+function testMultVec3() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.Mat4.multVec3(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([51, 58, 65], v1);
+
+  goog.vec.Mat4.multVec3(m0, v0, v0);
+  assertElementsEquals([51, 58, 65], v0);
+}
+
+function testMultVec3NoTranslate() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.Mat4.multVec3NoTranslate(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([38, 44, 50], v1);
+
+  goog.vec.Mat4.multVec3NoTranslate(m0, v0, v0);
+  assertElementsEquals([38, 44, 50], v0);
+}
+
+function testMultVec3Projective() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+  var invw = 1 / 72;
+
+  goog.vec.Mat4.multVec3Projective(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals(
+      [51 * invw, 58 * invw, 65 * invw], v1);
+
+  goog.vec.Mat4.multVec3Projective(m0, v0, v0);
+  assertElementsEquals(
+      [51 * invw, 58 * invw, 65 * invw], v0);
+}
+
+function testMultVec4() {
+  var m0 = goog.vec.Mat4.createFloat32FromValues(
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3, 4];
+  var v1 = [0, 0, 0, 0];
+
+  goog.vec.Mat4.multVec4(m0, v0, v1);
+  assertElementsEquals([90, 100, 110, 120], v1);
+  goog.vec.Mat4.multVec4(m0, v0, v0);
+  assertElementsEquals([90, 100, 110, 120], v0);
+}
+
+function testSetValues() {
+  var a0 = goog.vec.Mat4.createFloat32();
+  assertElementsEquals(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], a0);
+  a0 = goog.vec.Mat4.createFloat32FromArray(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], a0);
+
+  var a1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.setDiagonalValues(a1, 1, 2, 3, 4);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4], a1);
+
+  goog.vec.Mat4.setColumnValues(a1, 0, 2, 3, 4, 5);
+  goog.vec.Mat4.setColumnValues(a1, 1, 6, 7, 8, 9);
+  goog.vec.Mat4.setColumnValues(a1, 2, 10, 11, 12, 13);
+  goog.vec.Mat4.setColumnValues(a1, 3, 14, 15, 16, 1);
+  assertElementsEquals(
+      [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1], a1);
+
+  goog.vec.Mat4.setRowValues(a1, 0, 1, 5, 9, 13);
+  goog.vec.Mat4.setRowValues(a1, 1, 2, 6, 10, 14);
+  goog.vec.Mat4.setRowValues(a1, 2, 3, 7, 11, 15);
+  goog.vec.Mat4.setRowValues(a1, 3, 4, 8, 12, 16);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], a1);
+}
+
+function testMakeTranslate() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeTranslate(m0, 3, 4, 5);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 4, 5, 1], m0);
+}
+
+function testMakeScale() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeScale(m0, 3, 4, 5);
+  assertElementsEquals(
+      [3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1], m0);
+}
+
+function testMakeRotate() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeRotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeRotate(m1, -Math.PI / 4, 0, 0, 1);
+  goog.vec.Mat4.multMat(m0, m1, m1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0, 0,
+       -0.7071068, 0.7071068, 0, 0,
+       0, 0, 1, 0,
+       0, 0, 0, 1],
+      m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateX() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32();
+
+  goog.vec.Mat4.makeRotateX(m0, Math.PI / 7);
+  goog.vec.Mat4.makeRotate(m1, Math.PI / 7, 1, 0, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateY() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32();
+
+  goog.vec.Mat4.makeRotateY(m0, Math.PI / 7);
+  goog.vec.Mat4.makeRotate(m1, Math.PI / 7, 0, 1, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateZ() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32();
+
+  goog.vec.Mat4.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.Mat4.makeRotate(m1, Math.PI / 7, 0, 0, 1);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+
+function testTranslate() {
+  var m0 = goog.vec.Mat4.createIdentity();
+  goog.vec.Mat4.translate(m0, 3, 4, 5);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 4, 5, 1], m0);
+
+  goog.vec.Mat4.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+
+  var m1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeTranslate(m1, 5, 6, 7);
+  var m2 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.multMat(m0, m1, m2);
+  goog.vec.Mat4.translate(m0, 5, 6, 7);
+  assertElementsEquals(m2, m0);
+}
+
+function testScale() {
+  var m0 = goog.vec.Mat4.createIdentity();
+  goog.vec.Mat4.scale(m0, 3, 4, 5);
+  assertElementsEquals([3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1], m0);
+}
+
+function testRotate() {
+  var m0 = goog.vec.Mat4.createIdentity();
+  goog.vec.Mat4.rotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  goog.vec.Mat4.rotate(m0, -Math.PI / 4, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0, 0,
+       -0.7071068, 0.7071068, 0, 0,
+       0, 0, 1, 0,
+       0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+}
+
+function testRotateX() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32FromArray(randomMat4);
+
+  goog.vec.Mat4.makeRotateX(m0, Math.PI / 7);
+  goog.vec.Mat4.multMat(m1, m0, m0);
+  goog.vec.Mat4.rotateX(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32FromArray(randomMat4);
+
+  goog.vec.Mat4.makeRotateY(m0, Math.PI / 7);
+  goog.vec.Mat4.multMat(m1, m0, m0);
+  goog.vec.Mat4.rotateY(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var m1 = goog.vec.Mat4.createFloat32FromArray(randomMat4);
+
+  goog.vec.Mat4.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.Mat4.multMat(m1, m0, m0);
+  goog.vec.Mat4.rotateZ(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testGetTranslation() {
+  var mat = goog.vec.Mat4.createFloat32FromArray(randomMat4);
+  var translation = goog.vec.Vec3.createFloat32();
+  goog.vec.Mat4.getTranslation(mat, translation);
+  assertElementsRoughlyEqual(
+      [0.59465038776, 0.413479506969, 0.0663217827677],
+      translation, goog.vec.EPSILON);
+}
+
+function testMakeFrustum() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeFrustum(m0, -1, 2, -2, 1, .1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.06666666, 0, 0, 0,
+       0, 0.06666666, 0, 0,
+       0.33333333, -0.33333333, -1.2, -1,
+       0, 0, -0.22, 0], m0, goog.vec.EPSILON);
+}
+
+function testMakePerspective() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makePerspective(m0, 90 * Math.PI / 180, 2, 0.1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1.2, -1, 0, 0, -0.22, 0],
+      m0, goog.vec.EPSILON);
+}
+
+function testMakeOrtho() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeOrtho(m0, -1, 2, -2, 1, 0.1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.6666666, 0, 0, 0,
+       0, 0.6666666, 0, 0,
+       0, 0, -2, 0,
+       -0.333333, 0.3333333, -1.2, 1], m0, goog.vec.EPSILON);
+
+}
+
+function testMakeEulerZXZ() {
+  var m0 = goog.vec.Mat4.createFloat32();
+  var roll = 0.200982 * 2 * Math.PI;
+  var tilt = 0.915833 * Math.PI;
+  var yaw = 0.839392 * 2 * Math.PI;
+
+  goog.vec.Mat4.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.Mat4.rotate(m0, tilt, 1, 0, 0);
+  goog.vec.Mat4.rotate(m0, yaw, 0, 0, 1);
+
+  var m1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeEulerZXZ(m1, roll, tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+
+  var euler = [0, 0, 0];
+  goog.vec.Mat4.toEulerZXZ(m0, euler);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+
+  // Test negative tilt now.
+  goog.vec.Mat4.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.Mat4.rotate(m0, -tilt, 1, 0, 0);
+  goog.vec.Mat4.rotate(m0, yaw, 0, 0, 1);
+
+  goog.vec.Mat4.makeEulerZXZ(m1, roll, -tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+  var euler = [0, 0, 0];
+  goog.vec.Mat4.toEulerZXZ(m0, euler, true);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(-tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+}
+
+function testEulerZXZExtrema() {
+  var m0 = goog.vec.Mat4.createFloat32FromArray(
+  [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1]);
+  var m1 = goog.vec.Mat4.createFloat32FromArray(
+  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+  var euler = [0, 0, 0];
+  goog.vec.Mat4.toEulerZXZ(m0, euler);
+  assertElementsRoughlyEqual(
+      [Math.PI, Math.PI / 2, Math.PI], euler, goog.vec.EPSILON);
+  goog.vec.Mat4.makeEulerZXZ(m1, euler[0], euler[1], euler[2]);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testLookAt() {
+  var viewMatrix = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeLookAt(
+    viewMatrix, [0, 0, 0], [1, 0, 0], [0, 1, 0]);
+  assertElementsRoughlyEqual(
+    [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1], viewMatrix,
+    goog.vec.EPSILON);
+}
+
+function testToLookAt() {
+  // This test does not use the default precision goog.vec.EPSILON due to
+  // precision issues in some browsers leading to flaky tests.
+  var EPSILON = 1e-4;
+
+  var eyeExp = [0, 0, 0];
+  var fwdExp = [1, 0, 0];
+  var upExp = [0, 1, 0];
+
+  var centerExp = [0, 0, 0];
+  goog.vec.Vec3.add(eyeExp, fwdExp, centerExp);
+
+  var view = goog.vec.Mat4.createFloat32();
+  goog.vec.Mat4.makeLookAt(view, eyeExp, centerExp, upExp);
+
+  var eyeRes = [0, 0, 0];
+  var fwdRes = [0, 0, 0];
+  var upRes = [0, 0, 0];
+  goog.vec.Mat4.toLookAt(view, eyeRes, fwdRes, upRes);
+  assertElementsRoughlyEqual(eyeExp, eyeRes, EPSILON);
+  assertElementsRoughlyEqual(fwdExp, fwdRes, EPSILON);
+  assertElementsRoughlyEqual(upExp, upRes, EPSILON);
+}
+
+function testLookAtDecomposition() {
+  // This test does not use the default precision goog.vec.EPSILON due to
+  // precision issues in some browsers leading to flaky tests.
+  var EPSILON = 1e-4;
+
+  var viewExp = goog.vec.Mat4.createFloat32();
+  var viewRes = goog.vec.Mat4.createFloat32();
+
+  // Get a valid set of random vectors eye, forward, up by decomposing
+  // a random matrix into a set of lookAt vectors.
+  var tmp = goog.vec.Mat4.createFloat32FromArray(randomMat4);
+  var eyeExp = [0, 0, 0];
+  var fwdExp = [0, 0, 0];
+  var upExp = [0, 0, 0];
+  var centerExp = [0, 0, 0];
+  // Project the random matrix into a real modelview matrix.
+  goog.vec.Mat4.toLookAt(tmp, eyeExp, fwdExp, upExp);
+  goog.vec.Vec3.add(eyeExp, fwdExp, centerExp);
+
+  // Compute the expected modelview matrix from a set of valid random vectors.
+  goog.vec.Mat4.makeLookAt(viewExp, eyeExp, centerExp, upExp);
+
+  var eyeRes = [0, 0, 0];
+  var fwdRes = [0, 0, 0];
+  var upRes = [0, 0, 0];
+  var centerRes = [0, 0, 0];
+  goog.vec.Mat4.toLookAt(viewExp, eyeRes, fwdRes, upRes);
+  goog.vec.Vec3.add(eyeRes, fwdRes, centerRes);
+
+  goog.vec.Mat4.makeLookAt(viewRes, eyeRes, centerRes, upRes);
+
+  assertElementsRoughlyEqual(eyeExp, eyeRes, EPSILON);
+  assertElementsRoughlyEqual(fwdExp, fwdRes, EPSILON);
+  assertElementsRoughlyEqual(upExp, upRes, EPSILON);
+  assertElementsRoughlyEqual(viewExp, viewRes, EPSILON);
+}

+ 1943 - 0
public/lib/closure/vec/mat4d.js

@@ -0,0 +1,1943 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat4f.js by running:            //
+//   swap_type.sh mat4d.js > mat4f.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 4x4 double (64bit)
+ * matrices.  The matrices are stored in column-major order.
+ *
+ * The last parameter will typically be the output matrix and an
+ * object can be both an input and output parameter to all methods except
+ * where noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.mat4d');
+goog.provide('goog.vec.mat4d.Type');
+
+goog.require('goog.vec');
+/** @suppress {extraRequire} */
+goog.require('goog.vec.Quaternion');
+goog.require('goog.vec.vec3d');
+goog.require('goog.vec.vec4d');
+
+
+/** @typedef {goog.vec.Float64} */ goog.vec.mat4d.Type;
+
+
+/**
+ * Creates a mat4d with all elements initialized to zero.
+ *
+ * @return {!goog.vec.mat4d.Type} The new mat4d.
+ */
+goog.vec.mat4d.create = function() {
+  return new Float64Array(16);
+};
+
+
+/**
+ * Creates a mat4d identity matrix.
+ *
+ * @return {!goog.vec.mat4d.Type} The new mat4d.
+ */
+goog.vec.mat4d.createIdentity = function() {
+  var mat = goog.vec.mat4d.create();
+  mat[0] = mat[5] = mat[10] = mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Initializes the matrix from the set of values. Note the values supplied are
+ * in column major order.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the
+ *     values.
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v30 The values at (3, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v31 The values at (3, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @param {number} v32 The values at (3, 2).
+ * @param {number} v03 The values at (0, 3).
+ * @param {number} v13 The values at (1, 3).
+ * @param {number} v23 The values at (2, 3).
+ * @param {number} v33 The values at (3, 3).
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setFromValues = function(
+    mat, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13,
+    v23, v33) {
+  mat[0] = v00;
+  mat[1] = v10;
+  mat[2] = v20;
+  mat[3] = v30;
+  mat[4] = v01;
+  mat[5] = v11;
+  mat[6] = v21;
+  mat[7] = v31;
+  mat[8] = v02;
+  mat[9] = v12;
+  mat[10] = v22;
+  mat[11] = v32;
+  mat[12] = v03;
+  mat[13] = v13;
+  mat[14] = v23;
+  mat[15] = v33;
+  return mat;
+};
+
+
+/**
+ * Initializes mat4d mat from mat4d src.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The destination matrix.
+ * @param {!goog.vec.mat4d.Type} src The source matrix.
+ * @return {!goog.vec.mat4d.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setFromMat4d = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  mat[9] = src[9];
+  mat[10] = src[10];
+  mat[11] = src[11];
+  mat[12] = src[12];
+  mat[13] = src[13];
+  mat[14] = src[14];
+  mat[15] = src[15];
+  return mat;
+};
+
+
+/**
+ * Initializes mat4d mat from mat4f src (typed as a Float32Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.mat4d.Type} mat The destination matrix.
+ * @param {Float32Array} src The source matrix.
+ * @return {!goog.vec.mat4d.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setFromMat4f = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  mat[9] = src[9];
+  mat[10] = src[10];
+  mat[11] = src[11];
+  mat[12] = src[12];
+  mat[13] = src[13];
+  mat[14] = src[14];
+  mat[15] = src[15];
+  return mat;
+};
+
+
+/**
+ * Initializes mat4d mat from Array src.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The destination matrix.
+ * @param {Array<number>} src The source matrix.
+ * @return {!goog.vec.mat4d.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setFromArray = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  mat[9] = src[9];
+  mat[10] = src[10];
+  mat[11] = src[11];
+  mat[12] = src[12];
+  mat[13] = src[13];
+  mat[14] = src[14];
+  mat[15] = src[15];
+  return mat;
+};
+
+
+/**
+ * Retrieves the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @return {number} The element value at the requested row, column indices.
+ */
+goog.vec.mat4d.getElement = function(mat, row, column) {
+  return mat[row + column * 4];
+};
+
+
+/**
+ * Sets the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @param {number} value The value to set at the requested row, column.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setElement = function(mat, row, column, value) {
+  mat[row + column * 4] = value;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given values.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {number} v00 The values for (0, 0).
+ * @param {number} v11 The values for (1, 1).
+ * @param {number} v22 The values for (2, 2).
+ * @param {number} v33 The values for (3, 3).
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setDiagonalValues = function(mat, v00, v11, v22, v33) {
+  mat[0] = v00;
+  mat[5] = v11;
+  mat[10] = v22;
+  mat[15] = v33;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec4d.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setDiagonal = function(mat, vec) {
+  mat[0] = vec[0];
+  mat[5] = vec[1];
+  mat[10] = vec[2];
+  mat[15] = vec[3];
+  return mat;
+};
+
+
+/**
+ * Gets the diagonal values of the matrix into the given vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix containing the values.
+ * @param {!goog.vec.vec4d.Type} vec The vector to receive the values.
+ * @param {number=} opt_diagonal Which diagonal to get. A value of 0 selects the
+ *     main diagonal, a positive number selects a super diagonal and a negative
+ *     number selects a sub diagonal.
+ * @return {!goog.vec.vec4d.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.getDiagonal = function(mat, vec, opt_diagonal) {
+  if (!opt_diagonal) {
+    // This is the most common case, so we avoid the for loop.
+    vec[0] = mat[0];
+    vec[1] = mat[5];
+    vec[2] = mat[10];
+    vec[3] = mat[15];
+  } else {
+    var offset = opt_diagonal > 0 ? 4 * opt_diagonal : -opt_diagonal;
+    for (var i = 0; i < 4 - Math.abs(opt_diagonal); i++) {
+      vec[i] = mat[offset + 5 * i];
+    }
+  }
+  return vec;
+};
+
+
+/**
+ * Sets the specified column with the supplied values.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {number} v0 The value for row 0.
+ * @param {number} v1 The value for row 1.
+ * @param {number} v2 The value for row 2.
+ * @param {number} v3 The value for row 3.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setColumnValues = function(mat, column, v0, v1, v2, v3) {
+  var i = column * 4;
+  mat[i] = v0;
+  mat[i + 1] = v1;
+  mat[i + 2] = v2;
+  mat[i + 3] = v3;
+  return mat;
+};
+
+
+/**
+ * Sets the specified column with the value from the supplied vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {!goog.vec.vec4d.Type} vec The vector of elements for the column.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setColumn = function(mat, column, vec) {
+  var i = column * 4;
+  mat[i] = vec[0];
+  mat[i + 1] = vec[1];
+  mat[i + 2] = vec[2];
+  mat[i + 3] = vec[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the specified column from the matrix into the given vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the values.
+ * @param {number} column The column to get the values from.
+ * @param {!goog.vec.vec4d.Type} vec The vector of elements to
+ *     receive the column.
+ * @return {!goog.vec.vec4d.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.getColumn = function(mat, column, vec) {
+  var i = column * 4;
+  vec[0] = mat[i];
+  vec[1] = mat[i + 1];
+  vec[2] = mat[i + 2];
+  vec[3] = mat[i + 3];
+  return vec;
+};
+
+
+/**
+ * Sets the columns of the matrix from the given vectors.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec4d.Type} vec0 The values for column 0.
+ * @param {!goog.vec.vec4d.Type} vec1 The values for column 1.
+ * @param {!goog.vec.vec4d.Type} vec2 The values for column 2.
+ * @param {!goog.vec.vec4d.Type} vec3 The values for column 3.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setColumns = function(mat, vec0, vec1, vec2, vec3) {
+  mat[0] = vec0[0];
+  mat[1] = vec0[1];
+  mat[2] = vec0[2];
+  mat[3] = vec0[3];
+  mat[4] = vec1[0];
+  mat[5] = vec1[1];
+  mat[6] = vec1[2];
+  mat[7] = vec1[3];
+  mat[8] = vec2[0];
+  mat[9] = vec2[1];
+  mat[10] = vec2[2];
+  mat[11] = vec2[3];
+  mat[12] = vec3[0];
+  mat[13] = vec3[1];
+  mat[14] = vec3[2];
+  mat[15] = vec3[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the column values from the given matrix into the given vectors.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the columns.
+ * @param {!goog.vec.vec4d.Type} vec0 The vector to receive column 0.
+ * @param {!goog.vec.vec4d.Type} vec1 The vector to receive column 1.
+ * @param {!goog.vec.vec4d.Type} vec2 The vector to receive column 2.
+ * @param {!goog.vec.vec4d.Type} vec3 The vector to receive column 3.
+ */
+goog.vec.mat4d.getColumns = function(mat, vec0, vec1, vec2, vec3) {
+  vec0[0] = mat[0];
+  vec0[1] = mat[1];
+  vec0[2] = mat[2];
+  vec0[3] = mat[3];
+  vec1[0] = mat[4];
+  vec1[1] = mat[5];
+  vec1[2] = mat[6];
+  vec1[3] = mat[7];
+  vec2[0] = mat[8];
+  vec2[1] = mat[9];
+  vec2[2] = mat[10];
+  vec2[3] = mat[11];
+  vec3[0] = mat[12];
+  vec3[1] = mat[13];
+  vec3[2] = mat[14];
+  vec3[3] = mat[15];
+};
+
+
+/**
+ * Sets the row values from the supplied values.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {number} row The index of the row to receive the values.
+ * @param {number} v0 The value for column 0.
+ * @param {number} v1 The value for column 1.
+ * @param {number} v2 The value for column 2.
+ * @param {number} v3 The value for column 3.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setRowValues = function(mat, row, v0, v1, v2, v3) {
+  mat[row] = v0;
+  mat[row + 4] = v1;
+  mat[row + 8] = v2;
+  mat[row + 12] = v3;
+  return mat;
+};
+
+
+/**
+ * Sets the row values from the supplied vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the row values.
+ * @param {number} row The index of the row.
+ * @param {!goog.vec.vec4d.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setRow = function(mat, row, vec) {
+  mat[row] = vec[0];
+  mat[row + 4] = vec[1];
+  mat[row + 8] = vec[2];
+  mat[row + 12] = vec[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the row values into the given vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the values.
+ * @param {number} row The index of the row supplying the values.
+ * @param {!goog.vec.vec4d.Type} vec The vector to receive the row.
+ * @return {!goog.vec.vec4d.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.getRow = function(mat, row, vec) {
+  vec[0] = mat[row];
+  vec[1] = mat[row + 4];
+  vec[2] = mat[row + 8];
+  vec[3] = mat[row + 12];
+  return vec;
+};
+
+
+/**
+ * Sets the rows of the matrix from the supplied vectors.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec4d.Type} vec0 The values for row 0.
+ * @param {!goog.vec.vec4d.Type} vec1 The values for row 1.
+ * @param {!goog.vec.vec4d.Type} vec2 The values for row 2.
+ * @param {!goog.vec.vec4d.Type} vec3 The values for row 3.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.setRows = function(mat, vec0, vec1, vec2, vec3) {
+  mat[0] = vec0[0];
+  mat[1] = vec1[0];
+  mat[2] = vec2[0];
+  mat[3] = vec3[0];
+  mat[4] = vec0[1];
+  mat[5] = vec1[1];
+  mat[6] = vec2[1];
+  mat[7] = vec3[1];
+  mat[8] = vec0[2];
+  mat[9] = vec1[2];
+  mat[10] = vec2[2];
+  mat[11] = vec3[2];
+  mat[12] = vec0[3];
+  mat[13] = vec1[3];
+  mat[14] = vec2[3];
+  mat[15] = vec3[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the rows of the matrix into the supplied vectors.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to supply the values.
+ * @param {!goog.vec.vec4d.Type} vec0 The vector to receive row 0.
+ * @param {!goog.vec.vec4d.Type} vec1 The vector to receive row 1.
+ * @param {!goog.vec.vec4d.Type} vec2 The vector to receive row 2.
+ * @param {!goog.vec.vec4d.Type} vec3 The vector to receive row 3.
+ */
+goog.vec.mat4d.getRows = function(mat, vec0, vec1, vec2, vec3) {
+  vec0[0] = mat[0];
+  vec1[0] = mat[1];
+  vec2[0] = mat[2];
+  vec3[0] = mat[3];
+  vec0[1] = mat[4];
+  vec1[1] = mat[5];
+  vec2[1] = mat[6];
+  vec3[1] = mat[7];
+  vec0[2] = mat[8];
+  vec1[2] = mat[9];
+  vec2[2] = mat[10];
+  vec3[2] = mat[11];
+  vec0[3] = mat[12];
+  vec1[3] = mat[13];
+  vec2[3] = mat[14];
+  vec3[3] = mat[15];
+};
+
+
+/**
+ * Makes the given 4x4 matrix the zero matrix.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @return {!goog.vec.mat4d.Type} return mat so operations can be chained.
+ */
+goog.vec.mat4d.makeZero = function(mat) {
+  mat[0] = 0;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 0;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 0;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix the identity matrix.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @return {!goog.vec.mat4d.Type} return mat so operations can be chained.
+ */
+goog.vec.mat4d.makeIdentity = function(mat) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 1;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 1;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Performs a per-component addition of the matrix mat0 and mat1, storing
+ * the result into resultMat.
+ *
+ * @param {!goog.vec.mat4d.Type} mat0 The first addend.
+ * @param {!goog.vec.mat4d.Type} mat1 The second addend.
+ * @param {!goog.vec.mat4d.Type} resultMat The matrix to
+ *     receive the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat4d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.addMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] + mat1[0];
+  resultMat[1] = mat0[1] + mat1[1];
+  resultMat[2] = mat0[2] + mat1[2];
+  resultMat[3] = mat0[3] + mat1[3];
+  resultMat[4] = mat0[4] + mat1[4];
+  resultMat[5] = mat0[5] + mat1[5];
+  resultMat[6] = mat0[6] + mat1[6];
+  resultMat[7] = mat0[7] + mat1[7];
+  resultMat[8] = mat0[8] + mat1[8];
+  resultMat[9] = mat0[9] + mat1[9];
+  resultMat[10] = mat0[10] + mat1[10];
+  resultMat[11] = mat0[11] + mat1[11];
+  resultMat[12] = mat0[12] + mat1[12];
+  resultMat[13] = mat0[13] + mat1[13];
+  resultMat[14] = mat0[14] + mat1[14];
+  resultMat[15] = mat0[15] + mat1[15];
+  return resultMat;
+};
+
+
+/**
+ * Performs a per-component subtraction of the matrix mat0 and mat1,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat4d.Type} mat0 The minuend.
+ * @param {!goog.vec.mat4d.Type} mat1 The subtrahend.
+ * @param {!goog.vec.mat4d.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat4d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.subMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] - mat1[0];
+  resultMat[1] = mat0[1] - mat1[1];
+  resultMat[2] = mat0[2] - mat1[2];
+  resultMat[3] = mat0[3] - mat1[3];
+  resultMat[4] = mat0[4] - mat1[4];
+  resultMat[5] = mat0[5] - mat1[5];
+  resultMat[6] = mat0[6] - mat1[6];
+  resultMat[7] = mat0[7] - mat1[7];
+  resultMat[8] = mat0[8] - mat1[8];
+  resultMat[9] = mat0[9] - mat1[9];
+  resultMat[10] = mat0[10] - mat1[10];
+  resultMat[11] = mat0[11] - mat1[11];
+  resultMat[12] = mat0[12] - mat1[12];
+  resultMat[13] = mat0[13] - mat1[13];
+  resultMat[14] = mat0[14] - mat1[14];
+  resultMat[15] = mat0[15] - mat1[15];
+  return resultMat;
+};
+
+
+/**
+ * Multiplies matrix mat with the given scalar, storing the result
+ * into resultMat.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} scalar The scalar value to multiply to each element of mat.
+ * @param {!goog.vec.mat4d.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat4d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.multScalar = function(mat, scalar, resultMat) {
+  resultMat[0] = mat[0] * scalar;
+  resultMat[1] = mat[1] * scalar;
+  resultMat[2] = mat[2] * scalar;
+  resultMat[3] = mat[3] * scalar;
+  resultMat[4] = mat[4] * scalar;
+  resultMat[5] = mat[5] * scalar;
+  resultMat[6] = mat[6] * scalar;
+  resultMat[7] = mat[7] * scalar;
+  resultMat[8] = mat[8] * scalar;
+  resultMat[9] = mat[9] * scalar;
+  resultMat[10] = mat[10] * scalar;
+  resultMat[11] = mat[11] * scalar;
+  resultMat[12] = mat[12] * scalar;
+  resultMat[13] = mat[13] * scalar;
+  resultMat[14] = mat[14] * scalar;
+  resultMat[15] = mat[15] * scalar;
+  return resultMat;
+};
+
+
+/**
+ * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat4d.Type} mat0 The first (left hand) matrix.
+ * @param {!goog.vec.mat4d.Type} mat1 The second (right hand) matrix.
+ * @param {!goog.vec.mat4d.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat4d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.multMat = function(mat0, mat1, resultMat) {
+  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2], a30 = mat0[3];
+  var a01 = mat0[4], a11 = mat0[5], a21 = mat0[6], a31 = mat0[7];
+  var a02 = mat0[8], a12 = mat0[9], a22 = mat0[10], a32 = mat0[11];
+  var a03 = mat0[12], a13 = mat0[13], a23 = mat0[14], a33 = mat0[15];
+
+  var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2], b30 = mat1[3];
+  var b01 = mat1[4], b11 = mat1[5], b21 = mat1[6], b31 = mat1[7];
+  var b02 = mat1[8], b12 = mat1[9], b22 = mat1[10], b32 = mat1[11];
+  var b03 = mat1[12], b13 = mat1[13], b23 = mat1[14], b33 = mat1[15];
+
+  resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
+  resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
+  resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
+  resultMat[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
+
+  resultMat[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
+  resultMat[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
+  resultMat[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
+  resultMat[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
+
+  resultMat[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
+  resultMat[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
+  resultMat[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
+  resultMat[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
+
+  resultMat[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;
+  resultMat[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;
+  resultMat[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;
+  resultMat[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;
+  return resultMat;
+};
+
+
+/**
+ * Transposes the given matrix mat storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to transpose.
+ * @param {!goog.vec.mat4d.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat4d.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.transpose = function(mat, resultMat) {
+  if (resultMat == mat) {
+    var a10 = mat[1], a20 = mat[2], a30 = mat[3];
+    var a21 = mat[6], a31 = mat[7];
+    var a32 = mat[11];
+    resultMat[1] = mat[4];
+    resultMat[2] = mat[8];
+    resultMat[3] = mat[12];
+    resultMat[4] = a10;
+    resultMat[6] = mat[9];
+    resultMat[7] = mat[13];
+    resultMat[8] = a20;
+    resultMat[9] = a21;
+    resultMat[11] = mat[14];
+    resultMat[12] = a30;
+    resultMat[13] = a31;
+    resultMat[14] = a32;
+  } else {
+    resultMat[0] = mat[0];
+    resultMat[1] = mat[4];
+    resultMat[2] = mat[8];
+    resultMat[3] = mat[12];
+
+    resultMat[4] = mat[1];
+    resultMat[5] = mat[5];
+    resultMat[6] = mat[9];
+    resultMat[7] = mat[13];
+
+    resultMat[8] = mat[2];
+    resultMat[9] = mat[6];
+    resultMat[10] = mat[10];
+    resultMat[11] = mat[14];
+
+    resultMat[12] = mat[3];
+    resultMat[13] = mat[7];
+    resultMat[14] = mat[11];
+    resultMat[15] = mat[15];
+  }
+  return resultMat;
+};
+
+
+/**
+ * Computes the determinant of the matrix.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to compute the matrix for.
+ * @return {number} The determinant of the matrix.
+ */
+goog.vec.mat4d.determinant = function(mat) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+  var a0 = m00 * m11 - m10 * m01;
+  var a1 = m00 * m21 - m20 * m01;
+  var a2 = m00 * m31 - m30 * m01;
+  var a3 = m10 * m21 - m20 * m11;
+  var a4 = m10 * m31 - m30 * m11;
+  var a5 = m20 * m31 - m30 * m21;
+  var b0 = m02 * m13 - m12 * m03;
+  var b1 = m02 * m23 - m22 * m03;
+  var b2 = m02 * m33 - m32 * m03;
+  var b3 = m12 * m23 - m22 * m13;
+  var b4 = m12 * m33 - m32 * m13;
+  var b5 = m22 * m33 - m32 * m23;
+
+  return a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+};
+
+
+/**
+ * Computes the inverse of mat storing the result into resultMat. If the
+ * inverse is defined, this function returns true, false otherwise.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix to invert.
+ * @param {!goog.vec.mat4d.Type} resultMat The matrix to receive
+ *     the result (may be mat).
+ * @return {boolean} True if the inverse is defined. If false is returned,
+ *     resultMat is not modified.
+ */
+goog.vec.mat4d.invert = function(mat, resultMat) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+  var a0 = m00 * m11 - m10 * m01;
+  var a1 = m00 * m21 - m20 * m01;
+  var a2 = m00 * m31 - m30 * m01;
+  var a3 = m10 * m21 - m20 * m11;
+  var a4 = m10 * m31 - m30 * m11;
+  var a5 = m20 * m31 - m30 * m21;
+  var b0 = m02 * m13 - m12 * m03;
+  var b1 = m02 * m23 - m22 * m03;
+  var b2 = m02 * m33 - m32 * m03;
+  var b3 = m12 * m23 - m22 * m13;
+  var b4 = m12 * m33 - m32 * m13;
+  var b5 = m22 * m33 - m32 * m23;
+
+  var det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+  if (det == 0) {
+    return false;
+  }
+
+  var idet = 1.0 / det;
+  resultMat[0] = (m11 * b5 - m21 * b4 + m31 * b3) * idet;
+  resultMat[1] = (-m10 * b5 + m20 * b4 - m30 * b3) * idet;
+  resultMat[2] = (m13 * a5 - m23 * a4 + m33 * a3) * idet;
+  resultMat[3] = (-m12 * a5 + m22 * a4 - m32 * a3) * idet;
+  resultMat[4] = (-m01 * b5 + m21 * b2 - m31 * b1) * idet;
+  resultMat[5] = (m00 * b5 - m20 * b2 + m30 * b1) * idet;
+  resultMat[6] = (-m03 * a5 + m23 * a2 - m33 * a1) * idet;
+  resultMat[7] = (m02 * a5 - m22 * a2 + m32 * a1) * idet;
+  resultMat[8] = (m01 * b4 - m11 * b2 + m31 * b0) * idet;
+  resultMat[9] = (-m00 * b4 + m10 * b2 - m30 * b0) * idet;
+  resultMat[10] = (m03 * a4 - m13 * a2 + m33 * a0) * idet;
+  resultMat[11] = (-m02 * a4 + m12 * a2 - m32 * a0) * idet;
+  resultMat[12] = (-m01 * b3 + m11 * b1 - m21 * b0) * idet;
+  resultMat[13] = (m00 * b3 - m10 * b1 + m20 * b0) * idet;
+  resultMat[14] = (-m03 * a3 + m13 * a1 - m23 * a0) * idet;
+  resultMat[15] = (m02 * a3 - m12 * a1 + m22 * a0) * idet;
+  return true;
+};
+
+
+/**
+ * Returns true if the components of mat0 are equal to the components of mat1.
+ *
+ * @param {!goog.vec.mat4d.Type} mat0 The first matrix.
+ * @param {!goog.vec.mat4d.Type} mat1 The second matrix.
+ * @return {boolean} True if the the two matrices are equivalent.
+ */
+goog.vec.mat4d.equals = function(mat0, mat1) {
+  return mat0.length == mat1.length && mat0[0] == mat1[0] &&
+      mat0[1] == mat1[1] && mat0[2] == mat1[2] && mat0[3] == mat1[3] &&
+      mat0[4] == mat1[4] && mat0[5] == mat1[5] && mat0[6] == mat1[6] &&
+      mat0[7] == mat1[7] && mat0[8] == mat1[8] && mat0[9] == mat1[9] &&
+      mat0[10] == mat1[10] && mat0[11] == mat1[11] && mat0[12] == mat1[12] &&
+      mat0[13] == mat1[13] && mat0[14] == mat1[14] && mat0[15] == mat1[15];
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 3x4 matrix omitting the projective component.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3d.Type} vec The 3 element vector to transform.
+ * @param {!goog.vec.vec3d.Type} resultVec The 3 element vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec3d.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.multVec3 = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + mat[12];
+  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + mat[13];
+  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + mat[14];
+  return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 3x3 matrix omitting the projective component and translation
+ * components.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3d.Type} vec The 3 element vector to transform.
+ * @param {!goog.vec.vec3d.Type} resultVec The 3 element vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec3d.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.multVec3NoTranslate = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8];
+  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9];
+  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10];
+  return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * full 4x4 matrix with the homogeneous divide applied to reduce the 4 element
+ * vector to a 3 element vector.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3d.Type} vec The 3 element vector to transform.
+ * @param {!goog.vec.vec3d.Type} resultVec The 3 element vector
+ *     to receive the results (may be vec).
+ * @return {!goog.vec.vec3d.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.multVec3Projective = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  var invw = 1 / (x * mat[3] + y * mat[7] + z * mat[11] + mat[15]);
+  resultVec[0] = (x * mat[0] + y * mat[4] + z * mat[8] + mat[12]) * invw;
+  resultVec[1] = (x * mat[1] + y * mat[5] + z * mat[9] + mat[13]) * invw;
+  resultVec[2] = (x * mat[2] + y * mat[6] + z * mat[10] + mat[14]) * invw;
+  return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec4d.Type} vec The vector to transform.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec4d.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.multVec4 = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
+  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + w * mat[12];
+  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + w * mat[13];
+  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + w * mat[14];
+  resultVec[3] = x * mat[3] + y * mat[7] + z * mat[11] + w * mat[15];
+  return resultVec;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a translation matrix with x, y and z
+ * translation factors.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @param {number} z The translation along the z axis.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeTranslate = function(mat, x, y, z) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 1;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 1;
+  mat[11] = 0;
+  mat[12] = x;
+  mat[13] = y;
+  mat[14] = z;
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix as a scale matrix with x, y and z scale factors.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} x The scale along the x axis.
+ * @param {number} y The scale along the y axis.
+ * @param {number} z The scale along the z axis.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeScale = function(mat, x, y, z) {
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = y;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = z;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the axis defined by the vector (ax, ay, az).
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @param {number} ax The x component of the rotation axis.
+ * @param {number} ay The y component of the rotation axis.
+ * @param {number} az The z component of the rotation axis.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotate = function(mat, angle, ax, ay, az) {
+  var c = Math.cos(angle);
+  var d = 1 - c;
+  var s = Math.sin(angle);
+
+  mat[0] = ax * ax * d + c;
+  mat[1] = ax * ay * d + az * s;
+  mat[2] = ax * az * d - ay * s;
+  mat[3] = 0;
+  mat[4] = ax * ay * d - az * s;
+  mat[5] = ay * ay * d + c;
+  mat[6] = ay * az * d + ax * s;
+  mat[7] = 0;
+  mat[8] = ax * az * d + ay * s;
+  mat[9] = ay * az * d - ax * s;
+  mat[10] = az * az * d + c;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotateX = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = c;
+  mat[6] = s;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = -s;
+  mat[10] = c;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotateY = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = 0;
+  mat[2] = -s;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 1;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = s;
+  mat[9] = 0;
+  mat[10] = c;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotateZ = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = s;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = -s;
+  mat[5] = c;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 1;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Creates a matrix from a quaternion rotation and vector translation.
+ *
+ * This is a specialization of makeRotationTranslationScaleOrigin.
+ *
+ * This is equivalent to, but faster than:
+ *     goog.vec.mat4d.makeIdentity(m);
+ *     goog.vec.mat4d.translate(m, tx, ty, tz);
+ *     goog.vec.mat4d.rotate(m, theta, rx, ry, rz);
+ * and:
+ *     goog.vec.Quaternion.toRotationMatrix4(rotation, mat);
+ *     mat[12] = translation[0];
+ *     mat[13] = translation[1];
+ *     mat[14] = translation[2];
+ * See http://jsperf.com/goog-vec-makerotationtranslation2 .
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {!goog.vec.Quaternion.AnyType} rotation The quaternion rotation.
+ *     Note: this quaternion is assumed to already be normalized.
+ * @param {!goog.vec.vec3d.Type} translation The vector translation.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotationTranslation = function(mat, rotation, translation) {
+  // Quaternion math
+  var x = rotation[0], y = rotation[1], z = rotation[2], w = rotation[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var xx = x * x2;
+  var xy = x * y2;
+  var xz = x * z2;
+  var yy = y * y2;
+  var yz = y * z2;
+  var zz = z * z2;
+  var wx = w * x2;
+  var wy = w * y2;
+  var wz = w * z2;
+
+  mat[0] = 1 - (yy + zz);
+  mat[1] = xy + wz;
+  mat[2] = xz - wy;
+  mat[3] = 0;
+  mat[4] = xy - wz;
+  mat[5] = 1 - (xx + zz);
+  mat[6] = yz + wx;
+  mat[7] = 0;
+  mat[8] = xz + wy;
+  mat[9] = yz - wx;
+  mat[10] = 1 - (xx + yy);
+  mat[11] = 0;
+  mat[12] = translation[0];
+  mat[13] = translation[1];
+  mat[14] = translation[2];
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Creates a matrix from a quaternion rotation, vector translation, and
+ * vector scale.
+ *
+ * This is a specialization of makeRotationTranslationScaleOrigin.
+ *
+ * This is equivalent to, but faster than:
+ *     goog.vec.mat4d.makeIdentity(m);
+ *     goog.vec.mat4d.translate(m, tx, ty, tz);
+ *     goog.vec.mat4d.rotate(m, theta, rx, ry, rz);
+ *     goog.vec.mat4d.scale(m, sx, sy, sz);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {!goog.vec.Quaternion.AnyType} rotation The quaternion rotation.
+ *     Note: this quaternion is assumed to already be normalized.
+ * @param {!goog.vec.vec3d.Type} translation The vector translation.
+ * @param {!goog.vec.vec3d.Type} scale The vector scale.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotationTranslationScale = function(
+    mat, rotation, translation, scale) {
+  // Quaternion math
+  var x = rotation[0], y = rotation[1], z = rotation[2], w = rotation[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var xx = x * x2;
+  var xy = x * y2;
+  var xz = x * z2;
+  var yy = y * y2;
+  var yz = y * z2;
+  var zz = z * z2;
+  var wx = w * x2;
+  var wy = w * y2;
+  var wz = w * z2;
+  var sx = scale[0];
+  var sy = scale[1];
+  var sz = scale[2];
+
+  mat[0] = (1 - (yy + zz)) * sx;
+  mat[1] = (xy + wz) * sx;
+  mat[2] = (xz - wy) * sx;
+  mat[3] = 0;
+  mat[4] = (xy - wz) * sy;
+  mat[5] = (1 - (xx + zz)) * sy;
+  mat[6] = (yz + wx) * sy;
+  mat[7] = 0;
+  mat[8] = (xz + wy) * sz;
+  mat[9] = (yz - wx) * sz;
+  mat[10] = (1 - (xx + yy)) * sz;
+  mat[11] = 0;
+  mat[12] = translation[0];
+  mat[13] = translation[1];
+  mat[14] = translation[2];
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Creates a matrix from a quaternion rotation, vector translation, and
+ * vector scale, rotating and scaling about the given origin.
+ *
+ * This is equivalent to, but faster than:
+ *     goog.vec.mat4d.makeIdentity(m);
+ *     goog.vec.mat4d.translate(m, tx, ty, tz);
+ *     goog.vec.mat4d.translate(m, ox, oy, oz);
+ *     goog.vec.mat4d.rotate(m, theta, rx, ry, rz);
+ *     goog.vec.mat4d.scale(m, sx, sy, sz);
+ *     goog.vec.mat4d.translate(m, -ox, -oy, -oz);
+ * See http://jsperf.com/glmatrix-matrix-variant-test/3 for performance
+ * results of a similar function in the glmatrix library.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {!goog.vec.Quaternion.AnyType} rotation The quaternion rotation.
+ *     Note: this quaternion is assumed to already be normalized.
+ * @param {!goog.vec.vec3d.Type} translation The vector translation.
+ * @param {!goog.vec.vec3d.Type} scale The vector scale.
+ * @param {!goog.vec.vec3d.Type} origin The origin about which to scale and
+ *     rotate.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeRotationTranslationScaleOrigin = function(
+    mat, rotation, translation, scale, origin) {
+  // Quaternion math
+  var x = rotation[0], y = rotation[1], z = rotation[2], w = rotation[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var xx = x * x2;
+  var xy = x * y2;
+  var xz = x * z2;
+  var yy = y * y2;
+  var yz = y * z2;
+  var zz = z * z2;
+  var wx = w * x2;
+  var wy = w * y2;
+  var wz = w * z2;
+  var sx = scale[0];
+  var sy = scale[1];
+  var sz = scale[2];
+  var ox = origin[0];
+  var oy = origin[1];
+  var oz = origin[2];
+
+  mat[0] = (1 - (yy + zz)) * sx;
+  mat[1] = (xy + wz) * sx;
+  mat[2] = (xz - wy) * sx;
+  mat[3] = 0;
+  mat[4] = (xy - wz) * sy;
+  mat[5] = (1 - (xx + zz)) * sy;
+  mat[6] = (yz + wx) * sy;
+  mat[7] = 0;
+  mat[8] = (xz + wy) * sz;
+  mat[9] = (yz - wx) * sz;
+  mat[10] = (1 - (xx + yy)) * sz;
+  mat[11] = 0;
+  mat[12] = translation[0] + ox - (mat[0] * ox + mat[4] * oy + mat[8] * oz);
+  mat[13] = translation[1] + oy - (mat[1] * ox + mat[5] * oy + mat[9] * oz);
+  mat[14] = translation[2] + oz - (mat[2] * ox + mat[6] * oy + mat[10] * oz);
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a perspective projection matrix.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} left The coordinate of the left clipping plane.
+ * @param {number} right The coordinate of the right clipping plane.
+ * @param {number} bottom The coordinate of the bottom clipping plane.
+ * @param {number} top The coordinate of the top clipping plane.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeFrustum = function(
+    mat, left, right, bottom, top, near, far) {
+  var x = (2 * near) / (right - left);
+  var y = (2 * near) / (top - bottom);
+  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);
+
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = y;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = a;
+  mat[9] = b;
+  mat[10] = c;
+  mat[11] = -1;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = d;
+  mat[15] = 0;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix  perspective projection matrix given a
+ * field of view and aspect ratio.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} fovy The field of view along the y (vertical) axis in
+ *     radians.
+ * @param {number} aspect The x (width) to y (height) aspect ratio.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makePerspective = function(mat, fovy, aspect, near, far) {
+  var angle = fovy / 2;
+  var dz = far - near;
+  var sinAngle = Math.sin(angle);
+  if (dz == 0 || sinAngle == 0 || aspect == 0) {
+    return mat;
+  }
+
+  var cot = Math.cos(angle) / sinAngle;
+
+  mat[0] = cot / aspect;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = cot;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = -(far + near) / dz;
+  mat[11] = -1;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = -(2 * near * far) / dz;
+  mat[15] = 0;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix an orthographic projection matrix.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} left The coordinate of the left clipping plane.
+ * @param {number} right The coordinate of the right clipping plane.
+ * @param {number} bottom The coordinate of the bottom clipping plane.
+ * @param {number} top The coordinate of the top clipping plane.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeOrtho = function(mat, left, right, bottom, top, near, far) {
+  var x = 2 / (right - left);
+  var y = 2 / (top - bottom);
+  var z = -2 / (far - near);
+  var a = -(right + left) / (right - left);
+  var b = -(top + bottom) / (top - bottom);
+  var c = -(far + near) / (far - near);
+
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = y;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = z;
+  mat[11] = 0;
+  mat[12] = a;
+  mat[13] = b;
+  mat[14] = c;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a modelview matrix of a camera so that
+ * the camera is 'looking at' the given center point.
+ *
+ * Note that unlike most other goog.vec functions where we inline
+ * everything, this function does not inline various goog.vec
+ * functions.  This makes the code more readable, but somewhat
+ * less efficient.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {!goog.vec.vec3d.Type} eyePt The position of the eye point
+ *     (camera origin).
+ * @param {!goog.vec.vec3d.Type} centerPt The point to aim the camera at.
+ * @param {!goog.vec.vec3d.Type} worldUpVec The vector that identifies
+ *     the up direction for the camera.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeLookAt = function(mat, eyePt, centerPt, worldUpVec) {
+  // Compute the direction vector from the eye point to the center point and
+  // normalize.
+  var fwdVec = goog.vec.mat4d.tmpvec4d_[0];
+  goog.vec.vec3d.subtract(centerPt, eyePt, fwdVec);
+  goog.vec.vec3d.normalize(fwdVec, fwdVec);
+  fwdVec[3] = 0;
+
+  // Compute the side vector from the forward vector and the input up vector.
+  var sideVec = goog.vec.mat4d.tmpvec4d_[1];
+  goog.vec.vec3d.cross(fwdVec, worldUpVec, sideVec);
+  goog.vec.vec3d.normalize(sideVec, sideVec);
+  sideVec[3] = 0;
+
+  // Now the up vector to form the orthonormal basis.
+  var upVec = goog.vec.mat4d.tmpvec4d_[2];
+  goog.vec.vec3d.cross(sideVec, fwdVec, upVec);
+  goog.vec.vec3d.normalize(upVec, upVec);
+  upVec[3] = 0;
+
+  // Update the view matrix with the new orthonormal basis and position the
+  // camera at the given eye point.
+  goog.vec.vec3d.negate(fwdVec, fwdVec);
+  goog.vec.mat4d.setRow(mat, 0, sideVec);
+  goog.vec.mat4d.setRow(mat, 1, upVec);
+  goog.vec.mat4d.setRow(mat, 2, fwdVec);
+  goog.vec.mat4d.setRowValues(mat, 3, 0, 0, 0, 1);
+  goog.vec.mat4d.translate(mat, -eyePt[0], -eyePt[1], -eyePt[2]);
+
+  return mat;
+};
+
+
+/**
+ * Decomposes a matrix into the lookAt vectors eyePt, fwdVec and worldUpVec.
+ * The matrix represents the modelview matrix of a camera. It is the inverse
+ * of lookAt except for the output of the fwdVec instead of centerPt.
+ * The centerPt itself cannot be recovered from a modelview matrix.
+ *
+ * Note that unlike most other goog.vec functions where we inline
+ * everything, this function does not inline various goog.vec
+ * functions.  This makes the code more readable, but somewhat
+ * less efficient.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {!goog.vec.vec3d.Type} eyePt The position of the eye point
+ *     (camera origin).
+ * @param {!goog.vec.vec3d.Type} fwdVec The vector describing where
+ *     the camera points to.
+ * @param {!goog.vec.vec3d.Type} worldUpVec The vector that
+ *     identifies the up direction for the camera.
+ * @return {boolean} True if the method succeeds, false otherwise.
+ *     The method can only fail if the inverse of viewMatrix is not defined.
+ */
+goog.vec.mat4d.toLookAt = function(mat, eyePt, fwdVec, worldUpVec) {
+  // Get eye of the camera.
+  var matInverse = goog.vec.mat4d.tmpmat4d_[0];
+  if (!goog.vec.mat4d.invert(mat, matInverse)) {
+    // The input matrix does not have a valid inverse.
+    return false;
+  }
+
+  if (eyePt) {
+    eyePt[0] = matInverse[12];
+    eyePt[1] = matInverse[13];
+    eyePt[2] = matInverse[14];
+  }
+
+  // Get forward vector from the definition of lookAt.
+  if (fwdVec || worldUpVec) {
+    if (!fwdVec) {
+      fwdVec = goog.vec.mat4d.tmpvec3d_[0];
+    }
+    fwdVec[0] = -mat[2];
+    fwdVec[1] = -mat[6];
+    fwdVec[2] = -mat[10];
+    // Normalize forward vector.
+    goog.vec.vec3d.normalize(fwdVec, fwdVec);
+  }
+
+  if (worldUpVec) {
+    // Get side vector from the definition of gluLookAt.
+    var side = goog.vec.mat4d.tmpvec3d_[1];
+    side[0] = mat[0];
+    side[1] = mat[4];
+    side[2] = mat[8];
+    // Compute up vector as a up = side x forward.
+    goog.vec.vec3d.cross(side, fwdVec, worldUpVec);
+    // Normalize up vector.
+    goog.vec.vec3d.normalize(worldUpVec, worldUpVec);
+  }
+  return true;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix given Euler angles using
+ * the ZXZ convention.
+ * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
+ * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians,
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} theta1 The angle of rotation around the Z axis in radians.
+ * @param {number} theta2 The angle of rotation around the X axis in radians.
+ * @param {number} theta3 The angle of rotation around the Z axis in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
+  var c1 = Math.cos(theta1);
+  var s1 = Math.sin(theta1);
+
+  var c2 = Math.cos(theta2);
+  var s2 = Math.sin(theta2);
+
+  var c3 = Math.cos(theta3);
+  var s3 = Math.sin(theta3);
+
+  mat[0] = c1 * c3 - c2 * s1 * s3;
+  mat[1] = c2 * c1 * s3 + c3 * s1;
+  mat[2] = s3 * s2;
+  mat[3] = 0;
+
+  mat[4] = -c1 * s3 - c3 * c2 * s1;
+  mat[5] = c1 * c2 * c3 - s1 * s3;
+  mat[6] = c3 * s2;
+  mat[7] = 0;
+
+  mat[8] = s2 * s1;
+  mat[9] = -c1 * s2;
+  mat[10] = c2;
+  mat[11] = 0;
+
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Decomposes a rotation matrix into Euler angles using the ZXZ convention so
+ * that rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {!goog.vec.vec3d.Type} euler The ZXZ Euler angles in
+ *     radians as [theta1, theta2, theta3].
+ * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
+ *     of the default [0, pi].
+ * @return {!goog.vec.vec4d.Type} return euler so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4d.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
+  // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
+  var sinTheta2 = Math.sqrt(mat[2] * mat[2] + mat[6] * mat[6]);
+
+  // By default we explicitely constrain theta2 to be in [0, pi],
+  // so sinTheta2 is always positive. We can change the behavior and specify
+  // theta2 to be negative in [-pi, 0] with opt_Theta2IsNegative.
+  var signTheta2 = opt_theta2IsNegative ? -1 : 1;
+
+  if (sinTheta2 > goog.vec.EPSILON) {
+    euler[2] = Math.atan2(mat[2] * signTheta2, mat[6] * signTheta2);
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
+    euler[0] = Math.atan2(mat[8] * signTheta2, -mat[9] * signTheta2);
+  } else {
+    // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+    // We assume theta1 = 0 as some applications do not allow the camera to roll
+    // (i.e. have theta1 != 0).
+    euler[0] = 0;
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
+    euler[2] = Math.atan2(mat[1], mat[0]);
+  }
+
+  // Atan2 outputs angles in [-pi, pi] so we bring them back to [0, 2 * pi].
+  euler[0] = (euler[0] + Math.PI * 2) % (Math.PI * 2);
+  euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
+  // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
+  // signTheta2.
+  euler[1] =
+      ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) * signTheta2;
+
+  return euler;
+};
+
+
+/**
+ * Translates the given matrix by x,y,z.  Equvialent to:
+ * goog.vec.mat4d.multMat(
+ *     mat,
+ *     goog.vec.mat4d.makeTranslate(goog.vec.mat4d.create(), x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @param {number} z The translation along the z axis.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.translate = function(mat, x, y, z) {
+  mat[12] += mat[0] * x + mat[4] * y + mat[8] * z;
+  mat[13] += mat[1] * x + mat[5] * y + mat[9] * z;
+  mat[14] += mat[2] * x + mat[6] * y + mat[10] * z;
+  mat[15] += mat[3] * x + mat[7] * y + mat[11] * z;
+
+  return mat;
+};
+
+
+/**
+ * Scales the given matrix by x,y,z.  Equivalent to:
+ * goog.vec.mat4d.multMat(
+ *     mat,
+ *     goog.vec.mat4d.makeScale(goog.vec.mat4d.create(), x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} x The x scale factor.
+ * @param {number} y The y scale factor.
+ * @param {number} z The z scale factor.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.scale = function(mat, x, y, z) {
+  mat[0] = mat[0] * x;
+  mat[1] = mat[1] * x;
+  mat[2] = mat[2] * x;
+  mat[3] = mat[3] * x;
+  mat[4] = mat[4] * y;
+  mat[5] = mat[5] * y;
+  mat[6] = mat[6] * y;
+  mat[7] = mat[7] * y;
+  mat[8] = mat[8] * z;
+  mat[9] = mat[9] * z;
+  mat[10] = mat[10] * z;
+  mat[11] = mat[11] * z;
+  mat[12] = mat[12];
+  mat[13] = mat[13];
+  mat[14] = mat[14];
+  mat[15] = mat[15];
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x,y,z axis.  Equivalent to:
+ * goog.vec.mat4d.multMat(
+ *     mat,
+ *     goog.vec.mat4d.makeRotate(goog.vec.mat4d.create(), angle, x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @param {number} x The x component of the rotation axis.
+ * @param {number} y The y component of the rotation axis.
+ * @param {number} z The z component of the rotation axis.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.rotate = function(mat, angle, x, y, z) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var cosAngle = Math.cos(angle);
+  var sinAngle = Math.sin(angle);
+  var diffCosAngle = 1 - cosAngle;
+  var r00 = x * x * diffCosAngle + cosAngle;
+  var r10 = x * y * diffCosAngle + z * sinAngle;
+  var r20 = x * z * diffCosAngle - y * sinAngle;
+
+  var r01 = x * y * diffCosAngle - z * sinAngle;
+  var r11 = y * y * diffCosAngle + cosAngle;
+  var r21 = y * z * diffCosAngle + x * sinAngle;
+
+  var r02 = x * z * diffCosAngle + y * sinAngle;
+  var r12 = y * z * diffCosAngle - x * sinAngle;
+  var r22 = z * z * diffCosAngle + cosAngle;
+
+  mat[0] = m00 * r00 + m01 * r10 + m02 * r20;
+  mat[1] = m10 * r00 + m11 * r10 + m12 * r20;
+  mat[2] = m20 * r00 + m21 * r10 + m22 * r20;
+  mat[3] = m30 * r00 + m31 * r10 + m32 * r20;
+  mat[4] = m00 * r01 + m01 * r11 + m02 * r21;
+  mat[5] = m10 * r01 + m11 * r11 + m12 * r21;
+  mat[6] = m20 * r01 + m21 * r11 + m22 * r21;
+  mat[7] = m30 * r01 + m31 * r11 + m32 * r21;
+  mat[8] = m00 * r02 + m01 * r12 + m02 * r22;
+  mat[9] = m10 * r02 + m11 * r12 + m12 * r22;
+  mat[10] = m20 * r02 + m21 * r12 + m22 * r22;
+  mat[11] = m30 * r02 + m31 * r12 + m32 * r22;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis.  Equivalent to:
+ * goog.vec.mat4d.multMat(
+ *     mat,
+ *     goog.vec.mat4d.makeRotateX(goog.vec.mat4d.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.rotateX = function(mat, angle) {
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[4] = m01 * c + m02 * s;
+  mat[5] = m11 * c + m12 * s;
+  mat[6] = m21 * c + m22 * s;
+  mat[7] = m31 * c + m32 * s;
+  mat[8] = m01 * -s + m02 * c;
+  mat[9] = m11 * -s + m12 * c;
+  mat[10] = m21 * -s + m22 * c;
+  mat[11] = m31 * -s + m32 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis.  Equivalent to:
+ * goog.vec.mat4d.multMat(
+ *     mat,
+ *     goog.vec.mat4d.makeRotateY(goog.vec.mat4d.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.rotateY = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m02 * -s;
+  mat[1] = m10 * c + m12 * -s;
+  mat[2] = m20 * c + m22 * -s;
+  mat[3] = m30 * c + m32 * -s;
+  mat[8] = m00 * s + m02 * c;
+  mat[9] = m10 * s + m12 * c;
+  mat[10] = m20 * s + m22 * c;
+  mat[11] = m30 * s + m32 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the z axis.  Equivalent to:
+ * goog.vec.mat4d.multMat(
+ *     mat,
+ *     goog.vec.mat4d.makeRotateZ(goog.vec.mat4d.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4d.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat4d.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.rotateZ = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m01 * s;
+  mat[1] = m10 * c + m11 * s;
+  mat[2] = m20 * c + m21 * s;
+  mat[3] = m30 * c + m31 * s;
+  mat[4] = m00 * -s + m01 * c;
+  mat[5] = m10 * -s + m11 * c;
+  mat[6] = m20 * -s + m21 * c;
+  mat[7] = m30 * -s + m31 * c;
+
+  return mat;
+};
+
+
+/**
+ * Retrieves the translation component of the transformation matrix.
+ *
+ * @param {!goog.vec.mat4d.Type} mat The transformation matrix.
+ * @param {!goog.vec.vec3d.Type} translation The vector for storing the
+ *     result.
+ * @return {!goog.vec.vec3d.Type} return translation so that operations can be
+ *     chained.
+ */
+goog.vec.mat4d.getTranslation = function(mat, translation) {
+  translation[0] = mat[12];
+  translation[1] = mat[13];
+  translation[2] = mat[14];
+  return translation;
+};
+
+
+/**
+ * @type {Array<goog.vec.vec3d.Type>}
+ * @private
+ */
+goog.vec.mat4d.tmpvec3d_ = [goog.vec.vec3d.create(), goog.vec.vec3d.create()];
+
+
+/**
+ * @type {Array<goog.vec.vec4d.Type>}
+ * @private
+ */
+goog.vec.mat4d.tmpvec4d_ =
+    [goog.vec.vec4d.create(), goog.vec.vec4d.create(), goog.vec.vec4d.create()];
+
+
+/**
+ * @type {Array<goog.vec.mat4d.Type>}
+ * @private
+ */
+goog.vec.mat4d.tmpmat4d_ = [goog.vec.mat4d.create()];

+ 11 - 0
public/lib/closure/vec/mat4d_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.mat4dTest');</script>
+<title>Closure Unit Tests - goog.vec.mat4dTest</title></head><body></body></html>

+ 788 - 0
public/lib/closure/vec/mat4d_test.js

@@ -0,0 +1,788 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat4f_test.js by running:       //
+//   swap_type.sh mat4d_test.js > mat4f_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.mat4dTest');
+goog.setTestOnly('goog.vec.mat4dTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Quaternion');
+goog.require('goog.vec.mat4d');
+goog.require('goog.vec.vec3d');
+goog.require('goog.vec.vec4d');
+
+var randommat4d = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+    0.8025078773498535,
+    0.7559120655059814,
+    0.15274643898010254,
+    0.19196106493473053,
+    0.0890120416879654,
+    0.15422114729881287,
+    0.09754583984613419,
+    0.44862601161003113,
+    0.9196512699127197,
+    0.5310639142990112,
+    0.8962187170982361,
+    0.280601441860199,
+    0.594650387763977,
+    0.4134795069694519,
+    0.06632178276777267,
+    0.8837796449661255);
+
+function testCreate() {
+  var m = goog.vec.mat4d.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m);
+}
+
+function testCreateIdentity() {
+  var m = goog.vec.mat4d.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m);
+}
+
+function testSet() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  goog.vec.mat4d.setFromArray(m0, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  goog.vec.mat4d.setFromValues(
+      m0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
+  assertElementsEquals(
+      [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], m0);
+}
+
+function testSetDiagonal() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setDiagonalValues(m0, 1, 2, 3, 4);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4], m0);
+
+  goog.vec.mat4d.setDiagonal(m0, [4, 5, 6, 7]);
+  assertElementsEquals(
+      [4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7], m0);
+}
+
+function testGetDiagonal() {
+  var v0 = goog.vec.vec4d.create();
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setFromArray(
+      m0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
+
+  goog.vec.mat4d.getDiagonal(m0, v0);
+  assertElementsEquals([0, 5, 10, 15], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, 1);
+  assertElementsEquals([4, 9, 14, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, 2);
+  assertElementsEquals([8, 13, 0, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, 3);
+  assertElementsEquals([12, 0, 0, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, 4);
+  assertElementsEquals([0, 0, 0, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, -1);
+  assertElementsEquals([1, 6, 11, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, -2);
+  assertElementsEquals([2, 7, 0, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, -3);
+  assertElementsEquals([3, 0, 0, 0], v0);
+
+  goog.vec.vec4d.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4d.getDiagonal(m0, v0, -4);
+  assertElementsEquals([0, 0, 0, 0], v0);
+}
+
+function testSetGetColumn() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setColumn(m0, 0, [1, 2, 3, 4]);
+  goog.vec.mat4d.setColumn(m0, 1, [5, 6, 7, 8]);
+  goog.vec.mat4d.setColumn(m0, 2, [9, 10, 11, 12]);
+  goog.vec.mat4d.setColumn(m0, 3, [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  var v0 = [0, 0, 0, 0];
+  goog.vec.mat4d.getColumn(m0, 0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  goog.vec.mat4d.getColumn(m0, 1, v0);
+  assertElementsEquals([5, 6, 7, 8], v0);
+  goog.vec.mat4d.getColumn(m0, 2, v0);
+  assertElementsEquals([9, 10, 11, 12], v0);
+  goog.vec.mat4d.getColumn(m0, 3, v0);
+  assertElementsEquals([13, 14, 15, 16], v0);
+}
+
+function testSetGetColumns() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setColumns(
+      m0, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  var v0 = [0, 0, 0, 0], v1 = [0, 0, 0, 0];
+  var v2 = [0, 0, 0, 0], v3 = [0, 0, 0, 0];
+  goog.vec.mat4d.getColumns(m0, v0, v1, v2, v3);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([9, 10, 11, 12], v2);
+  assertElementsEquals([13, 14, 15, 16], v3);
+}
+
+function testSetGetRow() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setRow(m0, 0, [1, 2, 3, 4]);
+  goog.vec.mat4d.setRow(m0, 1, [5, 6, 7, 8]);
+  goog.vec.mat4d.setRow(m0, 2, [9, 10, 11, 12]);
+  goog.vec.mat4d.setRow(m0, 3, [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+
+  var v0 = [0, 0, 0, 0];
+  goog.vec.mat4d.getRow(m0, 0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  goog.vec.mat4d.getRow(m0, 1, v0);
+  assertElementsEquals([5, 6, 7, 8], v0);
+  goog.vec.mat4d.getRow(m0, 2, v0);
+  assertElementsEquals([9, 10, 11, 12], v0);
+  goog.vec.mat4d.getRow(m0, 3, v0);
+  assertElementsEquals([13, 14, 15, 16], v0);
+}
+
+function testSetGetRows() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setRows(
+      m0, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+
+  var v0 = [0, 0, 0, 0], v1 = [0, 0, 0, 0];
+  var v2 = [0, 0, 0, 0], v3 = [0, 0, 0, 0];
+  goog.vec.mat4d.getRows(m0, v0, v1, v2, v3);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([9, 10, 11, 12], v2);
+  assertElementsEquals([13, 14, 15, 16], v3);
+}
+
+function testMakeZero() {
+  var m0 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  goog.vec.mat4d.makeZero(m0);
+  assertElementsEquals(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+}
+
+function testMakeIdentity() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeIdentity(m0);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m0);
+}
+
+function testSetGetElement() {
+  var m0 = goog.vec.mat4d.create();
+  for (var r = 0; r < 4; r++) {
+    for (var c = 0; c < 4; c++) {
+      var value = c * 4 + r + 1;
+      goog.vec.mat4d.setElement(m0, r, c, value);
+      assertEquals(value, goog.vec.mat4d.getElement(m0, r, c));
+    }
+  }
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+}
+
+function testAddMat() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8);
+  var m2 = goog.vec.mat4d.create();
+  goog.vec.mat4d.addMat(m0, m1, m2);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [10, 12, 14, 16, 18, 20, 22, 24, 10, 12, 14, 16, 18, 20, 22, 24], m2);
+
+  goog.vec.mat4d.addMat(m0, m1, m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [10, 12, 14, 16, 18, 20, 22, 24, 10, 12, 14, 16, 18, 20, 22, 24], m0);
+}
+
+function testSubMat() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8);
+  var m2 = goog.vec.mat4d.create();
+
+  goog.vec.mat4d.subMat(m0, m1, m2);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [-8, -8, -8, -8, -8, -8, -8, -8, 8, 8, 8, 8, 8, 8, 8, 8], m2);
+
+  goog.vec.mat4d.subMat(m1, m0, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [8, 8, 8, 8, 8, 8, 8, 8, -8, -8, -8, -8, -8, -8, -8, -8], m1);
+}
+
+function testMultScalar() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4d.create();
+
+  goog.vec.mat4d.multScalar(m0, 2, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32], m1);
+
+  goog.vec.mat4d.multScalar(m0, 5, m0);
+  assertElementsEquals(
+      [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80], m0);
+}
+
+function testMultMat() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m2 = goog.vec.mat4d.create();
+
+  goog.vec.mat4d.multMat(m0, m1, m2);
+  assertElementsEquals(
+      [90, 100, 110, 120, 202, 228, 254, 280,
+       314, 356, 398, 440, 426, 484, 542, 600], m2);
+
+  goog.vec.mat4d.multScalar(m1, 2, m1);
+  goog.vec.mat4d.multMat(m1, m0, m1);
+  assertElementsEquals(
+      [180, 200, 220, 240, 404, 456, 508, 560,
+       628, 712, 796, 880, 852, 968, 1084, 1200], m1);
+}
+
+function testTranspose() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4d.create();
+  goog.vec.mat4d.transpose(m0, m1);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m1);
+
+  goog.vec.mat4d.transpose(m1, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+}
+
+function testDeterminant() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertEquals(0, goog.vec.mat4d.determinant(m0));
+  assertElementsEquals(
+      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.mat4d.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+  assertEquals(160, goog.vec.mat4d.determinant(m0));
+  assertElementsEquals(
+      [1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3], m0);
+}
+
+function testInvert() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertFalse(goog.vec.mat4d.invert(m0, m0));
+  assertElementsEquals(
+      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.mat4d.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+  assertTrue(goog.vec.mat4d.invert(m0, m0));
+  assertElementsRoughlyEqual(
+      [-0.225, 0.025, 0.025, 0.275, 0.025, 0.025, 0.275, -0.225,
+       0.025, 0.275, -0.225, 0.025, 0.275, -0.225, 0.025, 0.025], m0,
+       goog.vec.EPSILON);
+
+  goog.vec.mat4d.makeScale(m0, .01, .01, .01);
+  assertTrue(goog.vec.mat4d.invert(m0, m0));
+  var m1 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeScale(m1, 100, 100, 100);
+  assertElementsEquals(m1, m0);
+}
+
+function testEquals() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4d.setFromMat4d(goog.vec.mat4d.create(), m0);
+  assertTrue(goog.vec.mat4d.equals(m0, m1));
+  assertTrue(goog.vec.mat4d.equals(m1, m0));
+  for (var i = 0; i < 16; i++) {
+    m1[i] = 18;
+    assertFalse(goog.vec.mat4d.equals(m0, m1));
+    assertFalse(goog.vec.mat4d.equals(m1, m0));
+    m1[i] = i + 1;
+  }
+}
+
+function testMultVec3() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.mat4d.multVec3(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([51, 58, 65], v1);
+
+  goog.vec.mat4d.multVec3(m0, v0, v0);
+  assertElementsEquals([51, 58, 65], v0);
+}
+
+function testMultVec3NoTranslate() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.mat4d.multVec3NoTranslate(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([38, 44, 50], v1);
+
+  goog.vec.mat4d.multVec3NoTranslate(m0, v0, v0);
+  assertElementsEquals([38, 44, 50], v0);
+}
+
+function testMultVec3Projective() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+  var invw = 1 / 72;
+
+  goog.vec.mat4d.multVec3Projective(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals(
+      [51 * invw, 58 * invw, 65 * invw], v1);
+
+  goog.vec.mat4d.multVec3Projective(m0, v0, v0);
+  assertElementsEquals(
+      [51 * invw, 58 * invw, 65 * invw], v0);
+}
+
+function testMultVec4() {
+  var m0 = goog.vec.mat4d.setFromValues(goog.vec.mat4d.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3, 4];
+  var v1 = [0, 0, 0, 0];
+
+  goog.vec.mat4d.multVec4(m0, v0, v1);
+  assertElementsEquals([90, 100, 110, 120], v1);
+  goog.vec.mat4d.multVec4(m0, v0, v0);
+  assertElementsEquals([90, 100, 110, 120], v0);
+}
+
+function testSetValues() {
+  var a0 = goog.vec.mat4d.create();
+  assertElementsEquals(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], a0);
+  a0 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], a0);
+
+  var a1 = goog.vec.mat4d.create();
+  goog.vec.mat4d.setDiagonalValues(a1, 1, 2, 3, 4);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4], a1);
+
+  goog.vec.mat4d.setColumnValues(a1, 0, 2, 3, 4, 5);
+  goog.vec.mat4d.setColumnValues(a1, 1, 6, 7, 8, 9);
+  goog.vec.mat4d.setColumnValues(a1, 2, 10, 11, 12, 13);
+  goog.vec.mat4d.setColumnValues(a1, 3, 14, 15, 16, 1);
+  assertElementsEquals(
+      [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1], a1);
+
+  goog.vec.mat4d.setRowValues(a1, 0, 1, 5, 9, 13);
+  goog.vec.mat4d.setRowValues(a1, 1, 2, 6, 10, 14);
+  goog.vec.mat4d.setRowValues(a1, 2, 3, 7, 11, 15);
+  goog.vec.mat4d.setRowValues(a1, 3, 4, 8, 12, 16);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], a1);
+}
+
+function testMakeTranslate() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeTranslate(m0, 3, 4, 5);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 4, 5, 1], m0);
+}
+
+function testMakeScale() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeScale(m0, 3, 4, 5);
+  assertElementsEquals(
+      [3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1], m0);
+}
+
+function testMakeRotate() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeRotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeRotate(m1, -Math.PI / 4, 0, 0, 1);
+  goog.vec.mat4d.multMat(m0, m1, m1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0, 0,
+       -0.7071068, 0.7071068, 0, 0,
+       0, 0, 1, 0,
+       0, 0, 0, 1],
+      m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateX() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.create();
+
+  goog.vec.mat4d.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat4d.makeRotate(m1, Math.PI / 7, 1, 0, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateY() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.create();
+
+  goog.vec.mat4d.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat4d.makeRotate(m1, Math.PI / 7, 0, 1, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateZ() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.create();
+
+  goog.vec.mat4d.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat4d.makeRotate(m1, Math.PI / 7, 0, 0, 1);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testTranslate() {
+  var m0 = goog.vec.mat4d.makeIdentity(goog.vec.mat4d.create());
+  goog.vec.mat4d.translate(m0, 3, 4, 5);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 4, 5, 1], m0);
+
+  goog.vec.mat4d.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+
+  var m1 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeTranslate(m1, 5, 6, 7);
+  var m2 = goog.vec.mat4d.create();
+  goog.vec.mat4d.multMat(m0, m1, m2);
+  goog.vec.mat4d.translate(m0, 5, 6, 7);
+  assertElementsEquals(m2, m0);
+}
+
+function testScale() {
+  var m0 = goog.vec.mat4d.makeIdentity(goog.vec.mat4d.create());
+  goog.vec.mat4d.scale(m0, 3, 4, 5);
+  assertElementsEquals([3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1], m0);
+}
+
+function testRotate() {
+  var m0 = goog.vec.mat4d.makeIdentity(goog.vec.mat4d.create());
+  goog.vec.mat4d.rotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  goog.vec.mat4d.rotate(m0, -Math.PI / 4, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0, 0,
+       -0.7071068, 0.7071068, 0, 0,
+       0, 0, 1, 0,
+       0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+}
+
+function testRotateX() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(), randommat4d);
+
+  goog.vec.mat4d.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat4d.multMat(m1, m0, m0);
+  goog.vec.mat4d.rotateX(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(), randommat4d);
+
+  goog.vec.mat4d.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat4d.multMat(m1, m0, m0);
+  goog.vec.mat4d.rotateY(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var m0 = goog.vec.mat4d.create();
+  var m1 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(), randommat4d);
+
+  goog.vec.mat4d.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat4d.multMat(m1, m0, m0);
+  goog.vec.mat4d.rotateZ(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotationTranslation() {
+  // Create manually.
+  var m0 = goog.vec.mat4d.makeIdentity(goog.vec.mat4d.create());
+  goog.vec.mat4d.translate(m0, 3, 4, 5);
+  goog.vec.mat4d.rotate(m0, Math.PI / 2, 3 / 13, 4 / 13, 12 / 13);
+
+  // Create using makeRotationTranslation.
+  var m1 = goog.vec.mat4d.create();
+  var q = goog.vec.Quaternion.createFloat64();
+  var axis = goog.vec.vec3d.createFromValues(3 / 13, 4 / 13, 12 / 13);
+  goog.vec.Quaternion.fromAngleAxis(Math.PI / 2, axis, q);
+  var v = goog.vec.vec3d.createFromValues(3, 4, 5);
+  goog.vec.mat4d.makeRotationTranslation(m1, q, v);
+
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotationTranslationScale() {
+  // Create manually.
+  var m0 = goog.vec.mat4d.makeIdentity(goog.vec.mat4d.create());
+  goog.vec.mat4d.translate(m0, 3, 4, 5);
+  goog.vec.mat4d.rotate(m0, Math.PI / 2, 3 / 13, 4 / 13, 12 / 13);
+  goog.vec.mat4d.scale(m0, 6, 7, 8);
+
+  // Create using makeRotationTranslationScale.
+  var m1 = goog.vec.mat4d.create();
+  var q = goog.vec.Quaternion.createFloat64();
+  var axis = goog.vec.vec3d.createFromValues(3 / 13, 4 / 13, 12 / 13);
+  goog.vec.Quaternion.fromAngleAxis(Math.PI / 2, axis, q);
+  var v = goog.vec.vec3d.createFromValues(3, 4, 5);
+  var s = goog.vec.vec3d.createFromValues(6, 7, 8);
+  goog.vec.mat4d.makeRotationTranslationScale(m1, q, v, s);
+
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotationTranslationScaleOrigin() {
+  // Create manually.
+  var m0 = goog.vec.mat4d.makeIdentity(goog.vec.mat4d.create());
+  goog.vec.mat4d.translate(m0, 3, 4, 5);
+  goog.vec.mat4d.translate(m0, 9, 10, -11); // Origin.
+  goog.vec.mat4d.rotate(m0, Math.PI / 2, 3 / 13, 4 / 13, 12 / 13);
+  goog.vec.mat4d.scale(m0, 6, 7, 8);
+  goog.vec.mat4d.translate(m0, -9, -10, 11); // -Origin.
+
+  // Create using makeRotationTranslationScaleOrigin.
+  var m1 = goog.vec.mat4d.create();
+  var q = goog.vec.Quaternion.createFloat64();
+  var axis = goog.vec.vec3d.createFromValues(3 / 13, 4 / 13, 12 / 13);
+  goog.vec.Quaternion.fromAngleAxis(Math.PI / 2, axis, q);
+  var v = goog.vec.vec3d.createFromValues(3, 4, 5);
+  var s = goog.vec.vec3d.createFromValues(6, 7, 8);
+  var o = goog.vec.vec3d.createFromValues(9, 10, -11);
+  goog.vec.mat4d.makeRotationTranslationScaleOrigin(m1, q, v, s, o);
+
+  assertElementsRoughlyEqual(m0, m1, 0.00001); // Slightly larger epsilon.
+}
+
+function testGetTranslation() {
+  var mat = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(), randommat4d);
+  var translation = goog.vec.vec3d.create();
+  goog.vec.mat4d.getTranslation(mat, translation);
+  assertElementsRoughlyEqual(
+      [0.59465038776, 0.413479506969, 0.0663217827677],
+      translation, goog.vec.EPSILON);
+}
+
+function testMakeFrustum() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeFrustum(m0, -1, 2, -2, 1, .1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.06666666, 0, 0, 0,
+       0, 0.06666666, 0, 0,
+       0.33333333, -0.33333333, -1.2, -1,
+       0, 0, -0.22, 0], m0, goog.vec.EPSILON);
+}
+
+function testMakePerspective() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makePerspective(m0, 90 * Math.PI / 180, 2, 0.1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1.2, -1, 0, 0, -0.22, 0],
+      m0, goog.vec.EPSILON);
+}
+
+function testMakeOrtho() {
+  var m0 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeOrtho(m0, -1, 2, -2, 1, 0.1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.6666666, 0, 0, 0,
+       0, 0.6666666, 0, 0,
+       0, 0, -2, 0,
+       -0.333333, 0.3333333, -1.2, 1], m0, goog.vec.EPSILON);
+
+}
+
+function testMakeEulerZXZ() {
+  var m0 = goog.vec.mat4d.create();
+  var roll = 0.200982 * 2 * Math.PI;
+  var tilt = 0.915833 * Math.PI;
+  var yaw = 0.839392 * 2 * Math.PI;
+
+  goog.vec.mat4d.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat4d.rotate(m0, tilt, 1, 0, 0);
+  goog.vec.mat4d.rotate(m0, yaw, 0, 0, 1);
+
+  var m1 = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeEulerZXZ(m1, roll, tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+
+  var euler = [0, 0, 0];
+  goog.vec.mat4d.toEulerZXZ(m0, euler);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+
+  // Test negative tilt now.
+  goog.vec.mat4d.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat4d.rotate(m0, -tilt, 1, 0, 0);
+  goog.vec.mat4d.rotate(m0, yaw, 0, 0, 1);
+
+  goog.vec.mat4d.makeEulerZXZ(m1, roll, -tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat4d.toEulerZXZ(m0, euler, true);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(-tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+}
+
+function testEulerZXZExtrema() {
+  var m0 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(),
+  [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1]);
+  var m1 = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(),
+  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat4d.toEulerZXZ(m0, euler);
+  assertElementsRoughlyEqual(
+      [Math.PI, Math.PI / 2, Math.PI], euler, goog.vec.EPSILON);
+  goog.vec.mat4d.makeEulerZXZ(m1, euler[0], euler[1], euler[2]);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testLookAt() {
+  var viewMatrix = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeLookAt(
+    viewMatrix, [0, 0, 0], [1, 0, 0], [0, 1, 0]);
+  assertElementsRoughlyEqual(
+    [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1], viewMatrix,
+    goog.vec.EPSILON);
+}
+
+function testToLookAt() {
+  // This test does not use the default precision goog.vec.EPSILON due to
+  // precision issues in some browsers leading to flaky tests.
+  var EPSILON = 1e-4;
+
+  var eyeExp = [0, 0, 0];
+  var fwdExp = [1, 0, 0];
+  var upExp = [0, 1, 0];
+
+  var centerExp = [0, 0, 0];
+  goog.vec.vec3d.add(eyeExp, fwdExp, centerExp);
+
+  var view = goog.vec.mat4d.create();
+  goog.vec.mat4d.makeLookAt(view, eyeExp, centerExp, upExp);
+
+  var eyeRes = [0, 0, 0];
+  var fwdRes = [0, 0, 0];
+  var upRes = [0, 0, 0];
+  goog.vec.mat4d.toLookAt(view, eyeRes, fwdRes, upRes);
+  assertElementsRoughlyEqual(eyeExp, eyeRes, EPSILON);
+  assertElementsRoughlyEqual(fwdExp, fwdRes, EPSILON);
+  assertElementsRoughlyEqual(upExp, upRes, EPSILON);
+}
+
+function testLookAtDecomposition() {
+  // This test does not use the default precision goog.vec.EPSILON due to
+  // precision issues in some browsers leading to flaky tests.
+  var EPSILON = 1e-4;
+
+  var viewExp = goog.vec.mat4d.create();
+  var viewRes = goog.vec.mat4d.create();
+
+  // Get a valid set of random vectors eye, forward, up by decomposing
+  // a random matrix into a set of lookAt vectors.
+  var tmp = goog.vec.mat4d.setFromArray(goog.vec.mat4d.create(), randommat4d);
+  var eyeExp = [0, 0, 0];
+  var fwdExp = [0, 0, 0];
+  var upExp = [0, 0, 0];
+  var centerExp = [0, 0, 0];
+  // Project the random matrix into a real modelview matrix.
+  goog.vec.mat4d.toLookAt(tmp, eyeExp, fwdExp, upExp);
+  goog.vec.vec3d.add(eyeExp, fwdExp, centerExp);
+
+  // Compute the expected modelview matrix from a set of valid random vectors.
+  goog.vec.mat4d.makeLookAt(viewExp, eyeExp, centerExp, upExp);
+
+  var eyeRes = [0, 0, 0];
+  var fwdRes = [0, 0, 0];
+  var upRes = [0, 0, 0];
+  var centerRes = [0, 0, 0];
+  goog.vec.mat4d.toLookAt(viewExp, eyeRes, fwdRes, upRes);
+  goog.vec.vec3d.add(eyeRes, fwdRes, centerRes);
+
+  goog.vec.mat4d.makeLookAt(viewRes, eyeRes, centerRes, upRes);
+
+  assertElementsRoughlyEqual(eyeExp, eyeRes, EPSILON);
+  assertElementsRoughlyEqual(fwdExp, fwdRes, EPSILON);
+  assertElementsRoughlyEqual(upExp, upRes, EPSILON);
+  assertElementsRoughlyEqual(viewExp, viewRes, EPSILON);
+}

+ 1943 - 0
public/lib/closure/vec/mat4f.js

@@ -0,0 +1,1943 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat4d.js by running:            //
+//   swap_type.sh mat4f.js > mat4d.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 4x4 float (32bit)
+ * matrices.  The matrices are stored in column-major order.
+ *
+ * The last parameter will typically be the output matrix and an
+ * object can be both an input and output parameter to all methods except
+ * where noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.mat4f');
+goog.provide('goog.vec.mat4f.Type');
+
+goog.require('goog.vec');
+/** @suppress {extraRequire} */
+goog.require('goog.vec.Quaternion');
+goog.require('goog.vec.vec3f');
+goog.require('goog.vec.vec4f');
+
+
+/** @typedef {goog.vec.Float32} */ goog.vec.mat4f.Type;
+
+
+/**
+ * Creates a mat4f with all elements initialized to zero.
+ *
+ * @return {!goog.vec.mat4f.Type} The new mat4f.
+ */
+goog.vec.mat4f.create = function() {
+  return new Float32Array(16);
+};
+
+
+/**
+ * Creates a mat4f identity matrix.
+ *
+ * @return {!goog.vec.mat4f.Type} The new mat4f.
+ */
+goog.vec.mat4f.createIdentity = function() {
+  var mat = goog.vec.mat4f.create();
+  mat[0] = mat[5] = mat[10] = mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Initializes the matrix from the set of values. Note the values supplied are
+ * in column major order.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the
+ *     values.
+ * @param {number} v00 The values at (0, 0).
+ * @param {number} v10 The values at (1, 0).
+ * @param {number} v20 The values at (2, 0).
+ * @param {number} v30 The values at (3, 0).
+ * @param {number} v01 The values at (0, 1).
+ * @param {number} v11 The values at (1, 1).
+ * @param {number} v21 The values at (2, 1).
+ * @param {number} v31 The values at (3, 1).
+ * @param {number} v02 The values at (0, 2).
+ * @param {number} v12 The values at (1, 2).
+ * @param {number} v22 The values at (2, 2).
+ * @param {number} v32 The values at (3, 2).
+ * @param {number} v03 The values at (0, 3).
+ * @param {number} v13 The values at (1, 3).
+ * @param {number} v23 The values at (2, 3).
+ * @param {number} v33 The values at (3, 3).
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setFromValues = function(
+    mat, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32, v03, v13,
+    v23, v33) {
+  mat[0] = v00;
+  mat[1] = v10;
+  mat[2] = v20;
+  mat[3] = v30;
+  mat[4] = v01;
+  mat[5] = v11;
+  mat[6] = v21;
+  mat[7] = v31;
+  mat[8] = v02;
+  mat[9] = v12;
+  mat[10] = v22;
+  mat[11] = v32;
+  mat[12] = v03;
+  mat[13] = v13;
+  mat[14] = v23;
+  mat[15] = v33;
+  return mat;
+};
+
+
+/**
+ * Initializes mat4f mat from mat4f src.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The destination matrix.
+ * @param {!goog.vec.mat4f.Type} src The source matrix.
+ * @return {!goog.vec.mat4f.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setFromMat4f = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  mat[9] = src[9];
+  mat[10] = src[10];
+  mat[11] = src[11];
+  mat[12] = src[12];
+  mat[13] = src[13];
+  mat[14] = src[14];
+  mat[15] = src[15];
+  return mat;
+};
+
+
+/**
+ * Initializes mat4f mat from mat4d src (typed as a Float64Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.mat4f.Type} mat The destination matrix.
+ * @param {Float64Array} src The source matrix.
+ * @return {!goog.vec.mat4f.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setFromMat4d = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  mat[9] = src[9];
+  mat[10] = src[10];
+  mat[11] = src[11];
+  mat[12] = src[12];
+  mat[13] = src[13];
+  mat[14] = src[14];
+  mat[15] = src[15];
+  return mat;
+};
+
+
+/**
+ * Initializes mat4f mat from Array src.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The destination matrix.
+ * @param {Array<number>} src The source matrix.
+ * @return {!goog.vec.mat4f.Type} Return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setFromArray = function(mat, src) {
+  mat[0] = src[0];
+  mat[1] = src[1];
+  mat[2] = src[2];
+  mat[3] = src[3];
+  mat[4] = src[4];
+  mat[5] = src[5];
+  mat[6] = src[6];
+  mat[7] = src[7];
+  mat[8] = src[8];
+  mat[9] = src[9];
+  mat[10] = src[10];
+  mat[11] = src[11];
+  mat[12] = src[12];
+  mat[13] = src[13];
+  mat[14] = src[14];
+  mat[15] = src[15];
+  return mat;
+};
+
+
+/**
+ * Retrieves the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @return {number} The element value at the requested row, column indices.
+ */
+goog.vec.mat4f.getElement = function(mat, row, column) {
+  return mat[row + column * 4];
+};
+
+
+/**
+ * Sets the element at the requested row and column.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix containing the value to
+ *     retrieve.
+ * @param {number} row The row index.
+ * @param {number} column The column index.
+ * @param {number} value The value to set at the requested row, column.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setElement = function(mat, row, column, value) {
+  mat[row + column * 4] = value;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given values.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {number} v00 The values for (0, 0).
+ * @param {number} v11 The values for (1, 1).
+ * @param {number} v22 The values for (2, 2).
+ * @param {number} v33 The values for (3, 3).
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setDiagonalValues = function(mat, v00, v11, v22, v33) {
+  mat[0] = v00;
+  mat[5] = v11;
+  mat[10] = v22;
+  mat[15] = v33;
+  return mat;
+};
+
+
+/**
+ * Sets the diagonal values of the matrix from the given vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec4f.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setDiagonal = function(mat, vec) {
+  mat[0] = vec[0];
+  mat[5] = vec[1];
+  mat[10] = vec[2];
+  mat[15] = vec[3];
+  return mat;
+};
+
+
+/**
+ * Gets the diagonal values of the matrix into the given vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix containing the values.
+ * @param {!goog.vec.vec4f.Type} vec The vector to receive the values.
+ * @param {number=} opt_diagonal Which diagonal to get. A value of 0 selects the
+ *     main diagonal, a positive number selects a super diagonal and a negative
+ *     number selects a sub diagonal.
+ * @return {!goog.vec.vec4f.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.getDiagonal = function(mat, vec, opt_diagonal) {
+  if (!opt_diagonal) {
+    // This is the most common case, so we avoid the for loop.
+    vec[0] = mat[0];
+    vec[1] = mat[5];
+    vec[2] = mat[10];
+    vec[3] = mat[15];
+  } else {
+    var offset = opt_diagonal > 0 ? 4 * opt_diagonal : -opt_diagonal;
+    for (var i = 0; i < 4 - Math.abs(opt_diagonal); i++) {
+      vec[i] = mat[offset + 5 * i];
+    }
+  }
+  return vec;
+};
+
+
+/**
+ * Sets the specified column with the supplied values.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {number} v0 The value for row 0.
+ * @param {number} v1 The value for row 1.
+ * @param {number} v2 The value for row 2.
+ * @param {number} v3 The value for row 3.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setColumnValues = function(mat, column, v0, v1, v2, v3) {
+  var i = column * 4;
+  mat[i] = v0;
+  mat[i + 1] = v1;
+  mat[i + 2] = v2;
+  mat[i + 3] = v3;
+  return mat;
+};
+
+
+/**
+ * Sets the specified column with the value from the supplied vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {number} column The column index to set the values on.
+ * @param {!goog.vec.vec4f.Type} vec The vector of elements for the column.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setColumn = function(mat, column, vec) {
+  var i = column * 4;
+  mat[i] = vec[0];
+  mat[i + 1] = vec[1];
+  mat[i + 2] = vec[2];
+  mat[i + 3] = vec[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the specified column from the matrix into the given vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the values.
+ * @param {number} column The column to get the values from.
+ * @param {!goog.vec.vec4f.Type} vec The vector of elements to
+ *     receive the column.
+ * @return {!goog.vec.vec4f.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.getColumn = function(mat, column, vec) {
+  var i = column * 4;
+  vec[0] = mat[i];
+  vec[1] = mat[i + 1];
+  vec[2] = mat[i + 2];
+  vec[3] = mat[i + 3];
+  return vec;
+};
+
+
+/**
+ * Sets the columns of the matrix from the given vectors.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec4f.Type} vec0 The values for column 0.
+ * @param {!goog.vec.vec4f.Type} vec1 The values for column 1.
+ * @param {!goog.vec.vec4f.Type} vec2 The values for column 2.
+ * @param {!goog.vec.vec4f.Type} vec3 The values for column 3.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setColumns = function(mat, vec0, vec1, vec2, vec3) {
+  mat[0] = vec0[0];
+  mat[1] = vec0[1];
+  mat[2] = vec0[2];
+  mat[3] = vec0[3];
+  mat[4] = vec1[0];
+  mat[5] = vec1[1];
+  mat[6] = vec1[2];
+  mat[7] = vec1[3];
+  mat[8] = vec2[0];
+  mat[9] = vec2[1];
+  mat[10] = vec2[2];
+  mat[11] = vec2[3];
+  mat[12] = vec3[0];
+  mat[13] = vec3[1];
+  mat[14] = vec3[2];
+  mat[15] = vec3[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the column values from the given matrix into the given vectors.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the columns.
+ * @param {!goog.vec.vec4f.Type} vec0 The vector to receive column 0.
+ * @param {!goog.vec.vec4f.Type} vec1 The vector to receive column 1.
+ * @param {!goog.vec.vec4f.Type} vec2 The vector to receive column 2.
+ * @param {!goog.vec.vec4f.Type} vec3 The vector to receive column 3.
+ */
+goog.vec.mat4f.getColumns = function(mat, vec0, vec1, vec2, vec3) {
+  vec0[0] = mat[0];
+  vec0[1] = mat[1];
+  vec0[2] = mat[2];
+  vec0[3] = mat[3];
+  vec1[0] = mat[4];
+  vec1[1] = mat[5];
+  vec1[2] = mat[6];
+  vec1[3] = mat[7];
+  vec2[0] = mat[8];
+  vec2[1] = mat[9];
+  vec2[2] = mat[10];
+  vec2[3] = mat[11];
+  vec3[0] = mat[12];
+  vec3[1] = mat[13];
+  vec3[2] = mat[14];
+  vec3[3] = mat[15];
+};
+
+
+/**
+ * Sets the row values from the supplied values.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {number} row The index of the row to receive the values.
+ * @param {number} v0 The value for column 0.
+ * @param {number} v1 The value for column 1.
+ * @param {number} v2 The value for column 2.
+ * @param {number} v3 The value for column 3.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setRowValues = function(mat, row, v0, v1, v2, v3) {
+  mat[row] = v0;
+  mat[row + 4] = v1;
+  mat[row + 8] = v2;
+  mat[row + 12] = v3;
+  return mat;
+};
+
+
+/**
+ * Sets the row values from the supplied vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the row values.
+ * @param {number} row The index of the row.
+ * @param {!goog.vec.vec4f.Type} vec The vector containing the values.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setRow = function(mat, row, vec) {
+  mat[row] = vec[0];
+  mat[row + 4] = vec[1];
+  mat[row + 8] = vec[2];
+  mat[row + 12] = vec[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the row values into the given vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the values.
+ * @param {number} row The index of the row supplying the values.
+ * @param {!goog.vec.vec4f.Type} vec The vector to receive the row.
+ * @return {!goog.vec.vec4f.Type} return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.getRow = function(mat, row, vec) {
+  vec[0] = mat[row];
+  vec[1] = mat[row + 4];
+  vec[2] = mat[row + 8];
+  vec[3] = mat[row + 12];
+  return vec;
+};
+
+
+/**
+ * Sets the rows of the matrix from the supplied vectors.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to receive the values.
+ * @param {!goog.vec.vec4f.Type} vec0 The values for row 0.
+ * @param {!goog.vec.vec4f.Type} vec1 The values for row 1.
+ * @param {!goog.vec.vec4f.Type} vec2 The values for row 2.
+ * @param {!goog.vec.vec4f.Type} vec3 The values for row 3.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.setRows = function(mat, vec0, vec1, vec2, vec3) {
+  mat[0] = vec0[0];
+  mat[1] = vec1[0];
+  mat[2] = vec2[0];
+  mat[3] = vec3[0];
+  mat[4] = vec0[1];
+  mat[5] = vec1[1];
+  mat[6] = vec2[1];
+  mat[7] = vec3[1];
+  mat[8] = vec0[2];
+  mat[9] = vec1[2];
+  mat[10] = vec2[2];
+  mat[11] = vec3[2];
+  mat[12] = vec0[3];
+  mat[13] = vec1[3];
+  mat[14] = vec2[3];
+  mat[15] = vec3[3];
+  return mat;
+};
+
+
+/**
+ * Retrieves the rows of the matrix into the supplied vectors.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to supply the values.
+ * @param {!goog.vec.vec4f.Type} vec0 The vector to receive row 0.
+ * @param {!goog.vec.vec4f.Type} vec1 The vector to receive row 1.
+ * @param {!goog.vec.vec4f.Type} vec2 The vector to receive row 2.
+ * @param {!goog.vec.vec4f.Type} vec3 The vector to receive row 3.
+ */
+goog.vec.mat4f.getRows = function(mat, vec0, vec1, vec2, vec3) {
+  vec0[0] = mat[0];
+  vec1[0] = mat[1];
+  vec2[0] = mat[2];
+  vec3[0] = mat[3];
+  vec0[1] = mat[4];
+  vec1[1] = mat[5];
+  vec2[1] = mat[6];
+  vec3[1] = mat[7];
+  vec0[2] = mat[8];
+  vec1[2] = mat[9];
+  vec2[2] = mat[10];
+  vec3[2] = mat[11];
+  vec0[3] = mat[12];
+  vec1[3] = mat[13];
+  vec2[3] = mat[14];
+  vec3[3] = mat[15];
+};
+
+
+/**
+ * Makes the given 4x4 matrix the zero matrix.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @return {!goog.vec.mat4f.Type} return mat so operations can be chained.
+ */
+goog.vec.mat4f.makeZero = function(mat) {
+  mat[0] = 0;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 0;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 0;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 0;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix the identity matrix.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @return {!goog.vec.mat4f.Type} return mat so operations can be chained.
+ */
+goog.vec.mat4f.makeIdentity = function(mat) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 1;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 1;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Performs a per-component addition of the matrix mat0 and mat1, storing
+ * the result into resultMat.
+ *
+ * @param {!goog.vec.mat4f.Type} mat0 The first addend.
+ * @param {!goog.vec.mat4f.Type} mat1 The second addend.
+ * @param {!goog.vec.mat4f.Type} resultMat The matrix to
+ *     receive the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat4f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.addMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] + mat1[0];
+  resultMat[1] = mat0[1] + mat1[1];
+  resultMat[2] = mat0[2] + mat1[2];
+  resultMat[3] = mat0[3] + mat1[3];
+  resultMat[4] = mat0[4] + mat1[4];
+  resultMat[5] = mat0[5] + mat1[5];
+  resultMat[6] = mat0[6] + mat1[6];
+  resultMat[7] = mat0[7] + mat1[7];
+  resultMat[8] = mat0[8] + mat1[8];
+  resultMat[9] = mat0[9] + mat1[9];
+  resultMat[10] = mat0[10] + mat1[10];
+  resultMat[11] = mat0[11] + mat1[11];
+  resultMat[12] = mat0[12] + mat1[12];
+  resultMat[13] = mat0[13] + mat1[13];
+  resultMat[14] = mat0[14] + mat1[14];
+  resultMat[15] = mat0[15] + mat1[15];
+  return resultMat;
+};
+
+
+/**
+ * Performs a per-component subtraction of the matrix mat0 and mat1,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat4f.Type} mat0 The minuend.
+ * @param {!goog.vec.mat4f.Type} mat1 The subtrahend.
+ * @param {!goog.vec.mat4f.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat4f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.subMat = function(mat0, mat1, resultMat) {
+  resultMat[0] = mat0[0] - mat1[0];
+  resultMat[1] = mat0[1] - mat1[1];
+  resultMat[2] = mat0[2] - mat1[2];
+  resultMat[3] = mat0[3] - mat1[3];
+  resultMat[4] = mat0[4] - mat1[4];
+  resultMat[5] = mat0[5] - mat1[5];
+  resultMat[6] = mat0[6] - mat1[6];
+  resultMat[7] = mat0[7] - mat1[7];
+  resultMat[8] = mat0[8] - mat1[8];
+  resultMat[9] = mat0[9] - mat1[9];
+  resultMat[10] = mat0[10] - mat1[10];
+  resultMat[11] = mat0[11] - mat1[11];
+  resultMat[12] = mat0[12] - mat1[12];
+  resultMat[13] = mat0[13] - mat1[13];
+  resultMat[14] = mat0[14] - mat1[14];
+  resultMat[15] = mat0[15] - mat1[15];
+  return resultMat;
+};
+
+
+/**
+ * Multiplies matrix mat with the given scalar, storing the result
+ * into resultMat.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} scalar The scalar value to multiply to each element of mat.
+ * @param {!goog.vec.mat4f.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat4f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.multScalar = function(mat, scalar, resultMat) {
+  resultMat[0] = mat[0] * scalar;
+  resultMat[1] = mat[1] * scalar;
+  resultMat[2] = mat[2] * scalar;
+  resultMat[3] = mat[3] * scalar;
+  resultMat[4] = mat[4] * scalar;
+  resultMat[5] = mat[5] * scalar;
+  resultMat[6] = mat[6] * scalar;
+  resultMat[7] = mat[7] * scalar;
+  resultMat[8] = mat[8] * scalar;
+  resultMat[9] = mat[9] * scalar;
+  resultMat[10] = mat[10] * scalar;
+  resultMat[11] = mat[11] * scalar;
+  resultMat[12] = mat[12] * scalar;
+  resultMat[13] = mat[13] * scalar;
+  resultMat[14] = mat[14] * scalar;
+  resultMat[15] = mat[15] * scalar;
+  return resultMat;
+};
+
+
+/**
+ * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
+ * storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat4f.Type} mat0 The first (left hand) matrix.
+ * @param {!goog.vec.mat4f.Type} mat1 The second (right hand) matrix.
+ * @param {!goog.vec.mat4f.Type} resultMat The matrix to receive
+ *     the results (may be either mat0 or mat1).
+ * @return {!goog.vec.mat4f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.multMat = function(mat0, mat1, resultMat) {
+  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2], a30 = mat0[3];
+  var a01 = mat0[4], a11 = mat0[5], a21 = mat0[6], a31 = mat0[7];
+  var a02 = mat0[8], a12 = mat0[9], a22 = mat0[10], a32 = mat0[11];
+  var a03 = mat0[12], a13 = mat0[13], a23 = mat0[14], a33 = mat0[15];
+
+  var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2], b30 = mat1[3];
+  var b01 = mat1[4], b11 = mat1[5], b21 = mat1[6], b31 = mat1[7];
+  var b02 = mat1[8], b12 = mat1[9], b22 = mat1[10], b32 = mat1[11];
+  var b03 = mat1[12], b13 = mat1[13], b23 = mat1[14], b33 = mat1[15];
+
+  resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
+  resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
+  resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
+  resultMat[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
+
+  resultMat[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
+  resultMat[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
+  resultMat[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
+  resultMat[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
+
+  resultMat[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
+  resultMat[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
+  resultMat[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
+  resultMat[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
+
+  resultMat[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;
+  resultMat[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;
+  resultMat[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;
+  resultMat[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;
+  return resultMat;
+};
+
+
+/**
+ * Transposes the given matrix mat storing the result into resultMat.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to transpose.
+ * @param {!goog.vec.mat4f.Type} resultMat The matrix to receive
+ *     the results (may be mat).
+ * @return {!goog.vec.mat4f.Type} return resultMat so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.transpose = function(mat, resultMat) {
+  if (resultMat == mat) {
+    var a10 = mat[1], a20 = mat[2], a30 = mat[3];
+    var a21 = mat[6], a31 = mat[7];
+    var a32 = mat[11];
+    resultMat[1] = mat[4];
+    resultMat[2] = mat[8];
+    resultMat[3] = mat[12];
+    resultMat[4] = a10;
+    resultMat[6] = mat[9];
+    resultMat[7] = mat[13];
+    resultMat[8] = a20;
+    resultMat[9] = a21;
+    resultMat[11] = mat[14];
+    resultMat[12] = a30;
+    resultMat[13] = a31;
+    resultMat[14] = a32;
+  } else {
+    resultMat[0] = mat[0];
+    resultMat[1] = mat[4];
+    resultMat[2] = mat[8];
+    resultMat[3] = mat[12];
+
+    resultMat[4] = mat[1];
+    resultMat[5] = mat[5];
+    resultMat[6] = mat[9];
+    resultMat[7] = mat[13];
+
+    resultMat[8] = mat[2];
+    resultMat[9] = mat[6];
+    resultMat[10] = mat[10];
+    resultMat[11] = mat[14];
+
+    resultMat[12] = mat[3];
+    resultMat[13] = mat[7];
+    resultMat[14] = mat[11];
+    resultMat[15] = mat[15];
+  }
+  return resultMat;
+};
+
+
+/**
+ * Computes the determinant of the matrix.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to compute the matrix for.
+ * @return {number} The determinant of the matrix.
+ */
+goog.vec.mat4f.determinant = function(mat) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+  var a0 = m00 * m11 - m10 * m01;
+  var a1 = m00 * m21 - m20 * m01;
+  var a2 = m00 * m31 - m30 * m01;
+  var a3 = m10 * m21 - m20 * m11;
+  var a4 = m10 * m31 - m30 * m11;
+  var a5 = m20 * m31 - m30 * m21;
+  var b0 = m02 * m13 - m12 * m03;
+  var b1 = m02 * m23 - m22 * m03;
+  var b2 = m02 * m33 - m32 * m03;
+  var b3 = m12 * m23 - m22 * m13;
+  var b4 = m12 * m33 - m32 * m13;
+  var b5 = m22 * m33 - m32 * m23;
+
+  return a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+};
+
+
+/**
+ * Computes the inverse of mat storing the result into resultMat. If the
+ * inverse is defined, this function returns true, false otherwise.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix to invert.
+ * @param {!goog.vec.mat4f.Type} resultMat The matrix to receive
+ *     the result (may be mat).
+ * @return {boolean} True if the inverse is defined. If false is returned,
+ *     resultMat is not modified.
+ */
+goog.vec.mat4f.invert = function(mat, resultMat) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
+
+  var a0 = m00 * m11 - m10 * m01;
+  var a1 = m00 * m21 - m20 * m01;
+  var a2 = m00 * m31 - m30 * m01;
+  var a3 = m10 * m21 - m20 * m11;
+  var a4 = m10 * m31 - m30 * m11;
+  var a5 = m20 * m31 - m30 * m21;
+  var b0 = m02 * m13 - m12 * m03;
+  var b1 = m02 * m23 - m22 * m03;
+  var b2 = m02 * m33 - m32 * m03;
+  var b3 = m12 * m23 - m22 * m13;
+  var b4 = m12 * m33 - m32 * m13;
+  var b5 = m22 * m33 - m32 * m23;
+
+  var det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+  if (det == 0) {
+    return false;
+  }
+
+  var idet = 1.0 / det;
+  resultMat[0] = (m11 * b5 - m21 * b4 + m31 * b3) * idet;
+  resultMat[1] = (-m10 * b5 + m20 * b4 - m30 * b3) * idet;
+  resultMat[2] = (m13 * a5 - m23 * a4 + m33 * a3) * idet;
+  resultMat[3] = (-m12 * a5 + m22 * a4 - m32 * a3) * idet;
+  resultMat[4] = (-m01 * b5 + m21 * b2 - m31 * b1) * idet;
+  resultMat[5] = (m00 * b5 - m20 * b2 + m30 * b1) * idet;
+  resultMat[6] = (-m03 * a5 + m23 * a2 - m33 * a1) * idet;
+  resultMat[7] = (m02 * a5 - m22 * a2 + m32 * a1) * idet;
+  resultMat[8] = (m01 * b4 - m11 * b2 + m31 * b0) * idet;
+  resultMat[9] = (-m00 * b4 + m10 * b2 - m30 * b0) * idet;
+  resultMat[10] = (m03 * a4 - m13 * a2 + m33 * a0) * idet;
+  resultMat[11] = (-m02 * a4 + m12 * a2 - m32 * a0) * idet;
+  resultMat[12] = (-m01 * b3 + m11 * b1 - m21 * b0) * idet;
+  resultMat[13] = (m00 * b3 - m10 * b1 + m20 * b0) * idet;
+  resultMat[14] = (-m03 * a3 + m13 * a1 - m23 * a0) * idet;
+  resultMat[15] = (m02 * a3 - m12 * a1 + m22 * a0) * idet;
+  return true;
+};
+
+
+/**
+ * Returns true if the components of mat0 are equal to the components of mat1.
+ *
+ * @param {!goog.vec.mat4f.Type} mat0 The first matrix.
+ * @param {!goog.vec.mat4f.Type} mat1 The second matrix.
+ * @return {boolean} True if the the two matrices are equivalent.
+ */
+goog.vec.mat4f.equals = function(mat0, mat1) {
+  return mat0.length == mat1.length && mat0[0] == mat1[0] &&
+      mat0[1] == mat1[1] && mat0[2] == mat1[2] && mat0[3] == mat1[3] &&
+      mat0[4] == mat1[4] && mat0[5] == mat1[5] && mat0[6] == mat1[6] &&
+      mat0[7] == mat1[7] && mat0[8] == mat1[8] && mat0[9] == mat1[9] &&
+      mat0[10] == mat1[10] && mat0[11] == mat1[11] && mat0[12] == mat1[12] &&
+      mat0[13] == mat1[13] && mat0[14] == mat1[14] && mat0[15] == mat1[15];
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 3x4 matrix omitting the projective component.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3f.Type} vec The 3 element vector to transform.
+ * @param {!goog.vec.vec3f.Type} resultVec The 3 element vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec3f.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.multVec3 = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + mat[12];
+  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + mat[13];
+  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + mat[14];
+  return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * upper 3x3 matrix omitting the projective component and translation
+ * components.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3f.Type} vec The 3 element vector to transform.
+ * @param {!goog.vec.vec3f.Type} resultVec The 3 element vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec3f.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.multVec3NoTranslate = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8];
+  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9];
+  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10];
+  return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec. The input vector is multiplied against the
+ * full 4x4 matrix with the homogeneous divide applied to reduce the 4 element
+ * vector to a 3 element vector.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec3f.Type} vec The 3 element vector to transform.
+ * @param {!goog.vec.vec3f.Type} resultVec The 3 element vector
+ *     to receive the results (may be vec).
+ * @return {!goog.vec.vec3f.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.multVec3Projective = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  var invw = 1 / (x * mat[3] + y * mat[7] + z * mat[11] + mat[15]);
+  resultVec[0] = (x * mat[0] + y * mat[4] + z * mat[8] + mat[12]) * invw;
+  resultVec[1] = (x * mat[1] + y * mat[5] + z * mat[9] + mat[13]) * invw;
+  resultVec[2] = (x * mat[2] + y * mat[6] + z * mat[10] + mat[14]) * invw;
+  return resultVec;
+};
+
+
+/**
+ * Transforms the given vector with the given matrix storing the resulting,
+ * transformed vector into resultVec.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix supplying the transformation.
+ * @param {!goog.vec.vec4f.Type} vec The vector to transform.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to
+ *     receive the results (may be vec).
+ * @return {!goog.vec.vec4f.Type} return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.multVec4 = function(mat, vec, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
+  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + w * mat[12];
+  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + w * mat[13];
+  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + w * mat[14];
+  resultVec[3] = x * mat[3] + y * mat[7] + z * mat[11] + w * mat[15];
+  return resultVec;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a translation matrix with x, y and z
+ * translation factors.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @param {number} z The translation along the z axis.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeTranslate = function(mat, x, y, z) {
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 1;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 1;
+  mat[11] = 0;
+  mat[12] = x;
+  mat[13] = y;
+  mat[14] = z;
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix as a scale matrix with x, y and z scale factors.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} x The scale along the x axis.
+ * @param {number} y The scale along the y axis.
+ * @param {number} z The scale along the z axis.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeScale = function(mat, x, y, z) {
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = y;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = z;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the axis defined by the vector (ax, ay, az).
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @param {number} ax The x component of the rotation axis.
+ * @param {number} ay The y component of the rotation axis.
+ * @param {number} az The z component of the rotation axis.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotate = function(mat, angle, ax, ay, az) {
+  var c = Math.cos(angle);
+  var d = 1 - c;
+  var s = Math.sin(angle);
+
+  mat[0] = ax * ax * d + c;
+  mat[1] = ax * ay * d + az * s;
+  mat[2] = ax * az * d - ay * s;
+  mat[3] = 0;
+  mat[4] = ax * ay * d - az * s;
+  mat[5] = ay * ay * d + c;
+  mat[6] = ay * az * d + ax * s;
+  mat[7] = 0;
+  mat[8] = ax * az * d + ay * s;
+  mat[9] = ay * az * d - ax * s;
+  mat[10] = az * az * d + c;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the X axis.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotateX = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = 1;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = c;
+  mat[6] = s;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = -s;
+  mat[10] = c;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Y axis.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotateY = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = 0;
+  mat[2] = -s;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = 1;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = s;
+  mat[9] = 0;
+  mat[10] = c;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix with the given rotation
+ * angle about the Z axis.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The rotation angle in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotateZ = function(mat, angle) {
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = c;
+  mat[1] = s;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = -s;
+  mat[5] = c;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = 1;
+  mat[11] = 0;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Creates a matrix from a quaternion rotation and vector translation.
+ *
+ * This is a specialization of makeRotationTranslationScaleOrigin.
+ *
+ * This is equivalent to, but faster than:
+ *     goog.vec.mat4f.makeIdentity(m);
+ *     goog.vec.mat4f.translate(m, tx, ty, tz);
+ *     goog.vec.mat4f.rotate(m, theta, rx, ry, rz);
+ * and:
+ *     goog.vec.Quaternion.toRotationMatrix4(rotation, mat);
+ *     mat[12] = translation[0];
+ *     mat[13] = translation[1];
+ *     mat[14] = translation[2];
+ * See http://jsperf.com/goog-vec-makerotationtranslation2 .
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {!goog.vec.Quaternion.AnyType} rotation The quaternion rotation.
+ *     Note: this quaternion is assumed to already be normalized.
+ * @param {!goog.vec.vec3f.Type} translation The vector translation.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotationTranslation = function(mat, rotation, translation) {
+  // Quaternion math
+  var x = rotation[0], y = rotation[1], z = rotation[2], w = rotation[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var xx = x * x2;
+  var xy = x * y2;
+  var xz = x * z2;
+  var yy = y * y2;
+  var yz = y * z2;
+  var zz = z * z2;
+  var wx = w * x2;
+  var wy = w * y2;
+  var wz = w * z2;
+
+  mat[0] = 1 - (yy + zz);
+  mat[1] = xy + wz;
+  mat[2] = xz - wy;
+  mat[3] = 0;
+  mat[4] = xy - wz;
+  mat[5] = 1 - (xx + zz);
+  mat[6] = yz + wx;
+  mat[7] = 0;
+  mat[8] = xz + wy;
+  mat[9] = yz - wx;
+  mat[10] = 1 - (xx + yy);
+  mat[11] = 0;
+  mat[12] = translation[0];
+  mat[13] = translation[1];
+  mat[14] = translation[2];
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Creates a matrix from a quaternion rotation, vector translation, and
+ * vector scale.
+ *
+ * This is a specialization of makeRotationTranslationScaleOrigin.
+ *
+ * This is equivalent to, but faster than:
+ *     goog.vec.mat4f.makeIdentity(m);
+ *     goog.vec.mat4f.translate(m, tx, ty, tz);
+ *     goog.vec.mat4f.rotate(m, theta, rx, ry, rz);
+ *     goog.vec.mat4f.scale(m, sx, sy, sz);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {!goog.vec.Quaternion.AnyType} rotation The quaternion rotation.
+ *     Note: this quaternion is assumed to already be normalized.
+ * @param {!goog.vec.vec3f.Type} translation The vector translation.
+ * @param {!goog.vec.vec3f.Type} scale The vector scale.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotationTranslationScale = function(
+    mat, rotation, translation, scale) {
+  // Quaternion math
+  var x = rotation[0], y = rotation[1], z = rotation[2], w = rotation[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var xx = x * x2;
+  var xy = x * y2;
+  var xz = x * z2;
+  var yy = y * y2;
+  var yz = y * z2;
+  var zz = z * z2;
+  var wx = w * x2;
+  var wy = w * y2;
+  var wz = w * z2;
+  var sx = scale[0];
+  var sy = scale[1];
+  var sz = scale[2];
+
+  mat[0] = (1 - (yy + zz)) * sx;
+  mat[1] = (xy + wz) * sx;
+  mat[2] = (xz - wy) * sx;
+  mat[3] = 0;
+  mat[4] = (xy - wz) * sy;
+  mat[5] = (1 - (xx + zz)) * sy;
+  mat[6] = (yz + wx) * sy;
+  mat[7] = 0;
+  mat[8] = (xz + wy) * sz;
+  mat[9] = (yz - wx) * sz;
+  mat[10] = (1 - (xx + yy)) * sz;
+  mat[11] = 0;
+  mat[12] = translation[0];
+  mat[13] = translation[1];
+  mat[14] = translation[2];
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Creates a matrix from a quaternion rotation, vector translation, and
+ * vector scale, rotating and scaling about the given origin.
+ *
+ * This is equivalent to, but faster than:
+ *     goog.vec.mat4f.makeIdentity(m);
+ *     goog.vec.mat4f.translate(m, tx, ty, tz);
+ *     goog.vec.mat4f.translate(m, ox, oy, oz);
+ *     goog.vec.mat4f.rotate(m, theta, rx, ry, rz);
+ *     goog.vec.mat4f.scale(m, sx, sy, sz);
+ *     goog.vec.mat4f.translate(m, -ox, -oy, -oz);
+ * See http://jsperf.com/glmatrix-matrix-variant-test/3 for performance
+ * results of a similar function in the glmatrix library.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {!goog.vec.Quaternion.AnyType} rotation The quaternion rotation.
+ *     Note: this quaternion is assumed to already be normalized.
+ * @param {!goog.vec.vec3f.Type} translation The vector translation.
+ * @param {!goog.vec.vec3f.Type} scale The vector scale.
+ * @param {!goog.vec.vec3f.Type} origin The origin about which to scale and
+ *     rotate.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeRotationTranslationScaleOrigin = function(
+    mat, rotation, translation, scale, origin) {
+  // Quaternion math
+  var x = rotation[0], y = rotation[1], z = rotation[2], w = rotation[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var xx = x * x2;
+  var xy = x * y2;
+  var xz = x * z2;
+  var yy = y * y2;
+  var yz = y * z2;
+  var zz = z * z2;
+  var wx = w * x2;
+  var wy = w * y2;
+  var wz = w * z2;
+  var sx = scale[0];
+  var sy = scale[1];
+  var sz = scale[2];
+  var ox = origin[0];
+  var oy = origin[1];
+  var oz = origin[2];
+
+  mat[0] = (1 - (yy + zz)) * sx;
+  mat[1] = (xy + wz) * sx;
+  mat[2] = (xz - wy) * sx;
+  mat[3] = 0;
+  mat[4] = (xy - wz) * sy;
+  mat[5] = (1 - (xx + zz)) * sy;
+  mat[6] = (yz + wx) * sy;
+  mat[7] = 0;
+  mat[8] = (xz + wy) * sz;
+  mat[9] = (yz - wx) * sz;
+  mat[10] = (1 - (xx + yy)) * sz;
+  mat[11] = 0;
+  mat[12] = translation[0] + ox - (mat[0] * ox + mat[4] * oy + mat[8] * oz);
+  mat[13] = translation[1] + oy - (mat[1] * ox + mat[5] * oy + mat[9] * oz);
+  mat[14] = translation[2] + oz - (mat[2] * ox + mat[6] * oy + mat[10] * oz);
+  mat[15] = 1;
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a perspective projection matrix.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} left The coordinate of the left clipping plane.
+ * @param {number} right The coordinate of the right clipping plane.
+ * @param {number} bottom The coordinate of the bottom clipping plane.
+ * @param {number} top The coordinate of the top clipping plane.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeFrustum = function(
+    mat, left, right, bottom, top, near, far) {
+  var x = (2 * near) / (right - left);
+  var y = (2 * near) / (top - bottom);
+  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);
+
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = y;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = a;
+  mat[9] = b;
+  mat[10] = c;
+  mat[11] = -1;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = d;
+  mat[15] = 0;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix  perspective projection matrix given a
+ * field of view and aspect ratio.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} fovy The field of view along the y (vertical) axis in
+ *     radians.
+ * @param {number} aspect The x (width) to y (height) aspect ratio.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makePerspective = function(mat, fovy, aspect, near, far) {
+  var angle = fovy / 2;
+  var dz = far - near;
+  var sinAngle = Math.sin(angle);
+  if (dz == 0 || sinAngle == 0 || aspect == 0) {
+    return mat;
+  }
+
+  var cot = Math.cos(angle) / sinAngle;
+
+  mat[0] = cot / aspect;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = cot;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = -(far + near) / dz;
+  mat[11] = -1;
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = -(2 * near * far) / dz;
+  mat[15] = 0;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix an orthographic projection matrix.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} left The coordinate of the left clipping plane.
+ * @param {number} right The coordinate of the right clipping plane.
+ * @param {number} bottom The coordinate of the bottom clipping plane.
+ * @param {number} top The coordinate of the top clipping plane.
+ * @param {number} near The distance to the near clipping plane.
+ * @param {number} far The distance to the far clipping plane.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeOrtho = function(mat, left, right, bottom, top, near, far) {
+  var x = 2 / (right - left);
+  var y = 2 / (top - bottom);
+  var z = -2 / (far - near);
+  var a = -(right + left) / (right - left);
+  var b = -(top + bottom) / (top - bottom);
+  var c = -(far + near) / (far - near);
+
+  mat[0] = x;
+  mat[1] = 0;
+  mat[2] = 0;
+  mat[3] = 0;
+  mat[4] = 0;
+  mat[5] = y;
+  mat[6] = 0;
+  mat[7] = 0;
+  mat[8] = 0;
+  mat[9] = 0;
+  mat[10] = z;
+  mat[11] = 0;
+  mat[12] = a;
+  mat[13] = b;
+  mat[14] = c;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a modelview matrix of a camera so that
+ * the camera is 'looking at' the given center point.
+ *
+ * Note that unlike most other goog.vec functions where we inline
+ * everything, this function does not inline various goog.vec
+ * functions.  This makes the code more readable, but somewhat
+ * less efficient.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {!goog.vec.vec3f.Type} eyePt The position of the eye point
+ *     (camera origin).
+ * @param {!goog.vec.vec3f.Type} centerPt The point to aim the camera at.
+ * @param {!goog.vec.vec3f.Type} worldUpVec The vector that identifies
+ *     the up direction for the camera.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeLookAt = function(mat, eyePt, centerPt, worldUpVec) {
+  // Compute the direction vector from the eye point to the center point and
+  // normalize.
+  var fwdVec = goog.vec.mat4f.tmpvec4f_[0];
+  goog.vec.vec3f.subtract(centerPt, eyePt, fwdVec);
+  goog.vec.vec3f.normalize(fwdVec, fwdVec);
+  fwdVec[3] = 0;
+
+  // Compute the side vector from the forward vector and the input up vector.
+  var sideVec = goog.vec.mat4f.tmpvec4f_[1];
+  goog.vec.vec3f.cross(fwdVec, worldUpVec, sideVec);
+  goog.vec.vec3f.normalize(sideVec, sideVec);
+  sideVec[3] = 0;
+
+  // Now the up vector to form the orthonormal basis.
+  var upVec = goog.vec.mat4f.tmpvec4f_[2];
+  goog.vec.vec3f.cross(sideVec, fwdVec, upVec);
+  goog.vec.vec3f.normalize(upVec, upVec);
+  upVec[3] = 0;
+
+  // Update the view matrix with the new orthonormal basis and position the
+  // camera at the given eye point.
+  goog.vec.vec3f.negate(fwdVec, fwdVec);
+  goog.vec.mat4f.setRow(mat, 0, sideVec);
+  goog.vec.mat4f.setRow(mat, 1, upVec);
+  goog.vec.mat4f.setRow(mat, 2, fwdVec);
+  goog.vec.mat4f.setRowValues(mat, 3, 0, 0, 0, 1);
+  goog.vec.mat4f.translate(mat, -eyePt[0], -eyePt[1], -eyePt[2]);
+
+  return mat;
+};
+
+
+/**
+ * Decomposes a matrix into the lookAt vectors eyePt, fwdVec and worldUpVec.
+ * The matrix represents the modelview matrix of a camera. It is the inverse
+ * of lookAt except for the output of the fwdVec instead of centerPt.
+ * The centerPt itself cannot be recovered from a modelview matrix.
+ *
+ * Note that unlike most other goog.vec functions where we inline
+ * everything, this function does not inline various goog.vec
+ * functions.  This makes the code more readable, but somewhat
+ * less efficient.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {!goog.vec.vec3f.Type} eyePt The position of the eye point
+ *     (camera origin).
+ * @param {!goog.vec.vec3f.Type} fwdVec The vector describing where
+ *     the camera points to.
+ * @param {!goog.vec.vec3f.Type} worldUpVec The vector that
+ *     identifies the up direction for the camera.
+ * @return {boolean} True if the method succeeds, false otherwise.
+ *     The method can only fail if the inverse of viewMatrix is not defined.
+ */
+goog.vec.mat4f.toLookAt = function(mat, eyePt, fwdVec, worldUpVec) {
+  // Get eye of the camera.
+  var matInverse = goog.vec.mat4f.tmpmat4f_[0];
+  if (!goog.vec.mat4f.invert(mat, matInverse)) {
+    // The input matrix does not have a valid inverse.
+    return false;
+  }
+
+  if (eyePt) {
+    eyePt[0] = matInverse[12];
+    eyePt[1] = matInverse[13];
+    eyePt[2] = matInverse[14];
+  }
+
+  // Get forward vector from the definition of lookAt.
+  if (fwdVec || worldUpVec) {
+    if (!fwdVec) {
+      fwdVec = goog.vec.mat4f.tmpvec3f_[0];
+    }
+    fwdVec[0] = -mat[2];
+    fwdVec[1] = -mat[6];
+    fwdVec[2] = -mat[10];
+    // Normalize forward vector.
+    goog.vec.vec3f.normalize(fwdVec, fwdVec);
+  }
+
+  if (worldUpVec) {
+    // Get side vector from the definition of gluLookAt.
+    var side = goog.vec.mat4f.tmpvec3f_[1];
+    side[0] = mat[0];
+    side[1] = mat[4];
+    side[2] = mat[8];
+    // Compute up vector as a up = side x forward.
+    goog.vec.vec3f.cross(side, fwdVec, worldUpVec);
+    // Normalize up vector.
+    goog.vec.vec3f.normalize(worldUpVec, worldUpVec);
+  }
+  return true;
+};
+
+
+/**
+ * Makes the given 4x4 matrix a rotation matrix given Euler angles using
+ * the ZXZ convention.
+ * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
+ * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians,
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} theta1 The angle of rotation around the Z axis in radians.
+ * @param {number} theta2 The angle of rotation around the X axis in radians.
+ * @param {number} theta3 The angle of rotation around the Z axis in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.makeEulerZXZ = function(mat, theta1, theta2, theta3) {
+  var c1 = Math.cos(theta1);
+  var s1 = Math.sin(theta1);
+
+  var c2 = Math.cos(theta2);
+  var s2 = Math.sin(theta2);
+
+  var c3 = Math.cos(theta3);
+  var s3 = Math.sin(theta3);
+
+  mat[0] = c1 * c3 - c2 * s1 * s3;
+  mat[1] = c2 * c1 * s3 + c3 * s1;
+  mat[2] = s3 * s2;
+  mat[3] = 0;
+
+  mat[4] = -c1 * s3 - c3 * c2 * s1;
+  mat[5] = c1 * c2 * c3 - s1 * s3;
+  mat[6] = c3 * s2;
+  mat[7] = 0;
+
+  mat[8] = s2 * s1;
+  mat[9] = -c1 * s2;
+  mat[10] = c2;
+  mat[11] = 0;
+
+  mat[12] = 0;
+  mat[13] = 0;
+  mat[14] = 0;
+  mat[15] = 1;
+
+  return mat;
+};
+
+
+/**
+ * Decomposes a rotation matrix into Euler angles using the ZXZ convention so
+ * that rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
+ * with theta1 in [0, 2 * pi], theta2 in [0, pi] and theta3 in [0, 2 * pi].
+ * rotation_x(theta) means rotation around the X axis of theta radians.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {!goog.vec.vec3f.Type} euler The ZXZ Euler angles in
+ *     radians as [theta1, theta2, theta3].
+ * @param {boolean=} opt_theta2IsNegative Whether theta2 is in [-pi, 0] instead
+ *     of the default [0, pi].
+ * @return {!goog.vec.vec4f.Type} return euler so that operations can be
+ *     chained together.
+ */
+goog.vec.mat4f.toEulerZXZ = function(mat, euler, opt_theta2IsNegative) {
+  // There is an ambiguity in the sign of sinTheta2 because of the sqrt.
+  var sinTheta2 = Math.sqrt(mat[2] * mat[2] + mat[6] * mat[6]);
+
+  // By default we explicitely constrain theta2 to be in [0, pi],
+  // so sinTheta2 is always positive. We can change the behavior and specify
+  // theta2 to be negative in [-pi, 0] with opt_Theta2IsNegative.
+  var signTheta2 = opt_theta2IsNegative ? -1 : 1;
+
+  if (sinTheta2 > goog.vec.EPSILON) {
+    euler[2] = Math.atan2(mat[2] * signTheta2, mat[6] * signTheta2);
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
+    euler[0] = Math.atan2(mat[8] * signTheta2, -mat[9] * signTheta2);
+  } else {
+    // There is also an arbitrary choice for theta1 = 0 or theta2 = 0 here.
+    // We assume theta1 = 0 as some applications do not allow the camera to roll
+    // (i.e. have theta1 != 0).
+    euler[0] = 0;
+    euler[1] = Math.atan2(sinTheta2 * signTheta2, mat[10]);
+    euler[2] = Math.atan2(mat[1], mat[0]);
+  }
+
+  // Atan2 outputs angles in [-pi, pi] so we bring them back to [0, 2 * pi].
+  euler[0] = (euler[0] + Math.PI * 2) % (Math.PI * 2);
+  euler[2] = (euler[2] + Math.PI * 2) % (Math.PI * 2);
+  // For theta2 we want the angle to be in [0, pi] or [-pi, 0] depending on
+  // signTheta2.
+  euler[1] =
+      ((euler[1] * signTheta2 + Math.PI * 2) % (Math.PI * 2)) * signTheta2;
+
+  return euler;
+};
+
+
+/**
+ * Translates the given matrix by x,y,z.  Equvialent to:
+ * goog.vec.mat4f.multMat(
+ *     mat,
+ *     goog.vec.mat4f.makeTranslate(goog.vec.mat4f.create(), x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} x The translation along the x axis.
+ * @param {number} y The translation along the y axis.
+ * @param {number} z The translation along the z axis.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.translate = function(mat, x, y, z) {
+  mat[12] += mat[0] * x + mat[4] * y + mat[8] * z;
+  mat[13] += mat[1] * x + mat[5] * y + mat[9] * z;
+  mat[14] += mat[2] * x + mat[6] * y + mat[10] * z;
+  mat[15] += mat[3] * x + mat[7] * y + mat[11] * z;
+
+  return mat;
+};
+
+
+/**
+ * Scales the given matrix by x,y,z.  Equivalent to:
+ * goog.vec.mat4f.multMat(
+ *     mat,
+ *     goog.vec.mat4f.makeScale(goog.vec.mat4f.create(), x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} x The x scale factor.
+ * @param {number} y The y scale factor.
+ * @param {number} z The z scale factor.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.scale = function(mat, x, y, z) {
+  mat[0] = mat[0] * x;
+  mat[1] = mat[1] * x;
+  mat[2] = mat[2] * x;
+  mat[3] = mat[3] * x;
+  mat[4] = mat[4] * y;
+  mat[5] = mat[5] * y;
+  mat[6] = mat[6] * y;
+  mat[7] = mat[7] * y;
+  mat[8] = mat[8] * z;
+  mat[9] = mat[9] * z;
+  mat[10] = mat[10] * z;
+  mat[11] = mat[11] * z;
+  mat[12] = mat[12];
+  mat[13] = mat[13];
+  mat[14] = mat[14];
+  mat[15] = mat[15];
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x,y,z axis.  Equivalent to:
+ * goog.vec.mat4f.multMat(
+ *     mat,
+ *     goog.vec.mat4f.makeRotate(goog.vec.mat4f.create(), angle, x, y, z),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @param {number} x The x component of the rotation axis.
+ * @param {number} y The y component of the rotation axis.
+ * @param {number} z The z component of the rotation axis.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.rotate = function(mat, angle, x, y, z) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var cosAngle = Math.cos(angle);
+  var sinAngle = Math.sin(angle);
+  var diffCosAngle = 1 - cosAngle;
+  var r00 = x * x * diffCosAngle + cosAngle;
+  var r10 = x * y * diffCosAngle + z * sinAngle;
+  var r20 = x * z * diffCosAngle - y * sinAngle;
+
+  var r01 = x * y * diffCosAngle - z * sinAngle;
+  var r11 = y * y * diffCosAngle + cosAngle;
+  var r21 = y * z * diffCosAngle + x * sinAngle;
+
+  var r02 = x * z * diffCosAngle + y * sinAngle;
+  var r12 = y * z * diffCosAngle - x * sinAngle;
+  var r22 = z * z * diffCosAngle + cosAngle;
+
+  mat[0] = m00 * r00 + m01 * r10 + m02 * r20;
+  mat[1] = m10 * r00 + m11 * r10 + m12 * r20;
+  mat[2] = m20 * r00 + m21 * r10 + m22 * r20;
+  mat[3] = m30 * r00 + m31 * r10 + m32 * r20;
+  mat[4] = m00 * r01 + m01 * r11 + m02 * r21;
+  mat[5] = m10 * r01 + m11 * r11 + m12 * r21;
+  mat[6] = m20 * r01 + m21 * r11 + m22 * r21;
+  mat[7] = m30 * r01 + m31 * r11 + m32 * r21;
+  mat[8] = m00 * r02 + m01 * r12 + m02 * r22;
+  mat[9] = m10 * r02 + m11 * r12 + m12 * r22;
+  mat[10] = m20 * r02 + m21 * r12 + m22 * r22;
+  mat[11] = m30 * r02 + m31 * r12 + m32 * r22;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the x axis.  Equivalent to:
+ * goog.vec.mat4f.multMat(
+ *     mat,
+ *     goog.vec.mat4f.makeRotateX(goog.vec.mat4f.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.rotateX = function(mat, angle) {
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[4] = m01 * c + m02 * s;
+  mat[5] = m11 * c + m12 * s;
+  mat[6] = m21 * c + m22 * s;
+  mat[7] = m31 * c + m32 * s;
+  mat[8] = m01 * -s + m02 * c;
+  mat[9] = m11 * -s + m12 * c;
+  mat[10] = m21 * -s + m22 * c;
+  mat[11] = m31 * -s + m32 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the y axis.  Equivalent to:
+ * goog.vec.mat4f.multMat(
+ *     mat,
+ *     goog.vec.mat4f.makeRotateY(goog.vec.mat4f.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.rotateY = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m02 * -s;
+  mat[1] = m10 * c + m12 * -s;
+  mat[2] = m20 * c + m22 * -s;
+  mat[3] = m30 * c + m32 * -s;
+  mat[8] = m00 * s + m02 * c;
+  mat[9] = m10 * s + m12 * c;
+  mat[10] = m20 * s + m22 * c;
+  mat[11] = m30 * s + m32 * c;
+
+  return mat;
+};
+
+
+/**
+ * Rotate the given matrix by angle about the z axis.  Equivalent to:
+ * goog.vec.mat4f.multMat(
+ *     mat,
+ *     goog.vec.mat4f.makeRotateZ(goog.vec.mat4f.create(), angle),
+ *     mat);
+ *
+ * @param {!goog.vec.mat4f.Type} mat The matrix.
+ * @param {number} angle The angle in radians.
+ * @return {!goog.vec.mat4f.Type} return mat so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.rotateZ = function(mat, angle) {
+  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
+  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
+
+  var c = Math.cos(angle);
+  var s = Math.sin(angle);
+
+  mat[0] = m00 * c + m01 * s;
+  mat[1] = m10 * c + m11 * s;
+  mat[2] = m20 * c + m21 * s;
+  mat[3] = m30 * c + m31 * s;
+  mat[4] = m00 * -s + m01 * c;
+  mat[5] = m10 * -s + m11 * c;
+  mat[6] = m20 * -s + m21 * c;
+  mat[7] = m30 * -s + m31 * c;
+
+  return mat;
+};
+
+
+/**
+ * Retrieves the translation component of the transformation matrix.
+ *
+ * @param {!goog.vec.mat4f.Type} mat The transformation matrix.
+ * @param {!goog.vec.vec3f.Type} translation The vector for storing the
+ *     result.
+ * @return {!goog.vec.vec3f.Type} return translation so that operations can be
+ *     chained.
+ */
+goog.vec.mat4f.getTranslation = function(mat, translation) {
+  translation[0] = mat[12];
+  translation[1] = mat[13];
+  translation[2] = mat[14];
+  return translation;
+};
+
+
+/**
+ * @type {Array<goog.vec.vec3f.Type>}
+ * @private
+ */
+goog.vec.mat4f.tmpvec3f_ = [goog.vec.vec3f.create(), goog.vec.vec3f.create()];
+
+
+/**
+ * @type {Array<goog.vec.vec4f.Type>}
+ * @private
+ */
+goog.vec.mat4f.tmpvec4f_ =
+    [goog.vec.vec4f.create(), goog.vec.vec4f.create(), goog.vec.vec4f.create()];
+
+
+/**
+ * @type {Array<goog.vec.mat4f.Type>}
+ * @private
+ */
+goog.vec.mat4f.tmpmat4f_ = [goog.vec.mat4f.create()];

+ 11 - 0
public/lib/closure/vec/mat4f_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.mat4fTest');</script>
+<title>Closure Unit Tests - goog.vec.mat4fTest</title></head><body></body></html>

+ 788 - 0
public/lib/closure/vec/mat4f_test.js

@@ -0,0 +1,788 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to mat4d_test.js by running:       //
+//   swap_type.sh mat4f_test.js > mat4d_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.mat4fTest');
+goog.setTestOnly('goog.vec.mat4fTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Quaternion');
+goog.require('goog.vec.mat4f');
+goog.require('goog.vec.vec3f');
+goog.require('goog.vec.vec4f');
+
+var randommat4f = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+    0.8025078773498535,
+    0.7559120655059814,
+    0.15274643898010254,
+    0.19196106493473053,
+    0.0890120416879654,
+    0.15422114729881287,
+    0.09754583984613419,
+    0.44862601161003113,
+    0.9196512699127197,
+    0.5310639142990112,
+    0.8962187170982361,
+    0.280601441860199,
+    0.594650387763977,
+    0.4134795069694519,
+    0.06632178276777267,
+    0.8837796449661255);
+
+function testCreate() {
+  var m = goog.vec.mat4f.create();
+  assertElementsEquals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m);
+}
+
+function testCreateIdentity() {
+  var m = goog.vec.mat4f.createIdentity();
+  assertElementsEquals([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m);
+}
+
+function testSet() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  goog.vec.mat4f.setFromArray(m0, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  goog.vec.mat4f.setFromValues(
+      m0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17);
+  assertElementsEquals(
+      [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], m0);
+}
+
+function testSetDiagonal() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setDiagonalValues(m0, 1, 2, 3, 4);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4], m0);
+
+  goog.vec.mat4f.setDiagonal(m0, [4, 5, 6, 7]);
+  assertElementsEquals(
+      [4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 6, 0, 0, 0, 0, 7], m0);
+}
+
+function testGetDiagonal() {
+  var v0 = goog.vec.vec4f.create();
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setFromArray(
+      m0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
+
+  goog.vec.mat4f.getDiagonal(m0, v0);
+  assertElementsEquals([0, 5, 10, 15], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, 1);
+  assertElementsEquals([4, 9, 14, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, 2);
+  assertElementsEquals([8, 13, 0, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, 3);
+  assertElementsEquals([12, 0, 0, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, 4);
+  assertElementsEquals([0, 0, 0, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, -1);
+  assertElementsEquals([1, 6, 11, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, -2);
+  assertElementsEquals([2, 7, 0, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, -3);
+  assertElementsEquals([3, 0, 0, 0], v0);
+
+  goog.vec.vec4f.setFromArray(v0, [0, 0, 0, 0]);
+  goog.vec.mat4f.getDiagonal(m0, v0, -4);
+  assertElementsEquals([0, 0, 0, 0], v0);
+}
+
+function testSetGetColumn() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setColumn(m0, 0, [1, 2, 3, 4]);
+  goog.vec.mat4f.setColumn(m0, 1, [5, 6, 7, 8]);
+  goog.vec.mat4f.setColumn(m0, 2, [9, 10, 11, 12]);
+  goog.vec.mat4f.setColumn(m0, 3, [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  var v0 = [0, 0, 0, 0];
+  goog.vec.mat4f.getColumn(m0, 0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  goog.vec.mat4f.getColumn(m0, 1, v0);
+  assertElementsEquals([5, 6, 7, 8], v0);
+  goog.vec.mat4f.getColumn(m0, 2, v0);
+  assertElementsEquals([9, 10, 11, 12], v0);
+  goog.vec.mat4f.getColumn(m0, 3, v0);
+  assertElementsEquals([13, 14, 15, 16], v0);
+}
+
+function testSetGetColumns() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setColumns(
+      m0, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+
+  var v0 = [0, 0, 0, 0], v1 = [0, 0, 0, 0];
+  var v2 = [0, 0, 0, 0], v3 = [0, 0, 0, 0];
+  goog.vec.mat4f.getColumns(m0, v0, v1, v2, v3);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([9, 10, 11, 12], v2);
+  assertElementsEquals([13, 14, 15, 16], v3);
+}
+
+function testSetGetRow() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setRow(m0, 0, [1, 2, 3, 4]);
+  goog.vec.mat4f.setRow(m0, 1, [5, 6, 7, 8]);
+  goog.vec.mat4f.setRow(m0, 2, [9, 10, 11, 12]);
+  goog.vec.mat4f.setRow(m0, 3, [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+
+  var v0 = [0, 0, 0, 0];
+  goog.vec.mat4f.getRow(m0, 0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  goog.vec.mat4f.getRow(m0, 1, v0);
+  assertElementsEquals([5, 6, 7, 8], v0);
+  goog.vec.mat4f.getRow(m0, 2, v0);
+  assertElementsEquals([9, 10, 11, 12], v0);
+  goog.vec.mat4f.getRow(m0, 3, v0);
+  assertElementsEquals([13, 14, 15, 16], v0);
+}
+
+function testSetGetRows() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setRows(
+      m0, [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m0);
+
+  var v0 = [0, 0, 0, 0], v1 = [0, 0, 0, 0];
+  var v2 = [0, 0, 0, 0], v3 = [0, 0, 0, 0];
+  goog.vec.mat4f.getRows(m0, v0, v1, v2, v3);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([9, 10, 11, 12], v2);
+  assertElementsEquals([13, 14, 15, 16], v3);
+}
+
+function testMakeZero() {
+  var m0 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  goog.vec.mat4f.makeZero(m0);
+  assertElementsEquals(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], m0);
+}
+
+function testMakeIdentity() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeIdentity(m0);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], m0);
+}
+
+function testSetGetElement() {
+  var m0 = goog.vec.mat4f.create();
+  for (var r = 0; r < 4; r++) {
+    for (var c = 0; c < 4; c++) {
+      var value = c * 4 + r + 1;
+      goog.vec.mat4f.setElement(m0, r, c, value);
+      assertEquals(value, goog.vec.mat4f.getElement(m0, r, c));
+    }
+  }
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+}
+
+function testAddMat() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8);
+  var m2 = goog.vec.mat4f.create();
+  goog.vec.mat4f.addMat(m0, m1, m2);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [10, 12, 14, 16, 18, 20, 22, 24, 10, 12, 14, 16, 18, 20, 22, 24], m2);
+
+  goog.vec.mat4f.addMat(m0, m1, m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [10, 12, 14, 16, 18, 20, 22, 24, 10, 12, 14, 16, 18, 20, 22, 24], m0);
+}
+
+function testSubMat() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8);
+  var m2 = goog.vec.mat4f.create();
+
+  goog.vec.mat4f.subMat(m0, m1, m2);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8], m1);
+  assertElementsEquals(
+      [-8, -8, -8, -8, -8, -8, -8, -8, 8, 8, 8, 8, 8, 8, 8, 8], m2);
+
+  goog.vec.mat4f.subMat(m1, m0, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [8, 8, 8, 8, 8, 8, 8, 8, -8, -8, -8, -8, -8, -8, -8, -8], m1);
+}
+
+function testMultScalar() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4f.create();
+
+  goog.vec.mat4f.multScalar(m0, 2, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m0);
+  assertElementsEquals(
+      [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32], m1);
+
+  goog.vec.mat4f.multScalar(m0, 5, m0);
+  assertElementsEquals(
+      [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80], m0);
+}
+
+function testMultMat() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m2 = goog.vec.mat4f.create();
+
+  goog.vec.mat4f.multMat(m0, m1, m2);
+  assertElementsEquals(
+      [90, 100, 110, 120, 202, 228, 254, 280,
+       314, 356, 398, 440, 426, 484, 542, 600], m2);
+
+  goog.vec.mat4f.multScalar(m1, 2, m1);
+  goog.vec.mat4f.multMat(m1, m0, m1);
+  assertElementsEquals(
+      [180, 200, 220, 240, 404, 456, 508, 560,
+       628, 712, 796, 880, 852, 968, 1084, 1200], m1);
+}
+
+function testTranspose() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4f.create();
+  goog.vec.mat4f.transpose(m0, m1);
+  assertElementsEquals(
+      [1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15, 4, 8, 12, 16], m1);
+
+  goog.vec.mat4f.transpose(m1, m1);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], m1);
+}
+
+function testDeterminant() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertEquals(0, goog.vec.mat4f.determinant(m0));
+  assertElementsEquals(
+      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.mat4f.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+  assertEquals(160, goog.vec.mat4f.determinant(m0));
+  assertElementsEquals(
+      [1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3], m0);
+}
+
+function testInvert() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+  assertFalse(goog.vec.mat4f.invert(m0, m0));
+  assertElementsEquals(
+      [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], m0);
+
+  goog.vec.mat4f.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+  assertTrue(goog.vec.mat4f.invert(m0, m0));
+  assertElementsRoughlyEqual(
+      [-0.225, 0.025, 0.025, 0.275, 0.025, 0.025, 0.275, -0.225,
+       0.025, 0.275, -0.225, 0.025, 0.275, -0.225, 0.025, 0.025], m0,
+       goog.vec.EPSILON);
+
+  goog.vec.mat4f.makeScale(m0, .01, .01, .01);
+  assertTrue(goog.vec.mat4f.invert(m0, m0));
+  var m1 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeScale(m1, 100, 100, 100);
+  assertElementsEquals(m1, m0);
+}
+
+function testEquals() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var m1 = goog.vec.mat4f.setFromMat4f(goog.vec.mat4f.create(), m0);
+  assertTrue(goog.vec.mat4f.equals(m0, m1));
+  assertTrue(goog.vec.mat4f.equals(m1, m0));
+  for (var i = 0; i < 16; i++) {
+    m1[i] = 18;
+    assertFalse(goog.vec.mat4f.equals(m0, m1));
+    assertFalse(goog.vec.mat4f.equals(m1, m0));
+    m1[i] = i + 1;
+  }
+}
+
+function testMultVec3() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.mat4f.multVec3(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([51, 58, 65], v1);
+
+  goog.vec.mat4f.multVec3(m0, v0, v0);
+  assertElementsEquals([51, 58, 65], v0);
+}
+
+function testMultVec3NoTranslate() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+
+  goog.vec.mat4f.multVec3NoTranslate(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([38, 44, 50], v1);
+
+  goog.vec.mat4f.multVec3NoTranslate(m0, v0, v0);
+  assertElementsEquals([38, 44, 50], v0);
+}
+
+function testMultVec3Projective() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3];
+  var v1 = [0, 0, 0];
+  var invw = 1 / 72;
+
+  goog.vec.mat4f.multVec3Projective(m0, v0, v1);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals(
+      [51 * invw, 58 * invw, 65 * invw], v1);
+
+  goog.vec.mat4f.multVec3Projective(m0, v0, v0);
+  assertElementsEquals(
+      [51 * invw, 58 * invw, 65 * invw], v0);
+}
+
+function testMultVec4() {
+  var m0 = goog.vec.mat4f.setFromValues(goog.vec.mat4f.create(),
+      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
+  var v0 = [1, 2, 3, 4];
+  var v1 = [0, 0, 0, 0];
+
+  goog.vec.mat4f.multVec4(m0, v0, v1);
+  assertElementsEquals([90, 100, 110, 120], v1);
+  goog.vec.mat4f.multVec4(m0, v0, v0);
+  assertElementsEquals([90, 100, 110, 120], v0);
+}
+
+function testSetValues() {
+  var a0 = goog.vec.mat4f.create();
+  assertElementsEquals(
+      [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], a0);
+  a0 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(),
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], a0);
+
+  var a1 = goog.vec.mat4f.create();
+  goog.vec.mat4f.setDiagonalValues(a1, 1, 2, 3, 4);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 4], a1);
+
+  goog.vec.mat4f.setColumnValues(a1, 0, 2, 3, 4, 5);
+  goog.vec.mat4f.setColumnValues(a1, 1, 6, 7, 8, 9);
+  goog.vec.mat4f.setColumnValues(a1, 2, 10, 11, 12, 13);
+  goog.vec.mat4f.setColumnValues(a1, 3, 14, 15, 16, 1);
+  assertElementsEquals(
+      [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1], a1);
+
+  goog.vec.mat4f.setRowValues(a1, 0, 1, 5, 9, 13);
+  goog.vec.mat4f.setRowValues(a1, 1, 2, 6, 10, 14);
+  goog.vec.mat4f.setRowValues(a1, 2, 3, 7, 11, 15);
+  goog.vec.mat4f.setRowValues(a1, 3, 4, 8, 12, 16);
+  assertElementsEquals(
+      [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], a1);
+}
+
+function testMakeTranslate() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeTranslate(m0, 3, 4, 5);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 4, 5, 1], m0);
+}
+
+function testMakeScale() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeScale(m0, 3, 4, 5);
+  assertElementsEquals(
+      [3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1], m0);
+}
+
+function testMakeRotate() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeRotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeRotate(m1, -Math.PI / 4, 0, 0, 1);
+  goog.vec.mat4f.multMat(m0, m1, m1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0, 0,
+       -0.7071068, 0.7071068, 0, 0,
+       0, 0, 1, 0,
+       0, 0, 0, 1],
+      m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateX() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.create();
+
+  goog.vec.mat4f.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat4f.makeRotate(m1, Math.PI / 7, 1, 0, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateY() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.create();
+
+  goog.vec.mat4f.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat4f.makeRotate(m1, Math.PI / 7, 0, 1, 0);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotateZ() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.create();
+
+  goog.vec.mat4f.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat4f.makeRotate(m1, Math.PI / 7, 0, 0, 1);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testTranslate() {
+  var m0 = goog.vec.mat4f.makeIdentity(goog.vec.mat4f.create());
+  goog.vec.mat4f.translate(m0, 3, 4, 5);
+  assertElementsEquals(
+      [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 3, 4, 5, 1], m0);
+
+  goog.vec.mat4f.setFromValues(
+      m0, 1, 2, 3, 4, 2, 3, 4, 1, 3, 4, 1, 2, 4, 1, 2, 3);
+
+  var m1 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeTranslate(m1, 5, 6, 7);
+  var m2 = goog.vec.mat4f.create();
+  goog.vec.mat4f.multMat(m0, m1, m2);
+  goog.vec.mat4f.translate(m0, 5, 6, 7);
+  assertElementsEquals(m2, m0);
+}
+
+function testScale() {
+  var m0 = goog.vec.mat4f.makeIdentity(goog.vec.mat4f.create());
+  goog.vec.mat4f.scale(m0, 3, 4, 5);
+  assertElementsEquals([3, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1], m0);
+}
+
+function testRotate() {
+  var m0 = goog.vec.mat4f.makeIdentity(goog.vec.mat4f.create());
+  goog.vec.mat4f.rotate(m0, Math.PI / 2, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+
+  goog.vec.mat4f.rotate(m0, -Math.PI / 4, 0, 0, 1);
+  assertElementsRoughlyEqual(
+      [0.7071068, 0.7071068, 0, 0,
+       -0.7071068, 0.7071068, 0, 0,
+       0, 0, 1, 0,
+       0, 0, 0, 1],
+      m0, goog.vec.EPSILON);
+}
+
+function testRotateX() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(), randommat4f);
+
+  goog.vec.mat4f.makeRotateX(m0, Math.PI / 7);
+  goog.vec.mat4f.multMat(m1, m0, m0);
+  goog.vec.mat4f.rotateX(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(), randommat4f);
+
+  goog.vec.mat4f.makeRotateY(m0, Math.PI / 7);
+  goog.vec.mat4f.multMat(m1, m0, m0);
+  goog.vec.mat4f.rotateY(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var m0 = goog.vec.mat4f.create();
+  var m1 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(), randommat4f);
+
+  goog.vec.mat4f.makeRotateZ(m0, Math.PI / 7);
+  goog.vec.mat4f.multMat(m1, m0, m0);
+  goog.vec.mat4f.rotateZ(m1, Math.PI / 7);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotationTranslation() {
+  // Create manually.
+  var m0 = goog.vec.mat4f.makeIdentity(goog.vec.mat4f.create());
+  goog.vec.mat4f.translate(m0, 3, 4, 5);
+  goog.vec.mat4f.rotate(m0, Math.PI / 2, 3 / 13, 4 / 13, 12 / 13);
+
+  // Create using makeRotationTranslation.
+  var m1 = goog.vec.mat4f.create();
+  var q = goog.vec.Quaternion.createFloat32();
+  var axis = goog.vec.vec3f.createFromValues(3 / 13, 4 / 13, 12 / 13);
+  goog.vec.Quaternion.fromAngleAxis(Math.PI / 2, axis, q);
+  var v = goog.vec.vec3f.createFromValues(3, 4, 5);
+  goog.vec.mat4f.makeRotationTranslation(m1, q, v);
+
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotationTranslationScale() {
+  // Create manually.
+  var m0 = goog.vec.mat4f.makeIdentity(goog.vec.mat4f.create());
+  goog.vec.mat4f.translate(m0, 3, 4, 5);
+  goog.vec.mat4f.rotate(m0, Math.PI / 2, 3 / 13, 4 / 13, 12 / 13);
+  goog.vec.mat4f.scale(m0, 6, 7, 8);
+
+  // Create using makeRotationTranslationScale.
+  var m1 = goog.vec.mat4f.create();
+  var q = goog.vec.Quaternion.createFloat32();
+  var axis = goog.vec.vec3f.createFromValues(3 / 13, 4 / 13, 12 / 13);
+  goog.vec.Quaternion.fromAngleAxis(Math.PI / 2, axis, q);
+  var v = goog.vec.vec3f.createFromValues(3, 4, 5);
+  var s = goog.vec.vec3f.createFromValues(6, 7, 8);
+  goog.vec.mat4f.makeRotationTranslationScale(m1, q, v, s);
+
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testMakeRotationTranslationScaleOrigin() {
+  // Create manually.
+  var m0 = goog.vec.mat4f.makeIdentity(goog.vec.mat4f.create());
+  goog.vec.mat4f.translate(m0, 3, 4, 5);
+  goog.vec.mat4f.translate(m0, 9, 10, -11); // Origin.
+  goog.vec.mat4f.rotate(m0, Math.PI / 2, 3 / 13, 4 / 13, 12 / 13);
+  goog.vec.mat4f.scale(m0, 6, 7, 8);
+  goog.vec.mat4f.translate(m0, -9, -10, 11); // -Origin.
+
+  // Create using makeRotationTranslationScaleOrigin.
+  var m1 = goog.vec.mat4f.create();
+  var q = goog.vec.Quaternion.createFloat32();
+  var axis = goog.vec.vec3f.createFromValues(3 / 13, 4 / 13, 12 / 13);
+  goog.vec.Quaternion.fromAngleAxis(Math.PI / 2, axis, q);
+  var v = goog.vec.vec3f.createFromValues(3, 4, 5);
+  var s = goog.vec.vec3f.createFromValues(6, 7, 8);
+  var o = goog.vec.vec3f.createFromValues(9, 10, -11);
+  goog.vec.mat4f.makeRotationTranslationScaleOrigin(m1, q, v, s, o);
+
+  assertElementsRoughlyEqual(m0, m1, 0.00001); // Slightly larger epsilon.
+}
+
+function testGetTranslation() {
+  var mat = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(), randommat4f);
+  var translation = goog.vec.vec3f.create();
+  goog.vec.mat4f.getTranslation(mat, translation);
+  assertElementsRoughlyEqual(
+      [0.59465038776, 0.413479506969, 0.0663217827677],
+      translation, goog.vec.EPSILON);
+}
+
+function testMakeFrustum() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeFrustum(m0, -1, 2, -2, 1, .1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.06666666, 0, 0, 0,
+       0, 0.06666666, 0, 0,
+       0.33333333, -0.33333333, -1.2, -1,
+       0, 0, -0.22, 0], m0, goog.vec.EPSILON);
+}
+
+function testMakePerspective() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makePerspective(m0, 90 * Math.PI / 180, 2, 0.1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.5, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1.2, -1, 0, 0, -0.22, 0],
+      m0, goog.vec.EPSILON);
+}
+
+function testMakeOrtho() {
+  var m0 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeOrtho(m0, -1, 2, -2, 1, 0.1, 1.1);
+  assertElementsRoughlyEqual(
+      [0.6666666, 0, 0, 0,
+       0, 0.6666666, 0, 0,
+       0, 0, -2, 0,
+       -0.333333, 0.3333333, -1.2, 1], m0, goog.vec.EPSILON);
+
+}
+
+function testMakeEulerZXZ() {
+  var m0 = goog.vec.mat4f.create();
+  var roll = 0.200982 * 2 * Math.PI;
+  var tilt = 0.915833 * Math.PI;
+  var yaw = 0.839392 * 2 * Math.PI;
+
+  goog.vec.mat4f.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat4f.rotate(m0, tilt, 1, 0, 0);
+  goog.vec.mat4f.rotate(m0, yaw, 0, 0, 1);
+
+  var m1 = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeEulerZXZ(m1, roll, tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+
+  var euler = [0, 0, 0];
+  goog.vec.mat4f.toEulerZXZ(m0, euler);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+
+  // Test negative tilt now.
+  goog.vec.mat4f.makeRotate(m0, roll, 0, 0, 1);
+  goog.vec.mat4f.rotate(m0, -tilt, 1, 0, 0);
+  goog.vec.mat4f.rotate(m0, yaw, 0, 0, 1);
+
+  goog.vec.mat4f.makeEulerZXZ(m1, roll, -tilt, yaw);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat4f.toEulerZXZ(m0, euler, true);
+
+  assertRoughlyEquals(roll, euler[0], goog.vec.EPSILON);
+  assertRoughlyEquals(-tilt, euler[1], goog.vec.EPSILON);
+  assertRoughlyEquals(yaw, euler[2], goog.vec.EPSILON);
+}
+
+function testEulerZXZExtrema() {
+  var m0 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(),
+  [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1]);
+  var m1 = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(),
+  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
+  var euler = [0, 0, 0];
+  goog.vec.mat4f.toEulerZXZ(m0, euler);
+  assertElementsRoughlyEqual(
+      [Math.PI, Math.PI / 2, Math.PI], euler, goog.vec.EPSILON);
+  goog.vec.mat4f.makeEulerZXZ(m1, euler[0], euler[1], euler[2]);
+  assertElementsRoughlyEqual(m0, m1, goog.vec.EPSILON);
+}
+
+function testLookAt() {
+  var viewMatrix = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeLookAt(
+    viewMatrix, [0, 0, 0], [1, 0, 0], [0, 1, 0]);
+  assertElementsRoughlyEqual(
+    [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1], viewMatrix,
+    goog.vec.EPSILON);
+}
+
+function testToLookAt() {
+  // This test does not use the default precision goog.vec.EPSILON due to
+  // precision issues in some browsers leading to flaky tests.
+  var EPSILON = 1e-4;
+
+  var eyeExp = [0, 0, 0];
+  var fwdExp = [1, 0, 0];
+  var upExp = [0, 1, 0];
+
+  var centerExp = [0, 0, 0];
+  goog.vec.vec3f.add(eyeExp, fwdExp, centerExp);
+
+  var view = goog.vec.mat4f.create();
+  goog.vec.mat4f.makeLookAt(view, eyeExp, centerExp, upExp);
+
+  var eyeRes = [0, 0, 0];
+  var fwdRes = [0, 0, 0];
+  var upRes = [0, 0, 0];
+  goog.vec.mat4f.toLookAt(view, eyeRes, fwdRes, upRes);
+  assertElementsRoughlyEqual(eyeExp, eyeRes, EPSILON);
+  assertElementsRoughlyEqual(fwdExp, fwdRes, EPSILON);
+  assertElementsRoughlyEqual(upExp, upRes, EPSILON);
+}
+
+function testLookAtDecomposition() {
+  // This test does not use the default precision goog.vec.EPSILON due to
+  // precision issues in some browsers leading to flaky tests.
+  var EPSILON = 1e-4;
+
+  var viewExp = goog.vec.mat4f.create();
+  var viewRes = goog.vec.mat4f.create();
+
+  // Get a valid set of random vectors eye, forward, up by decomposing
+  // a random matrix into a set of lookAt vectors.
+  var tmp = goog.vec.mat4f.setFromArray(goog.vec.mat4f.create(), randommat4f);
+  var eyeExp = [0, 0, 0];
+  var fwdExp = [0, 0, 0];
+  var upExp = [0, 0, 0];
+  var centerExp = [0, 0, 0];
+  // Project the random matrix into a real modelview matrix.
+  goog.vec.mat4f.toLookAt(tmp, eyeExp, fwdExp, upExp);
+  goog.vec.vec3f.add(eyeExp, fwdExp, centerExp);
+
+  // Compute the expected modelview matrix from a set of valid random vectors.
+  goog.vec.mat4f.makeLookAt(viewExp, eyeExp, centerExp, upExp);
+
+  var eyeRes = [0, 0, 0];
+  var fwdRes = [0, 0, 0];
+  var upRes = [0, 0, 0];
+  var centerRes = [0, 0, 0];
+  goog.vec.mat4f.toLookAt(viewExp, eyeRes, fwdRes, upRes);
+  goog.vec.vec3f.add(eyeRes, fwdRes, centerRes);
+
+  goog.vec.mat4f.makeLookAt(viewRes, eyeRes, centerRes, upRes);
+
+  assertElementsRoughlyEqual(eyeExp, eyeRes, EPSILON);
+  assertElementsRoughlyEqual(fwdExp, fwdRes, EPSILON);
+  assertElementsRoughlyEqual(upExp, upRes, EPSILON);
+  assertElementsRoughlyEqual(viewExp, viewRes, EPSILON);
+}

+ 0 - 722
public/lib/closure/vec/matrix3.js

@@ -1,722 +0,0 @@
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview WARNING: DEPRECATED.  Use Mat3 instead.
- * Implements 3x3 matrices and their related functions which are
- * compatible with WebGL. The API is structured to avoid unnecessary memory
- * allocations.  The last parameter will typically be the output vector and
- * an object can be both an input and output parameter to all methods except
- * where noted. Matrix operations follow the mathematical form when multiplying
- * vectors as follows: resultVec = matrix * vec.
- *
- */
-goog.provide('goog.vec.Matrix3');
-
-goog.require('goog.vec');
-
-
-/**
- * @typedef {goog.vec.ArrayType}
- */
-goog.vec.Matrix3.Type;
-
-
-/**
- * Creates the array representation of a 3x3 matrix. The use of the array
- * directly eliminates any overhead associated with the class representation
- * defined above. The returned matrix is cleared to all zeros.
- *
- * @return {goog.vec.Matrix3.Type} The new, nine element array.
- */
-goog.vec.Matrix3.create = function() {
-  return new Float32Array(9);
-};
-
-
-/**
- * Creates the array representation of a 3x3 matrix. The use of the array
- * directly eliminates any overhead associated with the class representation
- * defined above. The returned matrix is initialized with the identity.
- *
- * @return {goog.vec.Matrix3.Type} The new, nine element array.
- */
-goog.vec.Matrix3.createIdentity = function() {
-  var mat = goog.vec.Matrix3.create();
-  mat[0] = mat[4] = mat[8] = 1;
-  return mat;
-};
-
-
-/**
- * Creates a 3x3 matrix initialized from the given array.
- *
- * @param {goog.vec.ArrayType} matrix The array containing the
- *     matrix values in column major order.
- * @return {goog.vec.Matrix3.Type} The new, nine element array.
- */
-goog.vec.Matrix3.createFromArray = function(matrix) {
-  var newMatrix = goog.vec.Matrix3.create();
-  goog.vec.Matrix3.setFromArray(newMatrix, matrix);
-  return newMatrix;
-};
-
-
-/**
- * Creates a 3x3 matrix initialized from the given values.
- *
- * @param {number} v00 The values at (0, 0).
- * @param {number} v10 The values at (1, 0).
- * @param {number} v20 The values at (2, 0).
- * @param {number} v01 The values at (0, 1).
- * @param {number} v11 The values at (1, 1).
- * @param {number} v21 The values at (2, 1).
- * @param {number} v02 The values at (0, 2).
- * @param {number} v12 The values at (1, 2).
- * @param {number} v22 The values at (2, 2).
- * @return {goog.vec.Matrix3.Type} The new, nine element array.
- */
-goog.vec.Matrix3.createFromValues = function(
-    v00, v10, v20, v01, v11, v21, v02, v12, v22) {
-  var newMatrix = goog.vec.Matrix3.create();
-  goog.vec.Matrix3.setFromValues(
-      newMatrix, v00, v10, v20, v01, v11, v21, v02, v12, v22);
-  return newMatrix;
-};
-
-
-/**
- * Creates a clone of a 3x3 matrix.
- *
- * @param {goog.vec.Matrix3.Type} matrix The source 3x3 matrix.
- * @return {goog.vec.Matrix3.Type} The new 3x3 element matrix.
- */
-goog.vec.Matrix3.clone =
-    goog.vec.Matrix3.createFromArray;
-
-
-/**
- * Retrieves the element at the requested row and column.
- *
- * @param {goog.vec.ArrayType} mat The matrix containing the
- *     value to retrieve.
- * @param {number} row The row index.
- * @param {number} column The column index.
- * @return {number} The element value at the requested row, column indices.
- */
-goog.vec.Matrix3.getElement = function(mat, row, column) {
-  return mat[row + column * 3];
-};
-
-
-/**
- * Sets the element at the requested row and column.
- *
- * @param {goog.vec.ArrayType} mat The matrix containing the
- *     value to retrieve.
- * @param {number} row The row index.
- * @param {number} column The column index.
- * @param {number} value The value to set at the requested row, column.
- */
-goog.vec.Matrix3.setElement = function(mat, row, column, value) {
-  mat[row + column * 3] = value;
-};
-
-
-/**
- * Initializes the matrix from the set of values. Note the values supplied are
- * in column major order.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} v00 The values at (0, 0).
- * @param {number} v10 The values at (1, 0).
- * @param {number} v20 The values at (2, 0).
- * @param {number} v01 The values at (0, 1).
- * @param {number} v11 The values at (1, 1).
- * @param {number} v21 The values at (2, 1).
- * @param {number} v02 The values at (0, 2).
- * @param {number} v12 The values at (1, 2).
- * @param {number} v22 The values at (2, 2).
- */
-goog.vec.Matrix3.setFromValues = function(
-    mat, v00, v10, v20, v01, v11, v21, v02, v12, v22) {
-  mat[0] = v00;
-  mat[1] = v10;
-  mat[2] = v20;
-  mat[3] = v01;
-  mat[4] = v11;
-  mat[5] = v21;
-  mat[6] = v02;
-  mat[7] = v12;
-  mat[8] = v22;
-};
-
-
-/**
- * Sets the matrix from the array of values stored in column major order.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} values The column major ordered
- *     array of values to store in the matrix.
- */
-goog.vec.Matrix3.setFromArray = function(mat, values) {
-  mat[0] = values[0];
-  mat[1] = values[1];
-  mat[2] = values[2];
-  mat[3] = values[3];
-  mat[4] = values[4];
-  mat[5] = values[5];
-  mat[6] = values[6];
-  mat[7] = values[7];
-  mat[8] = values[8];
-};
-
-
-/**
- * Sets the matrix from the array of values stored in row major order.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} values The row major ordered array
- *     of values to store in the matrix.
- */
-goog.vec.Matrix3.setFromRowMajorArray = function(mat, values) {
-  mat[0] = values[0];
-  mat[1] = values[3];
-  mat[2] = values[6];
-  mat[3] = values[1];
-  mat[4] = values[4];
-  mat[5] = values[7];
-  mat[6] = values[2];
-  mat[7] = values[5];
-  mat[8] = values[8];
-};
-
-
-/**
- * Sets the diagonal values of the matrix from the given values.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} v00 The values for (0, 0).
- * @param {number} v11 The values for (1, 1).
- * @param {number} v22 The values for (2, 2).
- */
-goog.vec.Matrix3.setDiagonalValues = function(mat, v00, v11, v22) {
-  mat[0] = v00;
-  mat[4] = v11;
-  mat[8] = v22;
-};
-
-
-/**
- * Sets the diagonal values of the matrix from the given vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} vec The vector containing the
- *     values.
- */
-goog.vec.Matrix3.setDiagonal = function(mat, vec) {
-  mat[0] = vec[0];
-  mat[4] = vec[1];
-  mat[8] = vec[2];
-};
-
-
-/**
- * Sets the specified column with the supplied values.
- *
- * @param {goog.vec.ArrayType} mat The matrix to recieve the
- *     values.
- * @param {number} column The column index to set the values on.
- * @param {number} v0 The value for row 0.
- * @param {number} v1 The value for row 1.
- * @param {number} v2 The value for row 2.
- */
-goog.vec.Matrix3.setColumnValues = function(
-    mat, column, v0, v1, v2) {
-  var i = column * 3;
-  mat[i] = v0;
-  mat[i + 1] = v1;
-  mat[i + 2] = v2;
-};
-
-
-/**
- * Sets the specified column with the value from the supplied array.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} column The column index to set the values on.
- * @param {goog.vec.ArrayType} vec The vector elements for the
- *     column.
- */
-goog.vec.Matrix3.setColumn = function(mat, column, vec) {
-  var i = column * 3;
-  mat[i] = vec[0];
-  mat[i + 1] = vec[1];
-  mat[i + 2] = vec[2];
-};
-
-
-/**
- * Retrieves the specified column from the matrix into the given vector
- * array.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     values.
- * @param {number} column The column to get the values from.
- * @param {goog.vec.ArrayType} vec The vector elements to receive
- *     the column.
- */
-goog.vec.Matrix3.getColumn = function(mat, column, vec) {
-  var i = column * 3;
-  vec[0] = mat[i];
-  vec[1] = mat[i + 1];
-  vec[2] = mat[i + 2];
-};
-
-
-/**
- * Sets the columns of the matrix from the set of vector elements.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} vec0 The values for column 0.
- * @param {goog.vec.ArrayType} vec1 The values for column 1.
- * @param {goog.vec.ArrayType} vec2 The values for column 2.
- */
-goog.vec.Matrix3.setColumns = function(
-    mat, vec0, vec1, vec2) {
-  goog.vec.Matrix3.setColumn(mat, 0, vec0);
-  goog.vec.Matrix3.setColumn(mat, 1, vec1);
-  goog.vec.Matrix3.setColumn(mat, 2, vec2);
-};
-
-
-/**
- * Retrieves the column values from the given matrix into the given vector
- * elements.
- *
- * @param {goog.vec.ArrayType} mat The matrix containing the
- *     columns to retrieve.
- * @param {goog.vec.ArrayType} vec0 The vector elements to receive
- *     column 0.
- * @param {goog.vec.ArrayType} vec1 The vector elements to receive
- *     column 1.
- * @param {goog.vec.ArrayType} vec2 The vector elements to receive
- *     column 2.
- */
-goog.vec.Matrix3.getColumns = function(
-    mat, vec0, vec1, vec2) {
-  goog.vec.Matrix3.getColumn(mat, 0, vec0);
-  goog.vec.Matrix3.getColumn(mat, 1, vec1);
-  goog.vec.Matrix3.getColumn(mat, 2, vec2);
-};
-
-
-/**
- * Sets the row values from the supplied values.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} row The index of the row to receive the values.
- * @param {number} v0 The value for column 0.
- * @param {number} v1 The value for column 1.
- * @param {number} v2 The value for column 2.
- */
-goog.vec.Matrix3.setRowValues = function(mat, row, v0, v1, v2) {
-  mat[row] = v0;
-  mat[row + 3] = v1;
-  mat[row + 6] = v2;
-};
-
-
-/**
- * Sets the row values from the supplied vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     row values.
- * @param {number} row The index of the row.
- * @param {goog.vec.ArrayType} vec The vector containing the values.
- */
-goog.vec.Matrix3.setRow = function(mat, row, vec) {
-  mat[row] = vec[0];
-  mat[row + 3] = vec[1];
-  mat[row + 6] = vec[2];
-};
-
-
-/**
- * Retrieves the row values into the given vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     values.
- * @param {number} row The index of the row supplying the values.
- * @param {goog.vec.ArrayType} vec The vector to receive the row.
- */
-goog.vec.Matrix3.getRow = function(mat, row, vec) {
-  vec[0] = mat[row];
-  vec[1] = mat[row + 3];
-  vec[2] = mat[row + 6];
-};
-
-
-/**
- * Sets the rows of the matrix from the supplied vectors.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} vec0 The values for row 0.
- * @param {goog.vec.ArrayType} vec1 The values for row 1.
- * @param {goog.vec.ArrayType} vec2 The values for row 2.
- */
-goog.vec.Matrix3.setRows = function(
-    mat, vec0, vec1, vec2) {
-  goog.vec.Matrix3.setRow(mat, 0, vec0);
-  goog.vec.Matrix3.setRow(mat, 1, vec1);
-  goog.vec.Matrix3.setRow(mat, 2, vec2);
-};
-
-
-/**
- * Retrieves the rows of the matrix into the supplied vectors.
- *
- * @param {goog.vec.ArrayType} mat The matrix to supplying
- *     the values.
- * @param {goog.vec.ArrayType} vec0 The vector to receive row 0.
- * @param {goog.vec.ArrayType} vec1 The vector to receive row 1.
- * @param {goog.vec.ArrayType} vec2 The vector to receive row 2.
- */
-goog.vec.Matrix3.getRows = function(
-    mat, vec0, vec1, vec2) {
-  goog.vec.Matrix3.getRow(mat, 0, vec0);
-  goog.vec.Matrix3.getRow(mat, 1, vec1);
-  goog.vec.Matrix3.getRow(mat, 2, vec2);
-};
-
-
-/**
- * Clears the given matrix to zero.
- *
- * @param {goog.vec.ArrayType} mat The matrix to clear.
- */
-goog.vec.Matrix3.setZero = function(mat) {
-  mat[0] = 0;
-  mat[1] = 0;
-  mat[2] = 0;
-  mat[3] = 0;
-  mat[4] = 0;
-  mat[5] = 0;
-  mat[6] = 0;
-  mat[7] = 0;
-  mat[8] = 0;
-};
-
-
-/**
- * Sets the given matrix to the identity matrix.
- *
- * @param {goog.vec.ArrayType} mat The matrix to set.
- */
-goog.vec.Matrix3.setIdentity = function(mat) {
-  mat[0] = 1;
-  mat[1] = 0;
-  mat[2] = 0;
-  mat[3] = 0;
-  mat[4] = 1;
-  mat[5] = 0;
-  mat[6] = 0;
-  mat[7] = 0;
-  mat[8] = 1;
-};
-
-
-/**
- * Performs a per-component addition of the matrices mat0 and mat1, storing
- * the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The first addend.
- * @param {goog.vec.ArrayType} mat1 The second addend.
- * @param {goog.vec.ArrayType} resultMat The matrix to
- *     receive the results (may be either mat0 or mat1).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix3.add = function(mat0, mat1, resultMat) {
-  resultMat[0] = mat0[0] + mat1[0];
-  resultMat[1] = mat0[1] + mat1[1];
-  resultMat[2] = mat0[2] + mat1[2];
-  resultMat[3] = mat0[3] + mat1[3];
-  resultMat[4] = mat0[4] + mat1[4];
-  resultMat[5] = mat0[5] + mat1[5];
-  resultMat[6] = mat0[6] + mat1[6];
-  resultMat[7] = mat0[7] + mat1[7];
-  resultMat[8] = mat0[8] + mat1[8];
-  return resultMat;
-};
-
-
-/**
- * Performs a per-component subtraction of the matrices mat0 and mat1,
- * storing the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The minuend.
- * @param {goog.vec.ArrayType} mat1 The subtrahend.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be either mat0 or mat1).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix3.subtract = function(mat0, mat1, resultMat) {
-  resultMat[0] = mat0[0] - mat1[0];
-  resultMat[1] = mat0[1] - mat1[1];
-  resultMat[2] = mat0[2] - mat1[2];
-  resultMat[3] = mat0[3] - mat1[3];
-  resultMat[4] = mat0[4] - mat1[4];
-  resultMat[5] = mat0[5] - mat1[5];
-  resultMat[6] = mat0[6] - mat1[6];
-  resultMat[7] = mat0[7] - mat1[7];
-  resultMat[8] = mat0[8] - mat1[8];
-  return resultMat;
-};
-
-
-/**
- * Performs a component-wise multiplication of mat0 with the given scalar
- * storing the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The matrix to scale.
- * @param {number} scalar The scalar value to multiple to each element of mat0.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be mat0).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix3.scale = function(mat0, scalar, resultMat) {
-  resultMat[0] = mat0[0] * scalar;
-  resultMat[1] = mat0[1] * scalar;
-  resultMat[2] = mat0[2] * scalar;
-  resultMat[3] = mat0[3] * scalar;
-  resultMat[4] = mat0[4] * scalar;
-  resultMat[5] = mat0[5] * scalar;
-  resultMat[6] = mat0[6] * scalar;
-  resultMat[7] = mat0[7] * scalar;
-  resultMat[8] = mat0[8] * scalar;
-  return resultMat;
-};
-
-
-/**
- * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
- * storing the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The first (left hand) matrix.
- * @param {goog.vec.ArrayType} mat1 The second (right hand)
- *     matrix.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be either mat0 or mat1).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix3.multMat = function(mat0, mat1, resultMat) {
-  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2];
-  var a01 = mat0[3], a11 = mat0[4], a21 = mat0[5];
-  var a02 = mat0[6], a12 = mat0[7], a22 = mat0[8];
-
-  var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2];
-  var b01 = mat1[3], b11 = mat1[4], b21 = mat1[5];
-  var b02 = mat1[6], b12 = mat1[7], b22 = mat1[8];
-
-  resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20;
-  resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20;
-  resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20;
-  resultMat[3] = a00 * b01 + a01 * b11 + a02 * b21;
-  resultMat[4] = a10 * b01 + a11 * b11 + a12 * b21;
-  resultMat[5] = a20 * b01 + a21 * b11 + a22 * b21;
-  resultMat[6] = a00 * b02 + a01 * b12 + a02 * b22;
-  resultMat[7] = a10 * b02 + a11 * b12 + a12 * b22;
-  resultMat[8] = a20 * b02 + a21 * b12 + a22 * b22;
-  return resultMat;
-};
-
-
-/**
- * Transposes the given matrix mat storing the result into resultMat.
- * @param {goog.vec.ArrayType} mat The matrix to transpose.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be mat).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix3.transpose = function(mat, resultMat) {
-  if (resultMat == mat) {
-    var a10 = mat[1], a20 = mat[2], a21 = mat[5];
-    resultMat[1] = mat[3];
-    resultMat[2] = mat[6];
-    resultMat[3] = a10;
-    resultMat[5] = mat[7];
-    resultMat[6] = a20;
-    resultMat[7] = a21;
-  } else {
-    resultMat[0] = mat[0];
-    resultMat[1] = mat[3];
-    resultMat[2] = mat[6];
-    resultMat[3] = mat[1];
-    resultMat[4] = mat[4];
-    resultMat[5] = mat[7];
-    resultMat[6] = mat[2];
-    resultMat[7] = mat[5];
-    resultMat[8] = mat[8];
-  }
-  return resultMat;
-};
-
-
-/**
- * Computes the inverse of mat0 storing the result into resultMat. If the
- * inverse is defined, this function returns true, false otherwise.
- * @param {goog.vec.ArrayType} mat0 The matrix to invert.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the result (may be mat0).
- * @return {boolean} True if the inverse is defined. If false is returned,
- *     resultMat is not modified.
- */
-goog.vec.Matrix3.invert = function(mat0, resultMat) {
-  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2];
-  var a01 = mat0[3], a11 = mat0[4], a21 = mat0[5];
-  var a02 = mat0[6], a12 = mat0[7], a22 = mat0[8];
-
-  var t00 = a11 * a22 - a12 * a21;
-  var t10 = a12 * a20 - a10 * a22;
-  var t20 = a10 * a21 - a11 * a20;
-  var det = a00 * t00 + a01 * t10 + a02 * t20;
-  if (det == 0) {
-    return false;
-  }
-
-  var idet = 1 / det;
-  resultMat[0] = t00 * idet;
-  resultMat[3] = (a02 * a21 - a01 * a22) * idet;
-  resultMat[6] = (a01 * a12 - a02 * a11) * idet;
-
-  resultMat[1] = t10 * idet;
-  resultMat[4] = (a00 * a22 - a02 * a20) * idet;
-  resultMat[7] = (a02 * a10 - a00 * a12) * idet;
-
-  resultMat[2] = t20 * idet;
-  resultMat[5] = (a01 * a20 - a00 * a21) * idet;
-  resultMat[8] = (a00 * a11 - a01 * a10) * idet;
-  return true;
-};
-
-
-/**
- * Returns true if the components of mat0 are equal to the components of mat1.
- *
- * @param {goog.vec.ArrayType} mat0 The first matrix.
- * @param {goog.vec.ArrayType} mat1 The second matrix.
- * @return {boolean} True if the the two matrices are equivalent.
- */
-goog.vec.Matrix3.equals = function(mat0, mat1) {
-  return mat0.length == mat1.length &&
-      mat0[0] == mat1[0] && mat0[1] == mat1[1] && mat0[2] == mat1[2] &&
-      mat0[3] == mat1[3] && mat0[4] == mat1[4] && mat0[5] == mat1[5] &&
-      mat0[6] == mat1[6] && mat0[7] == mat1[7] && mat0[8] == mat1[8];
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed matrix into resultVec.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The vector to transform.
- * @param {goog.vec.ArrayType} resultVec The vector to
- *     receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix3.multVec3 = function(mat, vec, resultVec) {
-  var x = vec[0], y = vec[1], z = vec[2];
-  resultVec[0] = x * mat[0] + y * mat[3] + z * mat[6];
-  resultVec[1] = x * mat[1] + y * mat[4] + z * mat[7];
-  resultVec[2] = x * mat[2] + y * mat[5] + z * mat[8];
-  return resultVec;
-};
-
-
-/**
- * Initializes the given 3x3 matrix as a translation matrix with x and y
- * translation values.
- *
- * @param {goog.vec.ArrayType} mat The 3x3 (9-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} x The translation along the x axis.
- * @param {number} y The translation along the y axis.
- */
-goog.vec.Matrix3.makeTranslate = function(mat, x, y) {
-  goog.vec.Matrix3.setIdentity(mat);
-  goog.vec.Matrix3.setColumnValues(mat, 2, x, y, 1);
-};
-
-
-/**
- * Initializes the given 3x3 matrix as a scale matrix with x, y and z scale
- * factors.
- * @param {goog.vec.ArrayType} mat The 3x3 (9-element) matrix
- *     array to receive the new scale matrix.
- * @param {number} x The scale along the x axis.
- * @param {number} y The scale along the y axis.
- * @param {number} z The scale along the z axis.
- */
-goog.vec.Matrix3.makeScale = function(mat, x, y, z) {
-  goog.vec.Matrix3.setIdentity(mat);
-  goog.vec.Matrix3.setDiagonalValues(mat, x, y, z);
-};
-
-
-/**
- * Initializes the given 3x3 matrix as a rotation matrix with the given rotation
- * angle about the axis defined by the vector (ax, ay, az).
- * @param {goog.vec.ArrayType} mat The 3x3 (9-element) matrix
- *     array to receive the new scale matrix.
- * @param {number} angle The rotation angle in radians.
- * @param {number} ax The x component of the rotation axis.
- * @param {number} ay The y component of the rotation axis.
- * @param {number} az The z component of the rotation axis.
- */
-goog.vec.Matrix3.makeAxisAngleRotate = function(
-    mat, angle, ax, ay, az) {
-  var c = Math.cos(angle);
-  var d = 1 - c;
-  var s = Math.sin(angle);
-
-  goog.vec.Matrix3.setFromValues(mat,
-      ax * ax * d + c,
-      ax * ay * d + az * s,
-      ax * az * d - ay * s,
-
-      ax * ay * d - az * s,
-      ay * ay * d + c,
-      ay * az * d + ax * s,
-
-      ax * az * d + ay * s,
-      ay * az * d - ax * s,
-      az * az * d + c);
-};

+ 0 - 1405
public/lib/closure/vec/matrix4.js

@@ -1,1405 +0,0 @@
-// Copyright 2011 The Closure Library Authors. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS-IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * @fileoverview WARNING: DEPRECATED.  Use Mat4 instead.
- * Implements 4x4 matrices and their related functions which are
- * compatible with WebGL. The API is structured to avoid unnecessary memory
- * allocations.  The last parameter will typically be the output vector and
- * an object can be both an input and output parameter to all methods except
- * where noted. Matrix operations follow the mathematical form when multiplying
- * vectors as follows: resultVec = matrix * vec.
- *
- */
-goog.provide('goog.vec.Matrix4');
-
-goog.require('goog.vec');
-goog.require('goog.vec.Vec3');
-goog.require('goog.vec.Vec4');
-
-
-/**
- * @typedef {goog.vec.ArrayType}
- */
-goog.vec.Matrix4.Type;
-
-
-/**
- * Creates the array representation of a 4x4 matrix. The use of the array
- * directly eliminates any overhead associated with the class representation
- * defined above. The returned matrix is cleared to all zeros.
- *
- * @return {goog.vec.Matrix4.Type} The new, sixteen element array.
- */
-goog.vec.Matrix4.create = function() {
-  return new Float32Array(16);
-};
-
-
-/**
- * Creates the array representation of a 4x4 matrix. The use of the array
- * directly eliminates any overhead associated with the class representation
- * defined above. The returned matrix is initialized with the identity
- *
- * @return {goog.vec.Matrix4.Type} The new, sixteen element array.
- */
-goog.vec.Matrix4.createIdentity = function() {
-  var mat = goog.vec.Matrix4.create();
-  mat[0] = mat[5] = mat[10] = mat[15] = 1;
-  return mat;
-};
-
-
-/**
- * Creates a 4x4 matrix initialized from the given array.
- *
- * @param {goog.vec.ArrayType} matrix The array containing the
- *     matrix values in column major order.
- * @return {goog.vec.Matrix4.Type} The new, 16 element array.
- */
-goog.vec.Matrix4.createFromArray = function(matrix) {
-  var newMatrix = goog.vec.Matrix4.create();
-  goog.vec.Matrix4.setFromArray(newMatrix, matrix);
-  return newMatrix;
-};
-
-
-/**
- * Creates a 4x4 matrix initialized from the given values.
- *
- * @param {number} v00 The values at (0, 0).
- * @param {number} v10 The values at (1, 0).
- * @param {number} v20 The values at (2, 0).
- * @param {number} v30 The values at (3, 0).
- * @param {number} v01 The values at (0, 1).
- * @param {number} v11 The values at (1, 1).
- * @param {number} v21 The values at (2, 1).
- * @param {number} v31 The values at (3, 1).
- * @param {number} v02 The values at (0, 2).
- * @param {number} v12 The values at (1, 2).
- * @param {number} v22 The values at (2, 2).
- * @param {number} v32 The values at (3, 2).
- * @param {number} v03 The values at (0, 3).
- * @param {number} v13 The values at (1, 3).
- * @param {number} v23 The values at (2, 3).
- * @param {number} v33 The values at (3, 3).
- * @return {goog.vec.Matrix4.Type} The new, 16 element array.
- */
-goog.vec.Matrix4.createFromValues = function(
-    v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
-    v03, v13, v23, v33) {
-  var newMatrix = goog.vec.Matrix4.create();
-  goog.vec.Matrix4.setFromValues(
-      newMatrix, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
-      v03, v13, v23, v33);
-  return newMatrix;
-};
-
-
-/**
- * Creates a clone of a 4x4 matrix.
- *
- * @param {goog.vec.Matrix4.Type} matrix The source 4x4 matrix.
- * @return {goog.vec.Matrix4.Type} The new, 16 element matrix.
- */
-goog.vec.Matrix4.clone =
-    goog.vec.Matrix4.createFromArray;
-
-
-/**
- * Retrieves the element at the requested row and column.
- *
- * @param {goog.vec.ArrayType} mat The matrix containing the
- *     value to retrieve.
- * @param {number} row The row index.
- * @param {number} column The column index.
- * @return {number} The element value at the requested row, column indices.
- */
-goog.vec.Matrix4.getElement = function(mat, row, column) {
-  return mat[row + column * 4];
-};
-
-
-/**
- * Sets the element at the requested row and column.
- *
- * @param {goog.vec.ArrayType} mat The matrix containing the
- *     value to retrieve.
- * @param {number} row The row index.
- * @param {number} column The column index.
- * @param {number} value The value to set at the requested row, column.
- */
-goog.vec.Matrix4.setElement = function(mat, row, column, value) {
-  mat[row + column * 4] = value;
-};
-
-
-/**
- * Initializes the matrix from the set of values. Note the values supplied are
- * in column major order.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} v00 The values at (0, 0).
- * @param {number} v10 The values at (1, 0).
- * @param {number} v20 The values at (2, 0).
- * @param {number} v30 The values at (3, 0).
- * @param {number} v01 The values at (0, 1).
- * @param {number} v11 The values at (1, 1).
- * @param {number} v21 The values at (2, 1).
- * @param {number} v31 The values at (3, 1).
- * @param {number} v02 The values at (0, 2).
- * @param {number} v12 The values at (1, 2).
- * @param {number} v22 The values at (2, 2).
- * @param {number} v32 The values at (3, 2).
- * @param {number} v03 The values at (0, 3).
- * @param {number} v13 The values at (1, 3).
- * @param {number} v23 The values at (2, 3).
- * @param {number} v33 The values at (3, 3).
- */
-goog.vec.Matrix4.setFromValues = function(
-    mat, v00, v10, v20, v30, v01, v11, v21, v31, v02, v12, v22, v32,
-    v03, v13, v23, v33) {
-  mat[0] = v00;
-  mat[1] = v10;
-  mat[2] = v20;
-  mat[3] = v30;
-  mat[4] = v01;
-  mat[5] = v11;
-  mat[6] = v21;
-  mat[7] = v31;
-  mat[8] = v02;
-  mat[9] = v12;
-  mat[10] = v22;
-  mat[11] = v32;
-  mat[12] = v03;
-  mat[13] = v13;
-  mat[14] = v23;
-  mat[15] = v33;
-};
-
-
-/**
- * Sets the matrix from the array of values stored in column major order.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} values The column major ordered
- *     array of values to store in the matrix.
- */
-goog.vec.Matrix4.setFromArray = function(mat, values) {
-  mat[0] = values[0];
-  mat[1] = values[1];
-  mat[2] = values[2];
-  mat[3] = values[3];
-  mat[4] = values[4];
-  mat[5] = values[5];
-  mat[6] = values[6];
-  mat[7] = values[7];
-  mat[8] = values[8];
-  mat[9] = values[9];
-  mat[10] = values[10];
-  mat[11] = values[11];
-  mat[12] = values[12];
-  mat[13] = values[13];
-  mat[14] = values[14];
-  mat[15] = values[15];
-};
-
-
-/**
- * Sets the matrix from the array of values stored in row major order.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} values The row major ordered array of
- *     values to store in the matrix.
- */
-goog.vec.Matrix4.setFromRowMajorArray = function(mat, values) {
-  mat[0] = values[0];
-  mat[1] = values[4];
-  mat[2] = values[8];
-  mat[3] = values[12];
-
-  mat[4] = values[1];
-  mat[5] = values[5];
-  mat[6] = values[9];
-  mat[7] = values[13];
-
-  mat[8] = values[2];
-  mat[9] = values[6];
-  mat[10] = values[10];
-  mat[11] = values[14];
-
-  mat[12] = values[3];
-  mat[13] = values[7];
-  mat[14] = values[11];
-  mat[15] = values[15];
-};
-
-
-/**
- * Sets the diagonal values of the matrix from the given values.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} v00 The values for (0, 0).
- * @param {number} v11 The values for (1, 1).
- * @param {number} v22 The values for (2, 2).
- * @param {number} v33 The values for (3, 3).
- */
-goog.vec.Matrix4.setDiagonalValues = function(
-    mat, v00, v11, v22, v33) {
-  mat[0] = v00;
-  mat[5] = v11;
-  mat[10] = v22;
-  mat[15] = v33;
-};
-
-
-/**
- * Sets the diagonal values of the matrix from the given vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} vec The vector containing the
- *     values.
- */
-goog.vec.Matrix4.setDiagonal = function(mat, vec) {
-  mat[0] = vec[0];
-  mat[5] = vec[1];
-  mat[10] = vec[2];
-  mat[15] = vec[3];
-};
-
-
-/**
- * Sets the specified column with the supplied values.
- *
- * @param {goog.vec.ArrayType} mat The matrix to recieve the
- *     values.
- * @param {number} column The column index to set the values on.
- * @param {number} v0 The value for row 0.
- * @param {number} v1 The value for row 1.
- * @param {number} v2 The value for row 2.
- * @param {number} v3 The value for row 3.
- */
-goog.vec.Matrix4.setColumnValues = function(
-    mat, column, v0, v1, v2, v3) {
-  var i = column * 4;
-  mat[i] = v0;
-  mat[i + 1] = v1;
-  mat[i + 2] = v2;
-  mat[i + 3] = v3;
-};
-
-
-/**
- * Sets the specified column with the value from the supplied array.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} column The column index to set the values on.
- * @param {goog.vec.ArrayType} vec The vector of elements for the
- *     column.
- */
-goog.vec.Matrix4.setColumn = function(mat, column, vec) {
-  var i = column * 4;
-  mat[i] = vec[0];
-  mat[i + 1] = vec[1];
-  mat[i + 2] = vec[2];
-  mat[i + 3] = vec[3];
-};
-
-
-/**
- * Retrieves the specified column from the matrix into the given vector
- * array.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     values.
- * @param {number} column The column to get the values from.
- * @param {goog.vec.ArrayType} vec The vector of elements to
- *     receive the column.
- */
-goog.vec.Matrix4.getColumn = function(mat, column, vec) {
-  var i = column * 4;
-  vec[0] = mat[i];
-  vec[1] = mat[i + 1];
-  vec[2] = mat[i + 2];
-  vec[3] = mat[i + 3];
-};
-
-
-/**
- * Sets the columns of the matrix from the set of vector elements.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} vec0 The values for column 0.
- * @param {goog.vec.ArrayType} vec1 The values for column 1.
- * @param {goog.vec.ArrayType} vec2 The values for column 2.
- * @param {goog.vec.ArrayType} vec3 The values for column 3.
- */
-goog.vec.Matrix4.setColumns = function(
-    mat, vec0, vec1, vec2, vec3) {
-  goog.vec.Matrix4.setColumn(mat, 0, vec0);
-  goog.vec.Matrix4.setColumn(mat, 1, vec1);
-  goog.vec.Matrix4.setColumn(mat, 2, vec2);
-  goog.vec.Matrix4.setColumn(mat, 3, vec3);
-};
-
-
-/**
- * Retrieves the column values from the given matrix into the given vector
- * elements.
- *
- * @param {goog.vec.ArrayType} mat The matrix containing the
- *     columns to retrieve.
- * @param {goog.vec.ArrayType} vec0 The vector elements to receive
- *     column 0.
- * @param {goog.vec.ArrayType} vec1 The vector elements to receive
- *     column 1.
- * @param {goog.vec.ArrayType} vec2 The vector elements to receive
- *     column 2.
- * @param {goog.vec.ArrayType} vec3 The vector elements to receive
- *     column 3.
- */
-goog.vec.Matrix4.getColumns = function(
-    mat, vec0, vec1, vec2, vec3) {
-  goog.vec.Matrix4.getColumn(mat, 0, vec0);
-  goog.vec.Matrix4.getColumn(mat, 1, vec1);
-  goog.vec.Matrix4.getColumn(mat, 2, vec2);
-  goog.vec.Matrix4.getColumn(mat, 3, vec3);
-};
-
-
-/**
- * Sets the row values from the supplied values.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {number} row The index of the row to receive the values.
- * @param {number} v0 The value for column 0.
- * @param {number} v1 The value for column 1.
- * @param {number} v2 The value for column 2.
- * @param {number} v3 The value for column 3.
- */
-goog.vec.Matrix4.setRowValues = function(mat, row, v0, v1, v2, v3) {
-  mat[row] = v0;
-  mat[row + 4] = v1;
-  mat[row + 8] = v2;
-  mat[row + 12] = v3;
-};
-
-
-/**
- * Sets the row values from the supplied vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     row values.
- * @param {number} row The index of the row.
- * @param {goog.vec.ArrayType} vec The vector containing the
- *     values.
- */
-goog.vec.Matrix4.setRow = function(mat, row, vec) {
-  mat[row] = vec[0];
-  mat[row + 4] = vec[1];
-  mat[row + 8] = vec[2];
-  mat[row + 12] = vec[3];
-};
-
-
-/**
- * Retrieves the row values into the given vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     values.
- * @param {number} row The index of the row supplying the values.
- * @param {goog.vec.ArrayType} vec The vector to receive the
- *     row.
- */
-goog.vec.Matrix4.getRow = function(mat, row, vec) {
-  vec[0] = mat[row];
-  vec[1] = mat[row + 4];
-  vec[2] = mat[row + 8];
-  vec[3] = mat[row + 12];
-};
-
-
-/**
- * Sets the rows of the matrix from the supplied vectors.
- *
- * @param {goog.vec.ArrayType} mat The matrix to receive the
- *     values.
- * @param {goog.vec.ArrayType} vec0 The values for row 0.
- * @param {goog.vec.ArrayType} vec1 The values for row 1.
- * @param {goog.vec.ArrayType} vec2 The values for row 2.
- * @param {goog.vec.ArrayType} vec3 The values for row 3.
- */
-goog.vec.Matrix4.setRows = function(
-    mat, vec0, vec1, vec2, vec3) {
-  goog.vec.Matrix4.setRow(mat, 0, vec0);
-  goog.vec.Matrix4.setRow(mat, 1, vec1);
-  goog.vec.Matrix4.setRow(mat, 2, vec2);
-  goog.vec.Matrix4.setRow(mat, 3, vec3);
-};
-
-
-/**
- * Retrieves the rows of the matrix into the supplied vectors.
- *
- * @param {goog.vec.ArrayType} mat The matrix to supply the
- *     values.
- * @param {goog.vec.ArrayType} vec0 The vector to receive row 0.
- * @param {goog.vec.ArrayType} vec1 The vector to receive row 1.
- * @param {goog.vec.ArrayType} vec2 The vector to receive row 2.
- * @param {goog.vec.ArrayType} vec3 The vector to receive row 3.
- */
-goog.vec.Matrix4.getRows = function(
-    mat, vec0, vec1, vec2, vec3) {
-  goog.vec.Matrix4.getRow(mat, 0, vec0);
-  goog.vec.Matrix4.getRow(mat, 1, vec1);
-  goog.vec.Matrix4.getRow(mat, 2, vec2);
-  goog.vec.Matrix4.getRow(mat, 3, vec3);
-};
-
-
-/**
- * Clears the given matrix to zero.
- *
- * @param {goog.vec.ArrayType} mat The matrix to clear.
- */
-goog.vec.Matrix4.setZero = function(mat) {
-  mat[0] = 0;
-  mat[1] = 0;
-  mat[2] = 0;
-  mat[3] = 0;
-  mat[4] = 0;
-  mat[5] = 0;
-  mat[6] = 0;
-  mat[7] = 0;
-  mat[8] = 0;
-  mat[9] = 0;
-  mat[10] = 0;
-  mat[11] = 0;
-  mat[12] = 0;
-  mat[13] = 0;
-  mat[14] = 0;
-  mat[15] = 0;
-};
-
-
-/**
- * Sets the given matrix to the identity matrix.
- *
- * @param {goog.vec.ArrayType} mat The matrix to set.
- */
-goog.vec.Matrix4.setIdentity = function(mat) {
-  mat[0] = 1;
-  mat[1] = 0;
-  mat[2] = 0;
-  mat[3] = 0;
-  mat[4] = 0;
-  mat[5] = 1;
-  mat[6] = 0;
-  mat[7] = 0;
-  mat[8] = 0;
-  mat[9] = 0;
-  mat[10] = 1;
-  mat[11] = 0;
-  mat[12] = 0;
-  mat[13] = 0;
-  mat[14] = 0;
-  mat[15] = 1;
-};
-
-
-/**
- * Performs a per-component addition of the matrix mat0 and mat1, storing
- * the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The first addend.
- * @param {goog.vec.ArrayType} mat1 The second addend.
- * @param {goog.vec.ArrayType} resultMat The matrix to
- *     receive the results (may be either mat0 or mat1).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.add = function(mat0, mat1, resultMat) {
-  resultMat[0] = mat0[0] + mat1[0];
-  resultMat[1] = mat0[1] + mat1[1];
-  resultMat[2] = mat0[2] + mat1[2];
-  resultMat[3] = mat0[3] + mat1[3];
-  resultMat[4] = mat0[4] + mat1[4];
-  resultMat[5] = mat0[5] + mat1[5];
-  resultMat[6] = mat0[6] + mat1[6];
-  resultMat[7] = mat0[7] + mat1[7];
-  resultMat[8] = mat0[8] + mat1[8];
-  resultMat[9] = mat0[9] + mat1[9];
-  resultMat[10] = mat0[10] + mat1[10];
-  resultMat[11] = mat0[11] + mat1[11];
-  resultMat[12] = mat0[12] + mat1[12];
-  resultMat[13] = mat0[13] + mat1[13];
-  resultMat[14] = mat0[14] + mat1[14];
-  resultMat[15] = mat0[15] + mat1[15];
-  return resultMat;
-};
-
-
-/**
- * Performs a per-component subtraction of the matrix mat0 and mat1,
- * storing the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The minuend.
- * @param {goog.vec.ArrayType} mat1 The subtrahend.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be either mat0 or mat1).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.subtract = function(mat0, mat1, resultMat) {
-  resultMat[0] = mat0[0] - mat1[0];
-  resultMat[1] = mat0[1] - mat1[1];
-  resultMat[2] = mat0[2] - mat1[2];
-  resultMat[3] = mat0[3] - mat1[3];
-  resultMat[4] = mat0[4] - mat1[4];
-  resultMat[5] = mat0[5] - mat1[5];
-  resultMat[6] = mat0[6] - mat1[6];
-  resultMat[7] = mat0[7] - mat1[7];
-  resultMat[8] = mat0[8] - mat1[8];
-  resultMat[9] = mat0[9] - mat1[9];
-  resultMat[10] = mat0[10] - mat1[10];
-  resultMat[11] = mat0[11] - mat1[11];
-  resultMat[12] = mat0[12] - mat1[12];
-  resultMat[13] = mat0[13] - mat1[13];
-  resultMat[14] = mat0[14] - mat1[14];
-  resultMat[15] = mat0[15] - mat1[15];
-  return resultMat;
-};
-
-
-/**
- * Performs a component-wise multiplication of mat0 with the given scalar
- * storing the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The matrix to scale.
- * @param {number} scalar The scalar value to multiple to each element of mat0.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be mat0).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.scale = function(mat0, scalar, resultMat) {
-  resultMat[0] = mat0[0] * scalar;
-  resultMat[1] = mat0[1] * scalar;
-  resultMat[2] = mat0[2] * scalar;
-  resultMat[3] = mat0[3] * scalar;
-  resultMat[4] = mat0[4] * scalar;
-  resultMat[5] = mat0[5] * scalar;
-  resultMat[6] = mat0[6] * scalar;
-  resultMat[7] = mat0[7] * scalar;
-  resultMat[8] = mat0[8] * scalar;
-  resultMat[9] = mat0[9] * scalar;
-  resultMat[10] = mat0[10] * scalar;
-  resultMat[11] = mat0[11] * scalar;
-  resultMat[12] = mat0[12] * scalar;
-  resultMat[13] = mat0[13] * scalar;
-  resultMat[14] = mat0[14] * scalar;
-  resultMat[15] = mat0[15] * scalar;
-  return resultMat;
-};
-
-
-/**
- * Multiplies the two matrices mat0 and mat1 using matrix multiplication,
- * storing the result into resultMat.
- *
- * @param {goog.vec.ArrayType} mat0 The first (left hand) matrix.
- * @param {goog.vec.ArrayType} mat1 The second (right hand)
- *     matrix.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be either mat0 or mat1).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multMat = function(mat0, mat1, resultMat) {
-  var a00 = mat0[0], a10 = mat0[1], a20 = mat0[2], a30 = mat0[3];
-  var a01 = mat0[4], a11 = mat0[5], a21 = mat0[6], a31 = mat0[7];
-  var a02 = mat0[8], a12 = mat0[9], a22 = mat0[10], a32 = mat0[11];
-  var a03 = mat0[12], a13 = mat0[13], a23 = mat0[14], a33 = mat0[15];
-
-  var b00 = mat1[0], b10 = mat1[1], b20 = mat1[2], b30 = mat1[3];
-  var b01 = mat1[4], b11 = mat1[5], b21 = mat1[6], b31 = mat1[7];
-  var b02 = mat1[8], b12 = mat1[9], b22 = mat1[10], b32 = mat1[11];
-  var b03 = mat1[12], b13 = mat1[13], b23 = mat1[14], b33 = mat1[15];
-
-  resultMat[0] = a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30;
-  resultMat[1] = a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30;
-  resultMat[2] = a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30;
-  resultMat[3] = a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30;
-
-  resultMat[4] = a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31;
-  resultMat[5] = a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31;
-  resultMat[6] = a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31;
-  resultMat[7] = a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31;
-
-  resultMat[8] = a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32;
-  resultMat[9] = a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32;
-  resultMat[10] = a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32;
-  resultMat[11] = a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32;
-
-  resultMat[12] = a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33;
-  resultMat[13] = a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33;
-  resultMat[14] = a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33;
-  resultMat[15] = a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33;
-  return resultMat;
-};
-
-
-/**
- * Transposes the given matrix mat storing the result into resultMat.
- * @param {goog.vec.ArrayType} mat The matrix to transpose.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the results (may be mat).
- * @return {goog.vec.ArrayType} return resultMat so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.transpose = function(mat, resultMat) {
-  if (resultMat == mat) {
-    var a10 = mat[1], a20 = mat[2], a30 = mat[3];
-    var a21 = mat[6], a31 = mat[7];
-    var a32 = mat[11];
-    resultMat[1] = mat[4];
-    resultMat[2] = mat[8];
-    resultMat[3] = mat[12];
-    resultMat[4] = a10;
-    resultMat[6] = mat[9];
-    resultMat[7] = mat[13];
-    resultMat[8] = a20;
-    resultMat[9] = a21;
-    resultMat[11] = mat[14];
-    resultMat[12] = a30;
-    resultMat[13] = a31;
-    resultMat[14] = a32;
-  } else {
-    resultMat[0] = mat[0];
-    resultMat[1] = mat[4];
-    resultMat[2] = mat[8];
-    resultMat[3] = mat[12];
-
-    resultMat[4] = mat[1];
-    resultMat[5] = mat[5];
-    resultMat[6] = mat[9];
-    resultMat[7] = mat[13];
-
-    resultMat[8] = mat[2];
-    resultMat[9] = mat[6];
-    resultMat[10] = mat[10];
-    resultMat[11] = mat[14];
-
-    resultMat[12] = mat[3];
-    resultMat[13] = mat[7];
-    resultMat[14] = mat[11];
-    resultMat[15] = mat[15];
-  }
-  return resultMat;
-};
-
-
-/**
- * Computes the determinant of the matrix.
- *
- * @param {goog.vec.ArrayType} mat The matrix to compute the
- *     matrix for.
- * @return {number} The determinant of the matrix.
- */
-goog.vec.Matrix4.determinant = function(mat) {
-  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
-  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
-  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
-  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
-
-  var a0 = m00 * m11 - m10 * m01;
-  var a1 = m00 * m21 - m20 * m01;
-  var a2 = m00 * m31 - m30 * m01;
-  var a3 = m10 * m21 - m20 * m11;
-  var a4 = m10 * m31 - m30 * m11;
-  var a5 = m20 * m31 - m30 * m21;
-  var b0 = m02 * m13 - m12 * m03;
-  var b1 = m02 * m23 - m22 * m03;
-  var b2 = m02 * m33 - m32 * m03;
-  var b3 = m12 * m23 - m22 * m13;
-  var b4 = m12 * m33 - m32 * m13;
-  var b5 = m22 * m33 - m32 * m23;
-
-  return a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
-};
-
-
-/**
- * Computes the inverse of mat storing the result into resultMat. If the
- * inverse is defined, this function returns true, false otherwise.
- *
- * @param {goog.vec.ArrayType} mat The matrix to invert.
- * @param {goog.vec.ArrayType} resultMat The matrix to receive
- *     the result (may be mat).
- * @return {boolean} True if the inverse is defined. If false is returned,
- *     resultMat is not modified.
- */
-goog.vec.Matrix4.invert = function(mat, resultMat) {
-  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
-  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
-  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
-  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
-
-  var a0 = m00 * m11 - m10 * m01;
-  var a1 = m00 * m21 - m20 * m01;
-  var a2 = m00 * m31 - m30 * m01;
-  var a3 = m10 * m21 - m20 * m11;
-  var a4 = m10 * m31 - m30 * m11;
-  var a5 = m20 * m31 - m30 * m21;
-  var b0 = m02 * m13 - m12 * m03;
-  var b1 = m02 * m23 - m22 * m03;
-  var b2 = m02 * m33 - m32 * m03;
-  var b3 = m12 * m23 - m22 * m13;
-  var b4 = m12 * m33 - m32 * m13;
-  var b5 = m22 * m33 - m32 * m23;
-
-  var det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
-  if (det == 0) {
-    return false;
-  }
-
-  var idet = 1.0 / det;
-  resultMat[0] = (m11 * b5 - m21 * b4 + m31 * b3) * idet;
-  resultMat[1] = (-m10 * b5 + m20 * b4 - m30 * b3) * idet;
-  resultMat[2] = (m13 * a5 - m23 * a4 + m33 * a3) * idet;
-  resultMat[3] = (-m12 * a5 + m22 * a4 - m32 * a3) * idet;
-  resultMat[4] = (-m01 * b5 + m21 * b2 - m31 * b1) * idet;
-  resultMat[5] = (m00 * b5 - m20 * b2 + m30 * b1) * idet;
-  resultMat[6] = (-m03 * a5 + m23 * a2 - m33 * a1) * idet;
-  resultMat[7] = (m02 * a5 - m22 * a2 + m32 * a1) * idet;
-  resultMat[8] = (m01 * b4 - m11 * b2 + m31 * b0) * idet;
-  resultMat[9] = (-m00 * b4 + m10 * b2 - m30 * b0) * idet;
-  resultMat[10] = (m03 * a4 - m13 * a2 + m33 * a0) * idet;
-  resultMat[11] = (-m02 * a4 + m12 * a2 - m32 * a0) * idet;
-  resultMat[12] = (-m01 * b3 + m11 * b1 - m21 * b0) * idet;
-  resultMat[13] = (m00 * b3 - m10 * b1 + m20 * b0) * idet;
-  resultMat[14] = (-m03 * a3 + m13 * a1 - m23 * a0) * idet;
-  resultMat[15] = (m02 * a3 - m12 * a1 + m22 * a0) * idet;
-  return true;
-};
-
-
-/**
- * Returns true if the components of mat0 are equal to the components of mat1.
- *
- * @param {goog.vec.ArrayType} mat0 The first matrix.
- * @param {goog.vec.ArrayType} mat1 The second matrix.
- * @return {boolean} True if the the two matrices are equivalent.
- */
-goog.vec.Matrix4.equals = function(mat0, mat1) {
-  return mat0.length == mat1.length &&
-      mat0[0] == mat1[0] &&
-      mat0[1] == mat1[1] &&
-      mat0[2] == mat1[2] &&
-      mat0[3] == mat1[3] &&
-      mat0[4] == mat1[4] &&
-      mat0[5] == mat1[5] &&
-      mat0[6] == mat1[6] &&
-      mat0[7] == mat1[7] &&
-      mat0[8] == mat1[8] &&
-      mat0[9] == mat1[9] &&
-      mat0[10] == mat1[10] &&
-      mat0[11] == mat1[11] &&
-      mat0[12] == mat1[12] &&
-      mat0[13] == mat1[13] &&
-      mat0[14] == mat1[14] &&
-      mat0[15] == mat1[15];
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec. The input vector is multiplied against the
- * upper 3x4 matrix omitting the projective component.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The 3 element vector to
- *     transform.
- * @param {goog.vec.ArrayType} resultVec The 3 element vector to
- *     receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multVec3 = function(mat, vec, resultVec) {
-  var x = vec[0], y = vec[1], z = vec[2];
-  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + mat[12];
-  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + mat[13];
-  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + mat[14];
-  return resultVec;
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec. The input vector is multiplied against the
- * upper 3x3 matrix omitting the projective component and translation
- * components.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The 3 element vector to
- *     transform.
- * @param {goog.vec.ArrayType} resultVec The 3 element vector to
- *     receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multVec3NoTranslate = function(
-    mat, vec, resultVec) {
-  var x = vec[0], y = vec[1], z = vec[2];
-  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8];
-  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9];
-  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10];
-  return resultVec;
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec. The input vector is multiplied against the
- * full 4x4 matrix with the homogeneous divide applied to reduce the 4 element
- * vector to a 3 element vector.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The 3 element vector to
- *     transform.
- * @param {goog.vec.ArrayType} resultVec The 3 element vector
- *     to receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multVec3Projective = function(
-    mat, vec, resultVec) {
-  var x = vec[0], y = vec[1], z = vec[2];
-  var invw = 1 / (x * mat[3] + y * mat[7] + z * mat[11] + mat[15]);
-  resultVec[0] = (x * mat[0] + y * mat[4] + z * mat[8] + mat[12]) * invw;
-  resultVec[1] = (x * mat[1] + y * mat[5] + z * mat[9] + mat[13]) * invw;
-  resultVec[2] = (x * mat[2] + y * mat[6] + z * mat[10] + mat[14]) * invw;
-  return resultVec;
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The vector to transform.
- * @param {goog.vec.ArrayType} resultVec The vector to
- *     receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multVec4 = function(mat, vec, resultVec) {
-  var x = vec[0], y = vec[1], z = vec[2], w = vec[3];
-  resultVec[0] = x * mat[0] + y * mat[4] + z * mat[8] + w * mat[12];
-  resultVec[1] = x * mat[1] + y * mat[5] + z * mat[9] + w * mat[13];
-  resultVec[2] = x * mat[2] + y * mat[6] + z * mat[10] + w * mat[14];
-  resultVec[3] = x * mat[3] + y * mat[7] + z * mat[11] + w * mat[15];
-  return resultVec;
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec. The input matrix is multiplied against the
- * upper 3x4 matrix omitting the projective component.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The 3 element vector to
- *     transform.
- * @param {goog.vec.ArrayType} resultVec The 3 element vector to
- *     receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multVec3ToArray = function(mat, vec, resultVec) {
-  goog.vec.Matrix4.multVec3(
-      mat, vec, (/** @type {goog.vec.ArrayType} */ resultVec));
-  return resultVec;
-};
-
-
-/**
- * Transforms the given vector with the given matrix storing the resulting,
- * transformed vector into resultVec.
- *
- * @param {goog.vec.ArrayType} mat The matrix supplying the
- *     transformation.
- * @param {goog.vec.ArrayType} vec The vector to transform.
- * @param {goog.vec.ArrayType} resultVec The vector to
- *     receive the results (may be vec).
- * @return {goog.vec.ArrayType} return resultVec so that operations can be
- *     chained together.
- */
-goog.vec.Matrix4.multVec4ToArray = function(mat, vec, resultVec) {
-  goog.vec.Matrix4.multVec4(
-      mat, vec, (/** @type {goog.vec.ArrayType} */ resultVec));
-  return resultVec;
-};
-
-
-/**
- * Initializes the given 4x4 matrix as a translation matrix with x, y and z
- * translation factors.
- * @param {goog.vec.ArrayType} mat The 4x4 (16-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} x The translation along the x axis.
- * @param {number} y The translation along the y axis.
- * @param {number} z The translation along the z axis.
- */
-goog.vec.Matrix4.makeTranslate = function(mat, x, y, z) {
-  goog.vec.Matrix4.setIdentity(mat);
-  goog.vec.Matrix4.setColumnValues(mat, 3, x, y, z, 1);
-};
-
-
-/**
- * Initializes the given 4x4 matrix as a scale matrix with x, y and z scale
- * factors.
- * @param {goog.vec.ArrayType} mat The 4x4 (16-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} x The scale along the x axis.
- * @param {number} y The scale along the y axis.
- * @param {number} z The scale along the z axis.
- */
-goog.vec.Matrix4.makeScale = function(mat, x, y, z) {
-  goog.vec.Matrix4.setIdentity(mat);
-  goog.vec.Matrix4.setDiagonalValues(mat, x, y, z, 1);
-};
-
-
-/**
- * Initializes the given 4x4 matrix as a rotation matrix with the given rotation
- * angle about the axis defined by the vector (ax, ay, az).
- * @param {goog.vec.ArrayType} mat The 4x4 (16-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} angle The rotation angle in radians.
- * @param {number} ax The x component of the rotation axis.
- * @param {number} ay The y component of the rotation axis.
- * @param {number} az The z component of the rotation axis.
- */
-goog.vec.Matrix4.makeAxisAngleRotate = function(
-    mat, angle, ax, ay, az) {
-  var c = Math.cos(angle);
-  var d = 1 - c;
-  var s = Math.sin(angle);
-
-  goog.vec.Matrix4.setFromValues(mat,
-      ax * ax * d + c,
-      ax * ay * d + az * s,
-      ax * az * d - ay * s,
-      0,
-
-      ax * ay * d - az * s,
-      ay * ay * d + c,
-      ay * az * d + ax * s,
-      0,
-
-      ax * az * d + ay * s,
-      ay * az * d - ax * s,
-      az * az * d + c,
-      0,
-
-      0, 0, 0, 1);
-};
-
-
-/**
- * Initializes the given 4x4 matrix as a perspective projection matrix.
- * @param {goog.vec.ArrayType} mat The 4x4 (16-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} left The coordinate of the left clipping plane.
- * @param {number} right The coordinate of the right clipping plane.
- * @param {number} bottom The coordinate of the bottom clipping plane.
- * @param {number} top The coordinate of the top clipping plane.
- * @param {number} near The distance to the near clipping plane.
- * @param {number} far The distance to the far clipping plane.
- */
-goog.vec.Matrix4.makeFrustum = function(
-    mat, left, right, bottom, top, near, far) {
-  var x = (2 * near) / (right - left);
-  var y = (2 * near) / (top - bottom);
-  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);
-
-  goog.vec.Matrix4.setFromValues(mat,
-      x, 0, 0, 0,
-      0, y, 0, 0,
-      a, b, c, -1,
-      0, 0, d, 0
-  );
-};
-
-
-/**
- * Initializes the given 4x4 matrix as a perspective projection matrix given a
- * field of view and aspect ratio.
- * @param {goog.vec.ArrayType} mat The 4x4 (16-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} fovy The field of view along the y (vertical) axis in
- *     radians.
- * @param {number} aspect The x (width) to y (height) aspect ratio.
- * @param {number} near The distance to the near clipping plane.
- * @param {number} far The distance to the far clipping plane.
- */
-goog.vec.Matrix4.makePerspective = function(
-    mat, fovy, aspect, near, far) {
-  var angle = fovy / 2;
-  var dz = far - near;
-  var sinAngle = Math.sin(angle);
-  if (dz == 0 || sinAngle == 0 || aspect == 0) return;
-
-  var cot = Math.cos(angle) / sinAngle;
-  goog.vec.Matrix4.setFromValues(mat,
-      cot / aspect, 0, 0, 0,
-      0, cot, 0, 0,
-      0, 0, -(far + near) / dz, -1,
-      0, 0, -(2 * near * far) / dz, 0
-  );
-};
-
-
-/**
- * Initializes the given 4x4 matrix as an orthographic projection matrix.
- * @param {goog.vec.ArrayType} mat The 4x4 (16-element) matrix
- *     array to receive the new translation matrix.
- * @param {number} left The coordinate of the left clipping plane.
- * @param {number} right The coordinate of the right clipping plane.
- * @param {number} bottom The coordinate of the bottom clipping plane.
- * @param {number} top The coordinate of the top clipping plane.
- * @param {number} near The distance to the near clipping plane.
- * @param {number} far The distance to the far clipping plane.
- */
-goog.vec.Matrix4.makeOrtho = function(
-    mat, left, right, bottom, top, near, far) {
-  var x = 2 / (right - left);
-  var y = 2 / (top - bottom);
-  var z = -2 / (far - near);
-  var a = -(right + left) / (right - left);
-  var b = -(top + bottom) / (top - bottom);
-  var c = -(far + near) / (far - near);
-
-  goog.vec.Matrix4.setFromValues(mat,
-      x, 0, 0, 0,
-      0, y, 0, 0,
-      0, 0, z, 0,
-      a, b, c, 1
-  );
-};
-
-
-/**
- * Updates a matrix representing the modelview matrix of a camera so that
- * the camera is 'looking at' the given center point.
- * @param {goog.vec.ArrayType} viewMatrix The matrix.
- * @param {goog.vec.ArrayType} eyePt The position of the eye point
- *     (camera origin).
- * @param {goog.vec.ArrayType} centerPt The point to aim the camera
- *     at.
- * @param {goog.vec.ArrayType} worldUpVec The vector that
- *     identifies the up direction for the camera.
- */
-goog.vec.Matrix4.lookAt = function(
-    viewMatrix, eyePt, centerPt, worldUpVec) {
-  // Compute the direction vector from the eye point to the center point and
-  // normalize.
-  var fwdVec = goog.vec.Matrix4.tmpVec4_[0];
-  goog.vec.Vec3.subtract(centerPt, eyePt, fwdVec);
-  goog.vec.Vec3.normalize(fwdVec, fwdVec);
-  fwdVec[3] = 0;
-
-  // Compute the side vector from the forward vector and the input up vector.
-  var sideVec = goog.vec.Matrix4.tmpVec4_[1];
-  goog.vec.Vec3.cross(fwdVec, worldUpVec, sideVec);
-  goog.vec.Vec3.normalize(sideVec, sideVec);
-  sideVec[3] = 0;
-
-  // Now the up vector to form the orthonormal basis.
-  var upVec = goog.vec.Matrix4.tmpVec4_[2];
-  goog.vec.Vec3.cross(sideVec, fwdVec, upVec);
-  goog.vec.Vec3.normalize(upVec, upVec);
-  upVec[3] = 0;
-
-  // Update the view matrix with the new orthonormal basis and position the
-  // camera at the given eye point.
-  goog.vec.Vec3.negate(fwdVec, fwdVec);
-  goog.vec.Matrix4.setRow(viewMatrix, 0, sideVec);
-  goog.vec.Matrix4.setRow(viewMatrix, 1, upVec);
-  goog.vec.Matrix4.setRow(viewMatrix, 2, fwdVec);
-  goog.vec.Matrix4.setRowValues(viewMatrix, 3, 0, 0, 0, 1);
-  goog.vec.Matrix4.applyTranslate(
-      viewMatrix, -eyePt[0], -eyePt[1], -eyePt[2]);
-};
-
-
-/**
- * Decomposes a matrix into the lookAt vectors eyePt, fwdVec and worldUpVec.
- * The matrix represents the modelview matrix of a camera. It is the inverse
- * of lookAt except for the output of the fwdVec instead of centerPt.
- * The centerPt itself cannot be recovered from a modelview matrix.
- * @param {goog.vec.ArrayType} viewMatrix The matrix.
- * @param {goog.vec.ArrayType} eyePt The position of the eye point
- *     (camera origin).
- * @param {goog.vec.ArrayType} fwdVec The vector describing where
- *     the camera points to.
- * @param {goog.vec.ArrayType} worldUpVec The vector that
- *     identifies the up direction for the camera.
- * @return {boolean} True if the method succeeds, false otherwise.
- *     The method can only fail if the inverse of viewMatrix is not defined.
- */
-goog.vec.Matrix4.toLookAt = function(
-    viewMatrix, eyePt, fwdVec, worldUpVec) {
-  // Get eye of the camera.
-  var viewMatrixInverse = goog.vec.Matrix4.tmpMatrix4_[0];
-  if (!goog.vec.Matrix4.invert(viewMatrix, viewMatrixInverse)) {
-    // The input matrix does not have a valid inverse.
-    return false;
-  }
-
-  if (eyePt) {
-    eyePt[0] = viewMatrixInverse[12];
-    eyePt[1] = viewMatrixInverse[13];
-    eyePt[2] = viewMatrixInverse[14];
-  }
-
-  // Get forward vector from the definition of lookAt.
-  if (fwdVec || worldUpVec) {
-    if (!fwdVec) {
-      fwdVec = goog.vec.Matrix4.tmpVec3_[0];
-    }
-    fwdVec[0] = -viewMatrix[2];
-    fwdVec[1] = -viewMatrix[6];
-    fwdVec[2] = -viewMatrix[10];
-    // Normalize forward vector.
-    goog.vec.Vec3.normalize(fwdVec, fwdVec);
-  }
-
-  if (worldUpVec) {
-    // Get side vector from the definition of gluLookAt.
-    var side = goog.vec.Matrix4.tmpVec3_[1];
-    side[0] = viewMatrix[0];
-    side[1] = viewMatrix[4];
-    side[2] = viewMatrix[8];
-    // Compute up vector as a up = side x forward.
-    goog.vec.Vec3.cross(side, fwdVec, worldUpVec);
-    // Normalize up vector.
-    goog.vec.Vec3.normalize(worldUpVec, worldUpVec);
-  }
-  return true;
-};
-
-
-/**
- * Constructs a rotation matrix from its Euler angles using the ZXZ convention.
- * Given the euler angles [theta1, theta2, theta3], the rotation is defined as
- * rotation = rotation_z(theta1) * rotation_x(theta2) * rotation_z(theta3),
- * where rotation_x(theta) means rotation around the X axis of theta radians.
- * @param {goog.vec.ArrayType} matrix The rotation matrix.
- * @param {number} theta1 The angle of rotation around the Z axis in radians.
- * @param {number} theta2 The angle of rotation around the X axis in radians.
- * @param {number} theta3 The angle of rotation around the Z axis in radians.
- */
-goog.vec.Matrix4.fromEulerZXZ = function(
-    matrix, theta1, theta2, theta3) {
-  var c1 = Math.cos(theta1);
-  var s1 = Math.sin(theta1);
-
-  var c2 = Math.cos(theta2);
-  var s2 = Math.sin(theta2);
-
-  var c3 = Math.cos(theta3);
-  var s3 = Math.sin(theta3);
-
-  matrix[0] = c1 * c3 - c2 * s1 * s3;
-  matrix[1] = c2 * c1 * s3 + c3 * s1;
-  matrix[2] = s3 * s2;
-  matrix[3] = 0;
-
-  matrix[4] = -c1 * s3 - c3 * c2 * s1;
-  matrix[5] = c1 * c2 * c3 - s1 * s3;
-  matrix[6] = c3 * s2;
-  matrix[7] = 0;
-
-  matrix[8] = s2 * s1;
-  matrix[9] = -c1 * s2;
-  matrix[10] = c2;
-  matrix[11] = 0;
-
-  matrix[12] = 0;
-  matrix[13] = 0;
-  matrix[14] = 0;
-  matrix[15] = 1;
-};
-
-
-/**
- * Decomposes a rotation matrix into Euler angles using the ZXZ convention.
- * @param {goog.vec.ArrayType} matrix The rotation matrix.
- * @param {goog.vec.ArrayType} euler The ZXZ Euler angles in
- *     radians. euler = [roll, tilt, pan].
- */
-goog.vec.Matrix4.toEulerZXZ = function(matrix, euler) {
-  var s2 = Math.sqrt(matrix[2] * matrix[2] + matrix[6] * matrix[6]);
-
-  // There is an ambiguity in the sign of s2. We assume the tilt value
-  // is between [-pi/2, 0], so s2 is always negative.
-  if (s2 > goog.vec.EPSILON) {
-    euler[2] = Math.atan2(-matrix[2], -matrix[6]);
-    euler[1] = Math.atan2(-s2, matrix[10]);
-    euler[0] = Math.atan2(-matrix[8], matrix[9]);
-  } else {
-    // There is also an arbitrary choice for roll = 0 or pan = 0 in this case.
-    // We assume roll = 0 as some applications do not allow the camera to roll.
-    euler[0] = 0;
-    euler[1] = Math.atan2(-s2, matrix[10]);
-    euler[2] = Math.atan2(matrix[1], matrix[0]);
-  }
-};
-
-
-/**
- * Applies a translation by x,y,z to the given matrix.
- *
- * @param {goog.vec.ArrayType} mat The matrix.
- * @param {number} x The translation along the x axis.
- * @param {number} y The translation along the y axis.
- * @param {number} z The translation along the z axis.
- */
-goog.vec.Matrix4.applyTranslate = function(mat, x, y, z) {
-  goog.vec.Matrix4.setColumnValues(
-      mat, 3,
-      mat[0] * x + mat[4] * y + mat[8] * z + mat[12],
-      mat[1] * x + mat[5] * y + mat[9] * z + mat[13],
-      mat[2] * x + mat[6] * y + mat[10] * z + mat[14],
-      mat[3] * x + mat[7] * y + mat[11] * z + mat[15]);
-};
-
-
-/**
- * Applies an x,y,z scale to the given matrix.
- *
- * @param {goog.vec.ArrayType} mat The matrix.
- * @param {number} x The x scale factor.
- * @param {number} y The y scale factor.
- * @param {number} z The z scale factor.
- */
-goog.vec.Matrix4.applyScale = function(mat, x, y, z) {
-  goog.vec.Matrix4.setFromValues(
-      mat,
-      mat[0] * x, mat[1] * x, mat[2] * x, mat[3] * x,
-      mat[4] * y, mat[5] * y, mat[6] * y, mat[7] * y,
-      mat[8] * z, mat[9] * z, mat[10] * z, mat[11] * z,
-      mat[12], mat[13], mat[14], mat[15]);
-};
-
-
-/**
- * Applies a rotation by angle about the x,y,z axis to the given matrix.
- *
- * @param {goog.vec.ArrayType} mat The matrix.
- * @param {number} angle The angle in radians.
- * @param {number} x The x component of the rotation axis.
- * @param {number} y The y component of the rotation axis.
- * @param {number} z The z component of the rotation axis.
- */
-goog.vec.Matrix4.applyRotate = function(mat, angle, x, y, z) {
-  var m00 = mat[0], m10 = mat[1], m20 = mat[2], m30 = mat[3];
-  var m01 = mat[4], m11 = mat[5], m21 = mat[6], m31 = mat[7];
-  var m02 = mat[8], m12 = mat[9], m22 = mat[10], m32 = mat[11];
-  var m03 = mat[12], m13 = mat[13], m23 = mat[14], m33 = mat[15];
-
-  var cosAngle = Math.cos(angle);
-  var sinAngle = Math.sin(angle);
-  var diffCosAngle = 1 - cosAngle;
-  var r00 = x * x * diffCosAngle + cosAngle;
-  var r10 = x * y * diffCosAngle + z * sinAngle;
-  var r20 = x * z * diffCosAngle - y * sinAngle;
-
-  var r01 = x * y * diffCosAngle - z * sinAngle;
-  var r11 = y * y * diffCosAngle + cosAngle;
-  var r21 = y * z * diffCosAngle + x * sinAngle;
-
-  var r02 = x * z * diffCosAngle + y * sinAngle;
-  var r12 = y * z * diffCosAngle - x * sinAngle;
-  var r22 = z * z * diffCosAngle + cosAngle;
-
-  goog.vec.Matrix4.setFromValues(
-      mat,
-      m00 * r00 + m01 * r10 + m02 * r20,
-      m10 * r00 + m11 * r10 + m12 * r20,
-      m20 * r00 + m21 * r10 + m22 * r20,
-      m30 * r00 + m31 * r10 + m32 * r20,
-
-      m00 * r01 + m01 * r11 + m02 * r21,
-      m10 * r01 + m11 * r11 + m12 * r21,
-      m20 * r01 + m21 * r11 + m22 * r21,
-      m30 * r01 + m31 * r11 + m32 * r21,
-
-      m00 * r02 + m01 * r12 + m02 * r22,
-      m10 * r02 + m11 * r12 + m12 * r22,
-      m20 * r02 + m21 * r12 + m22 * r22,
-      m30 * r02 + m31 * r12 + m32 * r22,
-
-      m03, m13, m23, m33);
-};
-
-
-/**
- * @type {Array.<goog.vec.Vec3.Type>}
- * @private
- */
-goog.vec.Matrix4.tmpVec3_ = [
-  goog.vec.Vec3.createNumber(),
-  goog.vec.Vec3.createNumber()
-];
-
-
-/**
- * @type {Array.<goog.vec.Vec4.Type>}
- * @private
- */
-goog.vec.Matrix4.tmpVec4_ = [
-  goog.vec.Vec4.createNumber(),
-  goog.vec.Vec4.createNumber(),
-  goog.vec.Vec4.createNumber()
-];
-
-
-/**
- * @type {Array.<goog.vec.Matrix4.Type>}
- * @private
- */
-goog.vec.Matrix4.tmpMatrix4_ = [
-  goog.vec.Matrix4.create()
-];

+ 337 - 112
public/lib/closure/vec/quaternion.js

@@ -21,6 +21,7 @@
  *
  */
 goog.provide('goog.vec.Quaternion');
+goog.provide('goog.vec.Quaternion.AnyType');
 
 goog.require('goog.vec');
 goog.require('goog.vec.Vec3');
@@ -32,16 +33,6 @@ goog.require('goog.vec.Vec4');
 /** @typedef {goog.vec.Number} */ goog.vec.Quaternion.Number;
 /** @typedef {goog.vec.AnyType} */ goog.vec.Quaternion.AnyType;
 
-//The following type are deprecated - use the above types instead.
-/** @typedef {goog.vec.Vec4.Type} */ goog.vec.Quaternion.Type;
-/** @typedef {goog.vec.ArrayType} */ goog.vec.Quaternion.QuaternionLike;
-
-
-/**
- * @typedef {goog.vec.Vec4.Type}
- */
-goog.vec.Quaternion.Type;
-
 
 /**
  * Creates a Float32 quaternion, initialized to zero.
@@ -54,7 +45,7 @@ goog.vec.Quaternion.createFloat32 = goog.vec.Vec4.createFloat32;
 /**
  * Creates a Float64 quaternion, initialized to zero.
  *
- * @return {goog.vec.Quaternion.Float64} The new quaternion.
+ * @return {!goog.vec.Quaternion.Float64} The new quaternion.
  */
 goog.vec.Quaternion.createFloat64 = goog.vec.Vec4.createFloat64;
 
@@ -67,20 +58,11 @@ goog.vec.Quaternion.createFloat64 = goog.vec.Vec4.createFloat64;
 goog.vec.Quaternion.createNumber = goog.vec.Vec4.createNumber;
 
 
-/**
- * Creates a quaternion, initialized to zero.
- *
- * @deprecated Use createFloat32.
- * @return {!goog.vec.Quaternion.Type} The new quaternion.
- */
-goog.vec.Quaternion.create = goog.vec.Vec4.create;
-
-
 /**
  * Creates a new Float32 quaternion initialized with the values from the
  * supplied array.
  *
- * @param {goog.vec.AnyType} vec The source 4 element array.
+ * @param {!goog.vec.AnyType} vec The source 4 element array.
  * @return {!goog.vec.Quaternion.Float32} The new quaternion.
  */
 goog.vec.Quaternion.createFloat32FromArray =
@@ -91,25 +73,13 @@ goog.vec.Quaternion.createFloat32FromArray =
  * Creates a new Float64 quaternion initialized with the values from the
  * supplied array.
  *
- * @param {goog.vec.AnyType} vec The source 4 element array.
+ * @param {!goog.vec.AnyType} vec The source 4 element array.
  * @return {!goog.vec.Quaternion.Float64} The new quaternion.
  */
 goog.vec.Quaternion.createFloat64FromArray =
     goog.vec.Vec4.createFloat64FromArray;
 
 
-/**
- * Creates a new quaternion initialized with the values from the supplied
- * array.
- *
- * @deprecated Use createFloat32FromArray.
- * @param {!goog.vec.Quaternion.QuaternionLike} vec The source 4 element array.
- * @return {!goog.vec.Quaternion.Type} The new quaternion.
- */
-goog.vec.Quaternion.createFromArray =
-    goog.vec.Vec4.createFromArray;
-
-
 /**
  * Creates a new Float32 quaternion initialized with the supplied values.
  *
@@ -137,51 +107,51 @@ goog.vec.Quaternion.createFloat64FromValues =
 
 
 /**
- * Creates a new quaternion initialized with the supplied values.
+ * Creates a clone of the given Float32 quaternion.
  *
- * @deprecated Use createFloat32FromValues.
- * @param {number} v0 The value for element at index 0.
- * @param {number} v1 The value for element at index 1.
- * @param {number} v2 The value for element at index 2.
- * @param {number} v3 The value for element at index 3.
- * @return {!goog.vec.Quaternion.Type} The new quaternion.
+ * @param {!goog.vec.Quaternion.Float32} q The source quaternion.
+ * @return {!goog.vec.Quaternion.Float32} The new quaternion.
  */
-goog.vec.Quaternion.createFromValues =
-    goog.vec.Vec4.createFromValues;
+goog.vec.Quaternion.cloneFloat32 = goog.vec.Vec4.cloneFloat32;
 
 
 /**
- * Creates a clone of the given Float32 quaternion.
+ * Creates a clone of the given Float64 quaternion.
  *
- * @param {goog.vec.Quaternion.Float32} q The source quaternion.
- * @return {goog.vec.Quaternion.Float32} The new quaternion.
+ * @param {!goog.vec.Quaternion.Float64} q The source quaternion.
+ * @return {!goog.vec.Quaternion.Float64} The new quaternion.
  */
-goog.vec.Quaternion.cloneFloat32 = goog.vec.Vec4.cloneFloat32;
+goog.vec.Quaternion.cloneFloat64 = goog.vec.Vec4.cloneFloat64;
 
 
 /**
- * Creates a clone of the given Float64 quaternion.
+ * Creates a Float32 quaternion, initialized to the identity.
  *
- * @param {goog.vec.Quaternion.Float64} q The source quaternion.
- * @return {goog.vec.Quaternion.Float64} The new quaternion.
+ * @return {!goog.vec.Quaternion.Float32} The new quaternion.
  */
-goog.vec.Quaternion.cloneFloat64 = goog.vec.Vec4.cloneFloat64;
+goog.vec.Quaternion.createIdentityFloat32 = function() {
+  var quat = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.makeIdentity(quat);
+  return quat;
+};
 
 
 /**
- * Creates a clone of the given quaternion.
+ * Creates a Float64 quaternion, initialized to the identity.
  *
- * @deprecated Use cloneFloat32.
- * @param {goog.vec.Quaternion.QuaternionLike} q The source quaternion.
- * @return {!goog.vec.Quaternion.Type} The new quaternion.
+ * @return {!goog.vec.Quaternion.Float64} The new quaternion.
  */
-goog.vec.Quaternion.clone = goog.vec.Vec4.clone;
+goog.vec.Quaternion.createIdentityFloat64 = function() {
+  var quat = goog.vec.Quaternion.createFloat64();
+  goog.vec.Quaternion.makeIdentity(quat);
+  return quat;
+};
 
 
 /**
  * Initializes the quaternion with the given values.
  *
- * @param {goog.vec.Quaternion.AnyType} q The quaternion to receive
+ * @param {!goog.vec.Quaternion.AnyType} q The quaternion to receive
  *     the values.
  * @param {number} v0 The value for element at index 0.
  * @param {number} v1 The value for element at index 1.
@@ -196,9 +166,9 @@ goog.vec.Quaternion.setFromValues = goog.vec.Vec4.setFromValues;
 /**
  * Initializes the quaternion with the given array of values.
  *
- * @param {goog.vec.Quaternion.AnyType} q The quaternion to receive
+ * @param {!goog.vec.Quaternion.AnyType} q The quaternion to receive
  *     the values.
- * @param {goog.vec.AnyType} values The array of values.
+ * @param {!goog.vec.AnyType} values The array of values.
  * @return {!goog.vec.Quaternion.AnyType} return q so that operations can be
  *     chained together.
  */
@@ -208,9 +178,9 @@ goog.vec.Quaternion.setFromArray = goog.vec.Vec4.setFromArray;
 /**
  * Adds the two quaternions.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The first addend.
- * @param {goog.vec.Quaternion.AnyType} quat1 The second addend.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The first addend.
+ * @param {!goog.vec.Quaternion.AnyType} quat1 The second addend.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result. May be quat0 or quat1.
  */
 goog.vec.Quaternion.add = goog.vec.Vec4.add;
@@ -219,8 +189,8 @@ goog.vec.Quaternion.add = goog.vec.Vec4.add;
 /**
  * Negates a quaternion, storing the result into resultQuat.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The quaternion to negate.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The quaternion to negate.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result. May be quat0.
  */
 goog.vec.Quaternion.negate = goog.vec.Vec4.negate;
@@ -230,9 +200,9 @@ goog.vec.Quaternion.negate = goog.vec.Vec4.negate;
  * Multiplies each component of quat0 with scalar storing the product into
  * resultVec.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The source quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The source quaternion.
  * @param {number} scalar The value to multiply with each component of quat0.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result. May be quat0.
  */
 goog.vec.Quaternion.scale = goog.vec.Vec4.scale;
@@ -241,29 +211,27 @@ goog.vec.Quaternion.scale = goog.vec.Vec4.scale;
 /**
  * Returns the square magnitude of the given quaternion.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The quaternion.
  * @return {number} The magnitude of the quaternion.
  */
-goog.vec.Quaternion.magnitudeSquared =
-    goog.vec.Vec4.magnitudeSquared;
+goog.vec.Quaternion.magnitudeSquared = goog.vec.Vec4.magnitudeSquared;
 
 
 /**
  * Returns the magnitude of the given quaternion.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The quaternion.
  * @return {number} The magnitude of the quaternion.
  */
-goog.vec.Quaternion.magnitude =
-    goog.vec.Vec4.magnitude;
+goog.vec.Quaternion.magnitude = goog.vec.Vec4.magnitude;
 
 
 /**
  * Normalizes the given quaternion storing the result into resultVec.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The quaternion to
  *     normalize.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result. May be quat0.
  */
 goog.vec.Quaternion.normalize = goog.vec.Vec4.normalize;
@@ -272,21 +240,50 @@ goog.vec.Quaternion.normalize = goog.vec.Vec4.normalize;
 /**
  * Computes the dot (scalar) product of two quaternions.
  *
- * @param {goog.vec.Quaternion.AnyType} q0 The first quaternion.
- * @param {goog.vec.Quaternion.AnyType} q1 The second quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} q0 The first quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} q1 The second quaternion.
  * @return {number} The scalar product.
  */
 goog.vec.Quaternion.dot = goog.vec.Vec4.dot;
 
 
 /**
- * Computes the conjugate of the quaternion in quat storing the result into
+ * Computes the inverse of the quaternion in quat, storing the result into
+ * resultQuat.
+ *
+ * If the quaternion is already normalized, goog.vec.Quaternion.conjugate
+ * is faster than this function and produces the same result.
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The quaternion to invert.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to receive
+ *     the result.
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.invert = function(quat, resultQuat) {
+  var a0 = quat[0], a1 = quat[1], a2 = quat[2], a3 = quat[3];
+  var dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3;
+  var invDot = dot ? 1.0 / dot : 0;
+
+  resultQuat[0] = -a0 * invDot;
+  resultQuat[1] = -a1 * invDot;
+  resultQuat[2] = -a2 * invDot;
+  resultQuat[3] = a3 * invDot;
+  return resultQuat;
+};
+
+
+/**
+ * Computes the conjugate of the quaternion in quat, storing the result into
  * resultQuat.
  *
- * @param {goog.vec.Quaternion.AnyType} quat The source quaternion.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * If the quaternion is normalized already, this function is faster than
+ * goog.Quaternion.inverse and produces the same result.
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The source quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result.
- * @return {!goog.vec.Quaternion.AnyType} Return q so that
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
  *     operations can be chained together.
  */
 goog.vec.Quaternion.conjugate = function(quat, resultQuat) {
@@ -301,11 +298,11 @@ goog.vec.Quaternion.conjugate = function(quat, resultQuat) {
 /**
  * Concatenates the two quaternions storing the result into resultQuat.
  *
- * @param {goog.vec.Quaternion.AnyType} quat0 The first quaternion.
- * @param {goog.vec.Quaternion.AnyType} quat1 The second quaternion.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} quat0 The first quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} quat1 The second quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result.
- * @return {!goog.vec.Quaternion.AnyType} Return q so that
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
  *     operations can be chained together.
  */
 goog.vec.Quaternion.concat = function(quat0, quat1, resultQuat) {
@@ -319,17 +316,33 @@ goog.vec.Quaternion.concat = function(quat0, quat1, resultQuat) {
 };
 
 
+/**
+ * Makes the given quaternion the identity quaternion (0, 0, 0, 1).
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The quaternion.
+ * @return {!goog.vec.Quaternion.AnyType} Return quat so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.makeIdentity = function(quat) {
+  quat[0] = 0;
+  quat[1] = 0;
+  quat[2] = 0;
+  quat[3] = 1;
+  return quat;
+};
+
+
 /**
  * Generates a unit quaternion from the given angle-axis rotation pair.
  * The rotation axis is not required to be a unit vector, but should
  * have non-zero length.  The angle should be specified in radians.
  *
  * @param {number} angle The angle (in radians) to rotate about the axis.
- * @param {goog.vec.Quaternion.AnyType} axis Unit vector specifying the
+ * @param {!goog.vec.Quaternion.AnyType} axis Unit vector specifying the
  *     axis of rotation.
- * @param {goog.vec.Quaternion.AnyType} quat Unit quaternion to store the
+ * @param {!goog.vec.Quaternion.AnyType} quat Unit quaternion to store the
  *     result.
- * @return {goog.vec.Quaternion.AnyType} Return q so that
+ * @return {!goog.vec.Quaternion.AnyType} Return quat so that
  *     operations can be chained together.
  */
 goog.vec.Quaternion.fromAngleAxis = function(angle, axis, quat) {
@@ -355,8 +368,8 @@ goog.vec.Quaternion.fromAngleAxis = function(angle, axis, quat) {
  * is a non-zero length unit vector, and the returned angle is in
  * radians in the range of [-PI, +PI].
  *
- * @param {goog.vec.Quaternion.AnyType} quat Unit quaternion to convert.
- * @param {goog.vec.Quaternion.AnyType} axis Vector to store the returned
+ * @param {!goog.vec.Quaternion.AnyType} quat Unit quaternion to convert.
+ * @param {!goog.vec.Quaternion.AnyType} axis Vector to store the returned
  *     rotation axis.
  * @return {number} angle Angle (in radians) to rotate about 'axis'.
  *     The range of the returned angle is [-PI, +PI].
@@ -382,32 +395,150 @@ goog.vec.Quaternion.toAngleAxis = function(quat, axis) {
 
 
 /**
- * Generates the quaternion from the given rotation matrix.
+ * Generates the quaternion from the given 3x3 rotation matrix.
+ *
+ * Perf: http://jsperf.com/conversion-of-3x3-matrix-to-quaternion
+ *       http://jsperf.com/goog-vec-fromrotationmatrix3-a
+ *
+ * @param {!goog.vec.AnyType} matrix The source matrix.
+ * @param {!goog.vec.Quaternion.AnyType} quat The resulting quaternion.
+ * @return {!goog.vec.Quaternion.AnyType} Return quat so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.fromRotationMatrix3 = function(matrix, quat) {
+  // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
+  // article "Quaternion Calculus and Fast Animation".
+  var fTrace = matrix[0] + matrix[4] + matrix[8];
+  var fRoot;
+
+  if (fTrace > 0.0) {
+    // |w| > 1/2, may as well choose w > 1/2
+    fRoot = Math.sqrt(fTrace + 1.0);  // 2w
+    quat[3] = 0.5 * fRoot;
+    fRoot = 0.5 / fRoot;  // 1 / (4w)
+    quat[0] = (matrix[5] - matrix[7]) * fRoot;
+    quat[1] = (matrix[6] - matrix[2]) * fRoot;
+    quat[2] = (matrix[1] - matrix[3]) * fRoot;
+  } else {
+    // |w| <= 1/2
+    var i = 0;
+    if (matrix[4] > matrix[0]) i = 1;
+    if (matrix[8] > matrix[i * 3 + i]) i = 2;
+    var j = (i + 1) % 3;
+    var k = (i + 2) % 3;
+
+    fRoot = Math.sqrt(
+        matrix[i * 3 + i] - matrix[j * 3 + j] - matrix[k * 3 + k] + 1.0);
+    quat[i] = 0.5 * fRoot;
+    fRoot = 0.5 / fRoot;
+    quat[3] = (matrix[j * 3 + k] - matrix[k * 3 + j]) * fRoot;
+    quat[j] = (matrix[j * 3 + i] + matrix[i * 3 + j]) * fRoot;
+    quat[k] = (matrix[k * 3 + i] + matrix[i * 3 + k]) * fRoot;
+
+    // Flip all signs if w is negative.
+    if (quat[3] < 0) {
+      quat[0] = -quat[0];
+      quat[1] = -quat[1];
+      quat[2] = -quat[2];
+      quat[3] = -quat[3];
+    }
+  }
+  return quat;
+};
+
+
+/**
+ * Generates the quaternion from the given 4x4 rotation matrix.
+ *
+ * Perf: http://jsperf.com/goog-vec-fromrotationmatrix4
  *
- * @param {goog.vec.Quaternion.AnyType} matrix The source matrix.
- * @param {goog.vec.Quaternion.AnyType} quat The resulting quaternion.
- * @return {!goog.vec.Quaternion.AnyType} Return q so that
+ * Implementation is the same as fromRotationMatrix3 but using indices from
+ * the top left 3x3 in a 4x4 matrix.
+ *
+ * @param {!goog.vec.AnyType} matrix The source matrix.
+ * @param {!goog.vec.Quaternion.AnyType} quat The resulting quaternion.
+ * @return {!goog.vec.Quaternion.AnyType} Return quat so that
  *     operations can be chained together.
  */
 goog.vec.Quaternion.fromRotationMatrix4 = function(matrix, quat) {
-  var sx = matrix[0], sy = matrix[5], sz = matrix[10];
-  quat[3] = Math.sqrt(Math.max(0, 1 + sx + sy + sz)) / 2;
-  quat[0] = Math.sqrt(Math.max(0, 1 + sx - sy - sz)) / 2;
-  quat[1] = Math.sqrt(Math.max(0, 1 - sx + sy - sz)) / 2;
-  quat[2] = Math.sqrt(Math.max(0, 1 - sx - sy + sz)) / 2;
-
-  quat[0] = (matrix[6] - matrix[9] < 0) != (quat[0] < 0) ? -quat[0] : quat[0];
-  quat[1] = (matrix[8] - matrix[2] < 0) != (quat[1] < 0) ? -quat[1] : quat[1];
-  quat[2] = (matrix[1] - matrix[4] < 0) != (quat[2] < 0) ? -quat[2] : quat[2];
+  var fTrace = matrix[0] + matrix[5] + matrix[10];
+  var fRoot;
+
+  if (fTrace > 0.0) {
+    // |w| > 1/2, may as well choose w > 1/2
+    fRoot = Math.sqrt(fTrace + 1.0);  // 2w
+    quat[3] = 0.5 * fRoot;
+    fRoot = 0.5 / fRoot;  // 1 / (4w)
+    quat[0] = (matrix[6] - matrix[9]) * fRoot;
+    quat[1] = (matrix[8] - matrix[2]) * fRoot;
+    quat[2] = (matrix[1] - matrix[4]) * fRoot;
+  } else {
+    // |w| <= 1/2
+    var i = 0;
+    if (matrix[5] > matrix[0]) i = 1;
+    if (matrix[10] > matrix[i * 4 + i]) i = 2;
+    var j = (i + 1) % 3;
+    var k = (i + 2) % 3;
+
+    fRoot = Math.sqrt(
+        matrix[i * 4 + i] - matrix[j * 4 + j] - matrix[k * 4 + k] + 1.0);
+    quat[i] = 0.5 * fRoot;
+    fRoot = 0.5 / fRoot;
+    quat[3] = (matrix[j * 4 + k] - matrix[k * 4 + j]) * fRoot;
+    quat[j] = (matrix[j * 4 + i] + matrix[i * 4 + j]) * fRoot;
+    quat[k] = (matrix[k * 4 + i] + matrix[i * 4 + k]) * fRoot;
+
+    // Flip all signs if w is negative.
+    if (quat[3] < 0) {
+      quat[0] = -quat[0];
+      quat[1] = -quat[1];
+      quat[2] = -quat[2];
+      quat[3] = -quat[3];
+    }
+  }
   return quat;
 };
 
 
 /**
- * Generates the rotation matrix from the given quaternion.
+ * Generates the 3x3 rotation matrix from the given quaternion.
  *
- * @param {goog.vec.Quaternion.AnyType} quat The source quaternion.
- * @param {goog.vec.AnyType} matrix The resulting matrix.
+ * @param {!goog.vec.Quaternion.AnyType} quat The source quaternion.
+ * @param {!goog.vec.AnyType} matrix The resulting matrix.
+ * @return {!goog.vec.AnyType} Return resulting matrix so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.toRotationMatrix3 = function(quat, matrix) {
+  var x = quat[0], y = quat[1], z = quat[2], w = quat[3];
+  var x2 = 2 * x, y2 = 2 * y, z2 = 2 * z;
+  var wx = x2 * w;
+  var wy = y2 * w;
+  var wz = z2 * w;
+  var xx = x2 * x;
+  var xy = y2 * x;
+  var xz = z2 * x;
+  var yy = y2 * y;
+  var yz = z2 * y;
+  var zz = z2 * z;
+
+  matrix[0] = 1 - (yy + zz);
+  matrix[1] = xy + wz;
+  matrix[2] = xz - wy;
+  matrix[3] = xy - wz;
+  matrix[4] = 1 - (xx + zz);
+  matrix[5] = yz + wx;
+  matrix[6] = xz + wy;
+  matrix[7] = yz - wx;
+  matrix[8] = 1 - (xx + yy);
+  return matrix;
+};
+
+
+/**
+ * Generates the 4x4 rotation matrix from the given quaternion.
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The source quaternion.
+ * @param {!goog.vec.AnyType} matrix The resulting matrix.
  * @return {!goog.vec.AnyType} Return resulting matrix so that
  *     operations can be chained together.
  */
@@ -444,17 +575,111 @@ goog.vec.Quaternion.toRotationMatrix4 = function(quat, matrix) {
 };
 
 
+/**
+ * Rotates a quaternion by the given angle about the X axis.
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The quaternion.
+ * @param {number} angle The angle in radians.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ *     receive the result.
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.rotateX = function(quat, angle, resultQuat) {
+  angle *= 0.5;
+  var ax = quat[0], ay = quat[1], az = quat[2], aw = quat[3];
+  var bx = Math.sin(angle), bw = Math.cos(angle);
+
+  resultQuat[0] = ax * bw + aw * bx;
+  resultQuat[1] = ay * bw + az * bx;
+  resultQuat[2] = az * bw - ay * bx;
+  resultQuat[3] = aw * bw - ax * bx;
+  return resultQuat;
+};
+
+
+/**
+ * Rotates a quaternion by the given angle about the Y axis.
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The quaternion.
+ * @param {number} angle The angle in radians.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ *     receive the result.
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.rotateY = function(quat, angle, resultQuat) {
+  angle *= 0.5;
+  var ax = quat[0], ay = quat[1], az = quat[2], aw = quat[3];
+  var by = Math.sin(angle), bw = Math.cos(angle);
+
+  resultQuat[0] = ax * bw - az * by;
+  resultQuat[1] = ay * bw + aw * by;
+  resultQuat[2] = az * bw + ax * by;
+  resultQuat[3] = aw * bw - ay * by;
+  return resultQuat;
+};
+
+
+/**
+ * Rotates a quaternion by the given angle about the Z axis.
+ *
+ * @param {!goog.vec.Quaternion.AnyType} quat The quaternion.
+ * @param {number} angle The angle in radians.
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ *     receive the result.
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
+ *     operations can be chained together.
+ */
+goog.vec.Quaternion.rotateZ = function(quat, angle, resultQuat) {
+  angle *= 0.5;
+  var ax = quat[0], ay = quat[1], az = quat[2], aw = quat[3];
+  var bz = Math.sin(angle), bw = Math.cos(angle);
+
+  resultQuat[0] = ax * bw + ay * bz;
+  resultQuat[1] = ay * bw - ax * bz;
+  resultQuat[2] = az * bw + aw * bz;
+  resultQuat[3] = aw * bw - az * bz;
+  return resultQuat;
+};
+
+
+/**
+ * Transforms a vec with a quaternion. Works on both vec3s and vec4s.
+ *
+ * @param {!goog.vec.AnyType} vec The vec to transform.
+ * @param {!goog.vec.Quaternion.AnyType} quat The quaternion.
+ * @param {!goog.vec.AnyType} resultVec The vec to receive the result.
+ * @return {!goog.vec.AnyType} Return resultVec so that operations can be
+ *     chained together. Note that the caller is responsible for type-casting.
+ */
+goog.vec.Quaternion.transformVec = function(vec, quat, resultVec) {
+  var x = vec[0], y = vec[1], z = vec[2];
+  var qx = quat[0], qy = quat[1], qz = quat[2], qw = quat[3];
+  // Calculate quat * vec.
+  var ix = qw * x + qy * z - qz * y;
+  var iy = qw * y + qz * x - qx * z;
+  var iz = qw * z + qx * y - qy * x;
+  var iw = -qx * x - qy * y - qz * z;
+  // Calculate result * inverse quat.
+  resultVec[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+  resultVec[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+  resultVec[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+  return resultVec;
+};
+
+
 /**
  * Computes the spherical linear interpolated value from the given quaternions
  * q0 and q1 according to the coefficient t. The resulting quaternion is stored
  * in resultQuat.
  *
- * @param {goog.vec.Quaternion.AnyType} q0 The first quaternion.
- * @param {goog.vec.Quaternion.AnyType} q1 The second quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} q0 The first quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} q1 The second quaternion.
  * @param {number} t The interpolating coefficient.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the result.
- * @return {goog.vec.Quaternion.AnyType} Return q so that
+ * @return {!goog.vec.Quaternion.AnyType} Return resultQuat so that
  *     operations can be chained together.
  */
 goog.vec.Quaternion.slerp = function(q0, q1, t, resultQuat) {
@@ -504,10 +729,10 @@ goog.vec.Quaternion.slerp = function(q0, q1, t, resultQuat) {
  * according to the coefficient t. The resulting quaternion is stored in
  * resultVec.
  *
- * @param {goog.vec.Quaternion.AnyType} q0 The first quaternion.
- * @param {goog.vec.Quaternion.AnyType} q1 The second quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} q0 The first quaternion.
+ * @param {!goog.vec.Quaternion.AnyType} q1 The second quaternion.
  * @param {number} t The interpolation factor.
- * @param {goog.vec.Quaternion.AnyType} resultQuat The quaternion to
+ * @param {!goog.vec.Quaternion.AnyType} resultQuat The quaternion to
  *     receive the results (may be q0 or q1).
  */
 goog.vec.Quaternion.nlerp = goog.vec.Vec4.lerp;

+ 11 - 0
public/lib/closure/vec/quaternion_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.QuaternionTest');</script>
+<title>Closure Unit Tests - goog.vec.QuaternionTest</title></head><body></body></html>

+ 292 - 0
public/lib/closure/vec/quaternion_test.js

@@ -0,0 +1,292 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.QuaternionTest');
+goog.setTestOnly('goog.vec.QuaternionTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Mat3');
+goog.require('goog.vec.Mat4');
+goog.require('goog.vec.Quaternion');
+goog.require('goog.vec.Vec3');
+goog.require('goog.vec.vec3f');
+
+function testCreateIdentityFloat32() {
+  var q = goog.vec.Quaternion.createIdentityFloat32();
+  assertElementsEquals([0, 0, 0, 1], q);
+}
+
+function testInvert() {
+  var q0 = goog.vec.Quaternion.createFloat32FromValues(1, 2, 3, 4);
+  var q1 = goog.vec.Quaternion.createFloat32();
+
+  goog.vec.Quaternion.invert(q0, q1);
+  assertElementsRoughlyEqual([1, 2, 3, 4], q0, goog.vec.EPSILON);
+  assertElementsRoughlyEqual([-0.033333, -0.066666, -0.1, 0.133333], q1,
+      goog.vec.EPSILON);
+}
+
+function testConjugate() {
+  var q0 = goog.vec.Quaternion.createFloat32FromValues(1, 2, 3, 4);
+  var q1 = goog.vec.Quaternion.createFloat32();
+
+  goog.vec.Quaternion.conjugate(q0, q1);
+  assertElementsEquals([1, 2, 3, 4], q0);
+  assertElementsEquals([-1, -2, -3, 4], q1);
+
+  goog.vec.Quaternion.conjugate(q1, q1);
+  assertElementsEquals([1, 2, 3, 4], q1);
+
+  // Conjugate and inverse of a normalized quaternion should be equal.
+  var q2 = goog.vec.Quaternion.createFloat32();
+  var q3 = goog.vec.Quaternion.createFloat32();
+
+  goog.vec.Quaternion.normalize(q0, q2);
+  goog.vec.Quaternion.conjugate(q2, q2);
+
+  goog.vec.Quaternion.normalize(q0, q3);
+  goog.vec.Quaternion.invert(q3, q3);
+
+  assertElementsRoughlyEqual(q2, q3, goog.vec.EPSILON);
+}
+
+function testConcat() {
+  var q0 = goog.vec.Quaternion.createFloat32FromValues(1, 2, 3, 4);
+  var q1 = goog.vec.Quaternion.createFloat32FromValues(2, 3, 4, 5);
+  var q2 = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.concat(q0, q1, q2);
+  assertElementsEquals([12, 24, 30, 0], q2);
+
+  goog.vec.Quaternion.concat(q0, q1, q0);
+  assertElementsEquals([12, 24, 30, 0], q0);
+}
+
+function testMakeIdentity() {
+  var q = goog.vec.Quaternion.createFloat32FromValues(1, 2, 3, 4);
+  goog.vec.Quaternion.makeIdentity(q);
+  assertElementsEquals([0, 0, 0, 1], q);
+}
+
+function testRotateX() {
+  var q = goog.vec.Quaternion.createIdentityFloat32();
+  goog.vec.Quaternion.rotateX(q, Math.PI / 2, q);
+
+  var axis = goog.vec.Vec3.createFloat32();
+  var angle = goog.vec.Quaternion.toAngleAxis(q, axis);
+
+  assertElementsRoughlyEqual([1, 0, 0], axis, goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
+}
+
+function testRotateY() {
+  var q = goog.vec.Quaternion.createIdentityFloat32();
+  goog.vec.Quaternion.rotateY(q, Math.PI / 2, q);
+
+  var axis = goog.vec.Vec3.createFloat32();
+  var angle = goog.vec.Quaternion.toAngleAxis(q, axis);
+
+  assertElementsRoughlyEqual([0, 1, 0], axis, goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
+}
+
+function testRotateZ() {
+  var q = goog.vec.Quaternion.createIdentityFloat32();
+  goog.vec.Quaternion.rotateZ(q, Math.PI / 2, q);
+
+  var axis = goog.vec.Vec3.createFloat32();
+  var angle = goog.vec.Quaternion.toAngleAxis(q, axis);
+
+  assertElementsRoughlyEqual([0, 0, 1], axis, goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
+}
+
+function testTransformVec() {
+  var q = goog.vec.Quaternion.createIdentityFloat32();
+  goog.vec.Quaternion.rotateX(q, Math.PI / 2, q);
+
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [0, 0, 1]);
+  var v1 = goog.vec.vec3f.create();
+
+  goog.vec.Quaternion.transformVec(v0, q, v1);
+  assertElementsRoughlyEqual([0, -1, 0], v1, goog.vec.EPSILON);
+}
+
+function testSlerp() {
+  var q0 = goog.vec.Quaternion.createFloat32FromValues(1, 2, 3, 4);
+  var q1 = goog.vec.Quaternion.createFloat32FromValues(5, -6, 7, -8);
+  var q2 = goog.vec.Quaternion.createFloat32();
+
+  goog.vec.Quaternion.slerp(q0, q1, 0, q2);
+  assertElementsEquals([5, -6, 7, -8], q2);
+
+  goog.vec.Quaternion.normalize(q0, q0);
+  goog.vec.Quaternion.normalize(q1, q1);
+
+  goog.vec.Quaternion.slerp(q0, q0, .5, q2);
+  assertElementsEquals(q0, q2);
+
+  goog.vec.Quaternion.slerp(q0, q1, 0, q2);
+  assertElementsEquals(q0, q2);
+
+  goog.vec.Quaternion.slerp(q0, q1, 1, q2);
+  if (q1[3] * q2[3] < 0) {
+    goog.vec.Quaternion.negate(q2, q2);
+  }
+  assertElementsEquals(q1, q2);
+
+  goog.vec.Quaternion.slerp(q0, q1, .3, q2);
+  assertElementsRoughlyEqual(
+      [-0.000501537327541, 0.4817612034640, 0.2398775270769, 0.842831337398],
+      q2, goog.vec.EPSILON);
+
+  goog.vec.Quaternion.slerp(q0, q1, .5, q2);
+  assertElementsRoughlyEqual(
+      [-0.1243045421171, 0.51879732466, 0.0107895780990, 0.845743047108],
+      q2, goog.vec.EPSILON);
+
+  goog.vec.Quaternion.slerp(q0, q1, .8, q0);
+  assertElementsRoughlyEqual(
+      [-0.291353561485, 0.506925588797, -0.3292443285721, 0.741442999653],
+      q0, goog.vec.EPSILON);
+}
+
+function testFromRotMatrix() {
+  var m0 = goog.vec.Mat3.createFloat32FromValues(
+      -0.408248, 0.8796528, -0.244016935,
+      -0.4082482, 0.06315623, 0.9106836,
+      0.8164965, 0.47140452, 0.3333333);
+  var q0 = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.fromRotationMatrix3(m0, q0);
+  assertElementsRoughlyEqual(
+      [0.22094256606638, 0.53340203646030, 0.64777022739548, 0.497051689967954],
+      q0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.Mat3.createFloat32FromValues(
+      -0.544310, 0, 0.838884, 0, 1, 0, -0.838884, 0, -0.544310);
+  var q1 = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.fromRotationMatrix3(m1, q1);
+  assertElementsRoughlyEqual(
+      [0, -0.87872350215912, 0, 0.477331042289734], q1, goog.vec.EPSILON);
+
+  var m2 = goog.vec.Mat4.createFloat32FromValues(
+      -0.408248, 0.8796528, -0.244016935, 0, -0.4082482, 0.06315623, 0.9106836,
+      0, 0.8164965, 0.47140452, 0.3333333, 0, 0, 0, 0, 1);
+  var q2 = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.fromRotationMatrix4(m2, q2);
+  assertElementsRoughlyEqual(
+      [0.22094256606638, 0.53340203646030, 0.64777022739548, 0.497051689967954],
+      q2, goog.vec.EPSILON);
+
+  var m3 = goog.vec.Mat4.createFloat32FromValues(
+      -0.544310, 0, 0.838884, 0, 0, 1, 0, 0, -0.838884, 0, -0.544310, 0, 0, 0,
+      0, 1);
+  var q3 = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.fromRotationMatrix4(m3, q3);
+  assertElementsRoughlyEqual(
+      [0, -0.87872350215912, 0, 0.477331042289734], q3, goog.vec.EPSILON);
+
+  assertElementsRoughlyEqual(q0, q2, goog.vec.EPSILON);
+  assertElementsRoughlyEqual(q1, q3, goog.vec.EPSILON);
+}
+
+function testToRotMatrix() {
+  var q0 = goog.vec.Quaternion.createFloat32FromValues(
+      0.22094256606638, 0.53340203646030,
+      0.64777022739548, 0.497051689967954);
+  var m0 = goog.vec.Mat3.createFloat32();
+  goog.vec.Quaternion.toRotationMatrix3(q0, m0);
+
+  assertElementsRoughlyEqual(
+      [-0.408248, 0.8796528, -0.244016935,
+       -0.4082482, 0.06315623, 0.9106836,
+       0.8164965, 0.47140452, 0.3333333],
+      m0, goog.vec.EPSILON);
+
+  var m1 = goog.vec.Mat4.createFloat32();
+  goog.vec.Quaternion.toRotationMatrix4(q0, m1);
+
+  assertElementsRoughlyEqual(
+      [-0.408248, 0.8796528, -0.244016935, 0,
+       -0.4082482, 0.06315623, 0.9106836, 0,
+       0.8164965, 0.47140452, 0.3333333, 0,
+       0, 0, 0, 1],
+      m1, goog.vec.EPSILON);
+}
+
+function testToAngleAxis() {
+  // Test the identity rotation.
+  var q0 = goog.vec.Quaternion.createFloat32FromValues(0, 0, 0, 1);
+  var axis = goog.vec.Vec3.createFloat32();
+  var angle = goog.vec.Quaternion.toAngleAxis(q0, axis);
+  assertRoughlyEquals(0.0, angle, goog.vec.EPSILON);
+  assertElementsRoughlyEqual([1, 0, 0], axis, goog.vec.EPSILON);
+
+  // Check equivalent representations of the same rotation.
+  goog.vec.Quaternion.setFromValues(
+      q0, -0.288675032, 0.622008682, -0.17254543, 0.70710678);
+  angle = goog.vec.Quaternion.toAngleAxis(q0, axis);
+  assertRoughlyEquals(Math.PI / 2, angle, goog.vec.EPSILON);
+  assertElementsRoughlyEqual([-0.408248, 0.8796528, -0.244016],
+                             axis, goog.vec.EPSILON);
+  // The polar opposite unit quaternion is the same rotation, so we
+  // check that the negated quaternion yields the negated angle and axis.
+  goog.vec.Quaternion.negate(q0, q0);
+  angle = goog.vec.Quaternion.toAngleAxis(q0, axis);
+  assertRoughlyEquals(-Math.PI / 2, angle, goog.vec.EPSILON);
+  assertElementsRoughlyEqual([0.408248, -0.8796528, 0.244016],
+                             axis, goog.vec.EPSILON);
+
+  // Verify that the inverse rotation yields the inverse axis.
+  goog.vec.Quaternion.conjugate(q0, q0);
+  angle = goog.vec.Quaternion.toAngleAxis(q0, axis);
+  assertRoughlyEquals(-Math.PI / 2, angle, goog.vec.EPSILON);
+  assertElementsRoughlyEqual([-0.408248, 0.8796528, -0.244016],
+                             axis, goog.vec.EPSILON);
+}
+
+function testFromAngleAxis() {
+  // Test identity rotation (zero angle or multiples of TWO_PI).
+  var angle = 0.0;
+  var axis = goog.vec.Vec3.createFloat32FromValues(-0.408248, 0.8796528,
+                                                   -0.244016);
+  var q0 = goog.vec.Quaternion.createFloat32();
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual([0, 0, 0, 1], q0, goog.vec.EPSILON);
+  angle = 4 * Math.PI;
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual([0, 0, 0, 1], q0, goog.vec.EPSILON);
+
+  // General test of various rotations around axes of different lengths.
+  angle = Math.PI / 2;
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual(
+      [-0.288675032, 0.622008682, -0.17254543, 0.70710678],
+      q0, goog.vec.EPSILON);
+  // Angle multiples of TWO_PI with a scaled axis should be the same.
+  angle += 4 * Math.PI;
+  goog.vec.Vec3.scale(axis, 7.0, axis);
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual(
+      [-0.288675032, 0.622008682, -0.17254543, 0.70710678],
+      q0, goog.vec.EPSILON);
+  goog.vec.Vec3.setFromValues(axis, 1, 5, 8);
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual(
+      [0.074535599, 0.372677996, 0.596284794, 0.70710678],
+      q0, goog.vec.EPSILON);
+
+  // Check equivalent representations of the same rotation.
+  angle = Math.PI / 5;
+  goog.vec.Vec3.setFromValues(axis, 5, -2, -10);
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual(
+      [0.136037146, -0.0544148586, -0.27207429, 0.951056516],
+      q0, goog.vec.EPSILON);
+  // The negated angle and axis should yield the same rotation.
+  angle = -Math.PI / 5;
+  goog.vec.Vec3.negate(axis, axis);
+  goog.vec.Quaternion.fromAngleAxis(angle, axis, q0);
+  assertElementsRoughlyEqual(
+      [0.136037146, -0.0544148586, -0.27207429, 0.951056516],
+      q0, goog.vec.EPSILON);
+}

+ 6 - 6
public/lib/closure/vec/ray.js

@@ -33,20 +33,21 @@ goog.require('goog.vec.Vec3');
  * @param {goog.vec.Vec3.AnyType=} opt_origin The optional origin.
  * @param {goog.vec.Vec3.AnyType=} opt_dir The optional direction.
  * @constructor
+ * @final
  */
 goog.vec.Ray = function(opt_origin, opt_dir) {
   /**
-   * @type {goog.vec.Vec3.Number}
+   * @type {goog.vec.Vec3.Float64}
    */
-  this.origin = goog.vec.Vec3.createNumber();
+  this.origin = goog.vec.Vec3.createFloat64();
   if (opt_origin) {
     goog.vec.Vec3.setFromArray(this.origin, opt_origin);
   }
 
   /**
-   * @type {goog.vec.Vec3.Number}
+   * @type {goog.vec.Vec3.Float64}
    */
-  this.dir = goog.vec.Vec3.createNumber();
+  this.dir = goog.vec.Vec3.createFloat64();
   if (opt_dir) {
     goog.vec.Vec3.setFromArray(this.dir, opt_dir);
   }
@@ -88,7 +89,6 @@ goog.vec.Ray.prototype.setDir = function(dir) {
  * @return {boolean} True if this ray is equal to the other ray.
  */
 goog.vec.Ray.prototype.equals = function(other) {
-  return other != null &&
-      goog.vec.Vec3.equals(this.origin, other.origin) &&
+  return other != null && goog.vec.Vec3.equals(this.origin, other.origin) &&
       goog.vec.Vec3.equals(this.dir, other.dir);
 };

+ 11 - 0
public/lib/closure/vec/ray_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.RayTest');</script>
+<title>Closure Unit Tests - goog.vec.RayTest</title></head><body></body></html>

+ 51 - 0
public/lib/closure/vec/ray_test.js

@@ -0,0 +1,51 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.RayTest');
+goog.setTestOnly('goog.vec.RayTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Ray');
+
+function testConstructor() {
+  var new_ray = new goog.vec.Ray();
+  assertElementsEquals([0, 0, 0], new_ray.origin);
+  assertElementsEquals([0, 0, 0], new_ray.dir);
+
+  new_ray = new goog.vec.Ray([1, 2, 3], [4, 5, 6]);
+  assertElementsEquals([1, 2, 3], new_ray.origin);
+  assertElementsEquals([4, 5, 6], new_ray.dir);
+}
+
+function testSet() {
+  var new_ray = new goog.vec.Ray();
+  new_ray.set([2, 3, 4], [5, 6, 7]);
+  assertElementsEquals([2, 3, 4], new_ray.origin);
+  assertElementsEquals([5, 6, 7], new_ray.dir);
+}
+
+function testSetOrigin() {
+  var new_ray = new goog.vec.Ray();
+  new_ray.setOrigin([1, 2, 3]);
+  assertElementsEquals([1, 2, 3], new_ray.origin);
+  assertElementsEquals([0, 0, 0], new_ray.dir);
+}
+
+
+function testSetDir() {
+  var new_ray = new goog.vec.Ray();
+  new_ray.setDir([2, 3, 4]);
+  assertElementsEquals([0, 0, 0], new_ray.origin);
+  assertElementsEquals([2, 3, 4], new_ray.dir);
+}
+
+function testEquals() {
+  var r0 = new goog.vec.Ray([1, 2, 3], [4, 5, 6]);
+  var r1 = new goog.vec.Ray([5, 2, 3], [4, 5, 6]);
+  assertFalse(r0.equals(r1));
+  assertFalse(r0.equals(null));
+  assertTrue(r1.equals(r1));
+  r1.setOrigin(r0.origin);
+  assertTrue(r1.equals(r0));
+  assertTrue(r0.equals(r1));
+}

+ 14 - 6
public/lib/closure/vec/vec.js

@@ -18,39 +18,47 @@
  *     library.
  */
 goog.provide('goog.vec');
+goog.provide('goog.vec.AnyType');
+goog.provide('goog.vec.ArrayType');
+goog.provide('goog.vec.Float32');
+goog.provide('goog.vec.Float64');
+goog.provide('goog.vec.Number');
+
 
 /**
  * On platforms that don't have native Float32Array or Float64Array support we
  * use a javascript implementation so that this math library can be used on all
  * platforms.
+ * @suppress {extraRequire}
  */
 goog.require('goog.vec.Float32Array');
+/** @suppress {extraRequire} */
 goog.require('goog.vec.Float64Array');
 
 // All vector and matrix operations are based upon arrays of numbers using
-// either Float32Array, Float64Array, or a standard Javascript Array of
+// either Float32Array, Float64Array, or a standard JavaScript Array of
 // Numbers.
 
 
-/** @typedef {Float32Array} */
+/** @typedef {!Float32Array} */
 goog.vec.Float32;
 
 
-/** @typedef {Float64Array} */
+/** @typedef {!Float64Array} */
 goog.vec.Float64;
 
 
-/** @typedef {Array.<number>} */
+/** @typedef {!Array<number>} */
 goog.vec.Number;
 
 
-/** @typedef {goog.vec.Float32|goog.vec.Float64|goog.vec.Number} */
+/** @typedef {!goog.vec.Float32|!goog.vec.Float64|!goog.vec.Number} */
 goog.vec.AnyType;
 
 
 /**
  * @deprecated Use AnyType.
- * @typedef {Float32Array|Array.<number>}
+ * @typedef {!Float32Array|!Array<number>}
  */
 goog.vec.ArrayType;
 

+ 65 - 2
public/lib/closure/vec/vec2.js

@@ -20,6 +20,7 @@
 
 goog.provide('goog.vec.Vec2');
 
+/** @suppress {extraRequire} */
 goog.require('goog.vec');
 
 
@@ -219,6 +220,23 @@ goog.vec.Vec2.negate = function(vec0, resultVec) {
 };
 
 
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {goog.vec.Vec2.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec2.AnyType} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.Vec2.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec2.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  return resultVec;
+};
+
+
 /**
  * Multiplies each component of vec0 with scalar storing the product into
  * resultVec.
@@ -362,6 +380,52 @@ goog.vec.Vec2.lerp = function(vec0, vec1, f, resultVec) {
 };
 
 
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {goog.vec.Vec2.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec2.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec2.AnyType} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.Vec2.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec2.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {goog.vec.Vec2.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec2.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec2.AnyType} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.Vec2.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec2.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+  }
+  return resultVec;
+};
+
+
 /**
  * Returns true if the components of vec0 are equal to the components of vec1.
  *
@@ -370,6 +434,5 @@ goog.vec.Vec2.lerp = function(vec0, vec1, f, resultVec) {
  * @return {boolean} True if the vectors are equal, false otherwise.
  */
 goog.vec.Vec2.equals = function(vec0, vec1) {
-  return vec0.length == vec1.length &&
-      vec0[0] == vec1[0] && vec0[1] == vec1[1];
+  return vec0.length == vec1.length && vec0[0] == vec1[0] && vec0[1] == vec1[1];
 };

+ 11 - 0
public/lib/closure/vec/vec2_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Vec2Test');</script>
+<title>Closure Unit Tests - goog.vec.Vec2Test</title></head><body></body></html>

+ 239 - 0
public/lib/closure/vec/vec2_test.js

@@ -0,0 +1,239 @@
+// Copyright 2012 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Vec2Test');
+goog.setTestOnly('goog.vec.Vec2Test');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Vec2');
+
+function testConstructor() {
+  var v = goog.vec.Vec2.createFloat32();
+  assertElementsEquals(0, v[0]);
+  assertEquals(0, v[1]);
+
+  assertElementsEquals([0, 0], goog.vec.Vec2.createFloat32());
+
+  goog.vec.Vec2.setFromValues(v, 1, 2);
+  assertElementsEquals([1, 2], v);
+
+  var w = goog.vec.Vec2.createFloat64();
+  assertElementsEquals(0, w[0]);
+  assertEquals(0, w[1]);
+
+  assertElementsEquals([0, 0], goog.vec.Vec2.createFloat64());
+
+  goog.vec.Vec2.setFromValues(w, 1, 2);
+  assertElementsEquals([1, 2], w);
+}
+
+
+function testSet() {
+  var v = goog.vec.Vec2.createFloat32();
+  goog.vec.Vec2.setFromValues(v, 1, 2);
+  assertElementsEquals([1, 2], v);
+
+  goog.vec.Vec2.setFromArray(v, [4, 5]);
+  assertElementsEquals([4, 5], v);
+
+  var w = goog.vec.Vec2.createFloat32();
+  goog.vec.Vec2.setFromValues(w, 1, 2);
+  assertElementsEquals([1, 2], w);
+
+  goog.vec.Vec2.setFromArray(w, [4, 5]);
+  assertElementsEquals([4, 5], w);
+}
+
+function testAdd() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  var v1 = goog.vec.Vec2.createFloat32FromArray([4, 5]);
+  var v2 = goog.vec.Vec2.cloneFloat32(v0);
+
+  goog.vec.Vec2.add(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([5, 7], v2);
+
+  goog.vec.Vec2.add(goog.vec.Vec2.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([6, 9], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  var v1 = goog.vec.Vec2.createFloat32FromArray([4, 5]);
+  var v2 = goog.vec.Vec2.cloneFloat32(v0);
+
+  goog.vec.Vec2.subtract(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([-3, -3], v2);
+
+  goog.vec.Vec2.setFromValues(v2, 0, 0, 0);
+  goog.vec.Vec2.subtract(v1, v0, v2);
+  assertElementsEquals([3, 3], v2);
+
+  v2 = goog.vec.Vec2.cloneFloat32(v0);
+  goog.vec.Vec2.subtract(v2, v1, v2);
+  assertElementsEquals([-3, -3], v2);
+
+  goog.vec.Vec2.subtract(goog.vec.Vec2.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([2, 1], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  var v1 = goog.vec.Vec2.createFloat32();
+
+  goog.vec.Vec2.negate(v0, v1);
+  assertElementsEquals([-1, -2], v1);
+  assertElementsEquals([1, 2], v0);
+
+  goog.vec.Vec2.negate(v0, v0);
+  assertElementsEquals([-1, -2], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(-1, -2);
+  var v1 = goog.vec.Vec2.createFloat32();
+
+  goog.vec.Vec2.abs(v0, v1);
+  assertElementsEquals([1, 2], v1);
+  assertElementsEquals([-1, -2], v0);
+
+  goog.vec.Vec2.abs(v0, v0);
+  assertElementsEquals([1, 2], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  var v1 = goog.vec.Vec2.createFloat32();
+
+  goog.vec.Vec2.scale(v0, 4, v1);
+  assertElementsEquals([4, 8], v1);
+  assertElementsEquals([1, 2], v0);
+
+  goog.vec.Vec2.setFromArray(v1, v0);
+  goog.vec.Vec2.scale(v1, 5, v1);
+  assertElementsEquals([5, 10], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  assertEquals(5, goog.vec.Vec2.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  assertEquals(Math.sqrt(5), goog.vec.Vec2.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([2, 3]);
+  var v1 = goog.vec.Vec2.createFloat32();
+  var v2 = goog.vec.Vec2.createFloat32();
+  goog.vec.Vec2.scale(
+      v0, 1 / goog.vec.Vec2.magnitude(v0), v2);
+
+  goog.vec.Vec2.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3], v0);
+
+  goog.vec.Vec2.setFromArray(v1, v0);
+  goog.vec.Vec2.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.Vec2.createFloat32FromArray([1, 2]);
+  var v1 = goog.vec.Vec2.createFloat32FromArray([4, 5]);
+  assertEquals(14, goog.vec.Vec2.dot(v0, v1));
+  assertEquals(14, goog.vec.Vec2.dot(v1, v0));
+}
+
+function testDistanceSquared() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  var v1 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  assertEquals(0, goog.vec.Vec2.distanceSquared(v0, v1));
+  goog.vec.Vec2.setFromValues(v0, 1, 2);
+  goog.vec.Vec2.setFromValues(v1, -1, -2);
+  assertEquals(20, goog.vec.Vec2.distanceSquared(v0, v1));
+}
+
+function testDistance() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  var v1 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  assertEquals(0, goog.vec.Vec2.distance(v0, v1));
+  goog.vec.Vec2.setFromValues(v0, 2, 3);
+  goog.vec.Vec2.setFromValues(v1, -2, 0);
+  assertEquals(5, goog.vec.Vec2.distance(v0, v1));
+}
+
+function testDirection() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  var v1 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  var dirVec = goog.vec.Vec2.createFloat32FromValues(4, 5);
+  goog.vec.Vec2.direction(v0, v1, dirVec);
+  assertElementsEquals([0, 0], dirVec);
+  goog.vec.Vec2.setFromValues(v0, 0, 0);
+  goog.vec.Vec2.setFromValues(v1, 1, 0);
+  goog.vec.Vec2.direction(v0, v1, dirVec);
+  assertElementsEquals([1, 0], dirVec);
+  goog.vec.Vec2.setFromValues(v0, 1, 1);
+  goog.vec.Vec2.setFromValues(v1, 0, 0);
+  goog.vec.Vec2.direction(v0, v1, dirVec);
+  assertElementsRoughlyEqual(
+      [-0.707106781, -0.707106781],
+      dirVec, goog.vec.EPSILON);
+}
+
+function testLerp() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  var v1 = goog.vec.Vec2.createFloat32FromValues(10, 20);
+  var v2 = goog.vec.Vec2.cloneFloat32(v0);
+
+  goog.vec.Vec2.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2], v2);
+  goog.vec.Vec2.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20], v2);
+  goog.vec.Vec2.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(10, 20);
+  var v1 = goog.vec.Vec2.createFloat32FromValues(5, 25);
+  var v2 = goog.vec.Vec2.createFloat32();
+
+  goog.vec.Vec2.max(v0, v1, v2);
+  assertElementsEquals([10, 25], v2);
+  goog.vec.Vec2.max(v1, v0, v1);
+  assertElementsEquals([10, 25], v1);
+  goog.vec.Vec2.max(v2, 20, v2);
+  assertElementsEquals([20, 25], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(10, 20);
+  var v1 = goog.vec.Vec2.createFloat32FromValues(5, 25);
+  var v2 = goog.vec.Vec2.createFloat32();
+
+  goog.vec.Vec2.min(v0, v1, v2);
+  assertElementsEquals([5, 20], v2);
+  goog.vec.Vec2.min(v1, v0, v1);
+  assertElementsEquals([5, 20], v1);
+  goog.vec.Vec2.min(v2, 10, v2);
+  assertElementsEquals([5, 10], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.Vec2.createFloat32FromValues(1, 2);
+  var v1 = goog.vec.Vec2.cloneFloat32(v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 4;
+  assertFalse(goog.vec.Vec2.equals(v0, v1));
+
+  v1 = goog.vec.Vec2.cloneFloat32(v0);
+  v1[1] = 4;
+  assertFalse(goog.vec.Vec2.equals(v0, v1));
+}

+ 463 - 0
public/lib/closure/vec/vec2d.js

@@ -0,0 +1,463 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec2f.js by running:            //
+//   swap_type.sh vec2d.js > vec2f.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 2 element double (64bit)
+ * vectors.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+
+goog.provide('goog.vec.vec2d');
+goog.provide('goog.vec.vec2d.Type');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+
+/** @typedef {goog.vec.Float64} */ goog.vec.vec2d.Type;
+
+
+/**
+ * Creates a vec2d with all elements initialized to zero.
+ *
+ * @return {!goog.vec.vec2d.Type} The new vec2d.
+ */
+goog.vec.vec2d.create = function() {
+  return new Float64Array(2);
+};
+
+
+/**
+ * Creates a new vec2d initialized with the value from the given array.
+ *
+ * @param {!Array<number>} vec The source 2 element array.
+ * @return {!goog.vec.vec2d.Type} The new vec2d.
+ */
+goog.vec.vec2d.createFromArray = function(vec) {
+  var newVec = goog.vec.vec2d.create();
+  goog.vec.vec2d.setFromArray(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Creates a new vec2d initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @return {!goog.vec.vec2d.Type} The new vector.
+ */
+goog.vec.vec2d.createFromValues = function(v0, v1) {
+  var vec = goog.vec.vec2d.create();
+  goog.vec.vec2d.setFromValues(vec, v0, v1);
+  return vec;
+};
+
+
+/**
+ * Creates a clone of the given vec2d.
+ *
+ * @param {!goog.vec.vec2d.Type} vec The source vec2d.
+ * @return {!goog.vec.vec2d.Type} The new cloned vec2d.
+ */
+goog.vec.vec2d.clone = function(vec) {
+  var newVec = goog.vec.vec2d.create();
+  goog.vec.vec2d.setFromVec2d(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {!goog.vec.vec2d.Type} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @return {!goog.vec.vec2d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.setFromValues = function(vec, v0, v1) {
+  vec[0] = v0;
+  vec[1] = v1;
+  return vec;
+};
+
+
+/**
+ * Initializes vec2d vec from vec2d src.
+ *
+ * @param {!goog.vec.vec2d.Type} vec The destination vector.
+ * @param {!goog.vec.vec2d.Type} src The source vector.
+ * @return {!goog.vec.vec2d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.setFromVec2d = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  return vec;
+};
+
+
+/**
+ * Initializes vec2d vec from vec2f src (typed as a Float32Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.vec2d.Type} vec The destination vector.
+ * @param {Float32Array} src The source vector.
+ * @return {!goog.vec.vec2d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.setFromVec2f = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  return vec;
+};
+
+
+/**
+ * Initializes vec2d vec from Array src.
+ *
+ * @param {!goog.vec.vec2d.Type} vec The destination vector.
+ * @param {Array<number>} src The source vector.
+ * @return {!goog.vec.vec2d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.setFromArray = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The first addend.
+ * @param {!goog.vec.vec2d.Type} vec1 The second addend.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.add = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] + vec1[0];
+  resultVec[1] = vec0[1] + vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The minuend.
+ * @param {!goog.vec.vec2d.Type} vec1 The subtrahend.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.subtract = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] - vec1[0];
+  resultVec[1] = vec0[1] - vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with the matching element of vec0
+ * storing the products into resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2d.Type} vec1 The second vector.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.componentMultiply = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] * vec1[0];
+  resultVec[1] = vec0[1] * vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Divides each component of vec0 with the matching element of vec0
+ * storing the divisor into resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2d.Type} vec1 The second vector.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.componentDivide = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] / vec1[0];
+  resultVec[1] = vec0[1] / vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The vector to negate.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.negate = function(vec0, resultVec) {
+  resultVec[0] = -vec0[0];
+  resultVec[1] = -vec0[1];
+  return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.scale = function(vec0, scalar, resultVec) {
+  resultVec[0] = vec0[0] * scalar;
+  resultVec[1] = vec0[1] * scalar;
+  return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec2d.magnitudeSquared = function(vec0) {
+  var x = vec0[0], y = vec0[1];
+  return x * x + y * y;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec2d.magnitude = function(vec0) {
+  var x = vec0[0], y = vec0[1];
+  return Math.sqrt(x * x + y * y);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The vector to normalize.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.normalize = function(vec0, resultVec) {
+  var x = vec0[0], y = vec0[1];
+  var ilen = 1 / Math.sqrt(x * x + y * y);
+  resultVec[0] = x * ilen;
+  resultVec[1] = y * ilen;
+  return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors vec0 and vec1.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2d.Type} vec1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.vec2d.dot = function(vec0, vec1) {
+  return vec0[0] * vec1[0] + vec0[1] * vec1[1];
+};
+
+
+/**
+ * Returns the squared distance between two points.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 First point.
+ * @param {!goog.vec.vec2d.Type} vec1 Second point.
+ * @return {number} The squared distance between the points.
+ */
+goog.vec.vec2d.distanceSquared = function(vec0, vec1) {
+  var x = vec0[0] - vec1[0];
+  var y = vec0[1] - vec1[1];
+  return x * x + y * y;
+};
+
+
+/**
+ * Returns the distance between two points.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 First point.
+ * @param {!goog.vec.vec2d.Type} vec1 Second point.
+ * @return {number} The distance between the points.
+ */
+goog.vec.vec2d.distance = function(vec0, vec1) {
+  return Math.sqrt(goog.vec.vec2d.distanceSquared(vec0, vec1));
+};
+
+
+/**
+ * Returns a unit vector pointing from one point to another.
+ * If the input points are equal then the result will be all zeros.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 Origin point.
+ * @param {!goog.vec.vec2d.Type} vec1 Target point.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or vec1).
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.direction = function(vec0, vec1, resultVec) {
+  var x = vec1[0] - vec0[0];
+  var y = vec1[1] - vec0[1];
+  var d = Math.sqrt(x * x + y * y);
+  if (d) {
+    d = 1 / d;
+    resultVec[0] = x * d;
+    resultVec[1] = y * d;
+  } else {
+    resultVec[0] = resultVec[1] = 0;
+  }
+  return resultVec;
+};
+
+
+/**
+ * Linearly interpolate from vec0 to vec1 according to f. The value of f should
+ * be in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2d.Type} vec1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or vec1).
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.lerp = function(vec0, vec1, f, resultVec) {
+  var x = vec0[0], y = vec0[1];
+  resultVec[0] = (vec1[0] - x) * f + x;
+  resultVec[1] = (vec1[1] - y) * f + y;
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec2d.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec2d.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec2d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec2d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2d.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Returns true if the components of vec0 are equal to the components of vec1.
+ *
+ * @param {!goog.vec.vec2d.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2d.Type} vec1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.vec2d.equals = function(vec0, vec1) {
+  return vec0.length == vec1.length && vec0[0] == vec1[0] && vec0[1] == vec1[1];
+};

+ 11 - 0
public/lib/closure/vec/vec2d_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.vec2dTest');</script>
+<title>Closure Unit Tests - goog.vec.vec2dTest</title></head><body></body></html>

+ 282 - 0
public/lib/closure/vec/vec2d_test.js

@@ -0,0 +1,282 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec2f_test.js by running:       //
+//   swap_type.sh vec2d_test.js > vec2f_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.vec2dTest');
+goog.setTestOnly('goog.vec.vec2dTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.vec2d');
+
+function testCreate() {
+  var v = goog.vec.vec2d.create();
+  assertElementsEquals([0, 0], v);
+}
+
+function testCreateFromArray() {
+  var v = goog.vec.vec2d.createFromArray([1, 2]);
+  assertElementsEquals([1, 2], v);
+}
+
+function testCreateFromValues() {
+  var v = goog.vec.vec2d.createFromValues(1, 2);
+  assertElementsEquals([1, 2], v);
+}
+
+function testClone() {
+  var v0 = goog.vec.vec2d.createFromValues(1, 2);
+  var v1 = goog.vec.vec2d.clone(v0);
+  assertElementsEquals([1, 2], v1);
+}
+
+function testSet() {
+  var v = goog.vec.vec2d.create();
+  goog.vec.vec2d.setFromValues(v, 1, 2);
+  assertElementsEquals([1, 2], v);
+
+  goog.vec.vec2d.setFromArray(v, [4, 5]);
+  assertElementsEquals([4, 5], v);
+
+  var w = goog.vec.vec2d.create();
+  goog.vec.vec2d.setFromValues(w, 1, 2);
+  assertElementsEquals([1, 2], w);
+
+  goog.vec.vec2d.setFromArray(w, [4, 5]);
+  assertElementsEquals([4, 5], w);
+}
+
+function testAdd() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [4, 5]);
+  var v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+
+  goog.vec.vec2d.add(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([5, 7], v2);
+
+  goog.vec.vec2d.add(goog.vec.vec2d.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([6, 9], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [4, 5]);
+  var v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+
+  goog.vec.vec2d.subtract(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([-3, -3], v2);
+
+  goog.vec.vec2d.setFromValues(v2, 0, 0);
+  goog.vec.vec2d.subtract(v1, v0, v2);
+  assertElementsEquals([3, 3], v2);
+
+  v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+  goog.vec.vec2d.subtract(v2, v1, v2);
+  assertElementsEquals([-3, -3], v2);
+
+  goog.vec.vec2d.subtract(goog.vec.vec2d.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([2, 1], v2);
+}
+
+function testMultiply() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [4, 5]);
+  var v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+
+  goog.vec.vec2d.componentMultiply(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([4, 10], v2);
+
+  goog.vec.vec2d.componentMultiply(goog.vec.vec2d.componentMultiply(v0, v1, v2), v0, v2);
+  assertElementsEquals([4, 20], v2);
+}
+
+function testDivide() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [4, 5]);
+  var v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+
+  goog.vec.vec2d.componentDivide(v2, v1, v2);
+  assertElementsRoughlyEqual([1, 2], v0, 10e-5);
+  assertElementsRoughlyEqual([4, 5], v1, 10e-5);
+  assertElementsRoughlyEqual([.25, .4], v2, 10e-5);
+
+  goog.vec.vec2d.setFromValues(v2, 0, 0);
+  goog.vec.vec2d.componentDivide(v1, v0, v2);
+  assertElementsRoughlyEqual([4, 2.5], v2, 10e-5);
+
+  v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+  goog.vec.vec2d.componentDivide(v2, v1, v2);
+  assertElementsRoughlyEqual([.25, .4], v2, 10e-5);
+
+  goog.vec.vec2d.componentDivide(goog.vec.vec2d.componentDivide(v1, v0, v2), v0, v2);
+  assertElementsRoughlyEqual([4, 1.25], v2, 10e-5);
+}
+
+function testNegate() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.create();
+
+  goog.vec.vec2d.negate(v0, v1);
+  assertElementsEquals([-1, -2], v1);
+  assertElementsEquals([1, 2], v0);
+
+  goog.vec.vec2d.negate(v0, v0);
+  assertElementsEquals([-1, -2], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [-1, -2]);
+  var v1 = goog.vec.vec2d.create();
+
+  goog.vec.vec2d.abs(v0, v1);
+  assertElementsEquals([1, 2], v1);
+  assertElementsEquals([-1, -2], v0);
+
+  goog.vec.vec2d.abs(v0, v0);
+  assertElementsEquals([1, 2], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.create();
+
+  goog.vec.vec2d.scale(v0, 4, v1);
+  assertElementsEquals([4, 8], v1);
+  assertElementsEquals([1, 2], v0);
+
+  goog.vec.vec2d.setFromArray(v1, v0);
+  goog.vec.vec2d.scale(v1, 5, v1);
+  assertElementsEquals([5, 10], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  assertEquals(5, goog.vec.vec2d.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  assertEquals(Math.sqrt(5), goog.vec.vec2d.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [2, 3]);
+  var v1 = goog.vec.vec2d.create();
+  var v2 = goog.vec.vec2d.create();
+  goog.vec.vec2d.scale(
+      v0, 1 / goog.vec.vec2d.magnitude(v0), v2);
+
+  goog.vec.vec2d.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3], v0);
+
+  goog.vec.vec2d.setFromArray(v1, v0);
+  goog.vec.vec2d.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [1, 2]);
+  var v1 = goog.vec.vec2d.setFromArray(goog.vec.vec2d.create(), [4, 5]);
+  assertEquals(14, goog.vec.vec2d.dot(v0, v1));
+  assertEquals(14, goog.vec.vec2d.dot(v1, v0));
+}
+
+function testDistanceSquared() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  var v1 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  assertEquals(0, goog.vec.vec2d.distanceSquared(v0, v1));
+  goog.vec.vec2d.setFromValues(v0, 1, 2);
+  goog.vec.vec2d.setFromValues(v1, -1, -2);
+  assertEquals(20, goog.vec.vec2d.distanceSquared(v0, v1));
+}
+
+function testDistance() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  var v1 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  assertEquals(0, goog.vec.vec2d.distance(v0, v1));
+  goog.vec.vec2d.setFromValues(v0, 2, 3);
+  goog.vec.vec2d.setFromValues(v1, -2, 0);
+  assertEquals(5, goog.vec.vec2d.distance(v0, v1));
+}
+
+function testDirection() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  var v1 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  var dirVec = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 4, 5);
+  goog.vec.vec2d.direction(v0, v1, dirVec);
+  assertElementsEquals([0, 0], dirVec);
+  goog.vec.vec2d.setFromValues(v0, 0, 0);
+  goog.vec.vec2d.setFromValues(v1, 1, 0);
+  goog.vec.vec2d.direction(v0, v1, dirVec);
+  assertElementsEquals([1, 0], dirVec);
+  goog.vec.vec2d.setFromValues(v0, 1, 1);
+  goog.vec.vec2d.setFromValues(v1, 0, 0);
+  goog.vec.vec2d.direction(v0, v1, dirVec);
+  assertElementsRoughlyEqual(
+      [-0.707106781, -0.707106781],
+      dirVec, goog.vec.EPSILON);
+}
+
+function testLerp() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  var v1 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 10, 20);
+  var v2 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+
+  goog.vec.vec2d.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2], v2);
+  goog.vec.vec2d.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20], v2);
+  goog.vec.vec2d.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 10, 20);
+  var v1 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 5, 25);
+  var v2 = goog.vec.vec2d.create();
+
+  goog.vec.vec2d.max(v0, v1, v2);
+  assertElementsEquals([10, 25], v2);
+  goog.vec.vec2d.max(v1, v0, v1);
+  assertElementsEquals([10, 25], v1);
+  goog.vec.vec2d.max(v2, 20, v2);
+  assertElementsEquals([20, 25], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 10, 20);
+  var v1 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 5, 25);
+  var v2 = goog.vec.vec2d.create();
+
+  goog.vec.vec2d.min(v0, v1, v2);
+  assertElementsEquals([5, 20], v2);
+  goog.vec.vec2d.min(v1, v0, v1);
+  assertElementsEquals([5, 20], v1);
+  goog.vec.vec2d.min(v2, 10, v2);
+  assertElementsEquals([5, 10], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.vec2d.setFromValues(goog.vec.vec2d.create(), 1, 2);
+  var v1 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 4;
+  assertFalse(goog.vec.vec2d.equals(v0, v1));
+
+  v1 = goog.vec.vec2d.setFromVec2d(goog.vec.vec2d.create(), v0);
+  v1[1] = 4;
+  assertFalse(goog.vec.vec2d.equals(v0, v1));
+}

+ 463 - 0
public/lib/closure/vec/vec2f.js

@@ -0,0 +1,463 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec2d.js by running:            //
+//   swap_type.sh vec2f.js > vec2d.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 2 element float (32bit)
+ * vectors.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+
+goog.provide('goog.vec.vec2f');
+goog.provide('goog.vec.vec2f.Type');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+
+/** @typedef {goog.vec.Float32} */ goog.vec.vec2f.Type;
+
+
+/**
+ * Creates a vec2f with all elements initialized to zero.
+ *
+ * @return {!goog.vec.vec2f.Type} The new vec2f.
+ */
+goog.vec.vec2f.create = function() {
+  return new Float32Array(2);
+};
+
+
+/**
+ * Creates a new vec2f initialized with the value from the given array.
+ *
+ * @param {!Array<number>} vec The source 2 element array.
+ * @return {!goog.vec.vec2f.Type} The new vec2f.
+ */
+goog.vec.vec2f.createFromArray = function(vec) {
+  var newVec = goog.vec.vec2f.create();
+  goog.vec.vec2f.setFromArray(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Creates a new vec2f initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @return {!goog.vec.vec2f.Type} The new vector.
+ */
+goog.vec.vec2f.createFromValues = function(v0, v1) {
+  var vec = goog.vec.vec2f.create();
+  goog.vec.vec2f.setFromValues(vec, v0, v1);
+  return vec;
+};
+
+
+/**
+ * Creates a clone of the given vec2f.
+ *
+ * @param {!goog.vec.vec2f.Type} vec The source vec2f.
+ * @return {!goog.vec.vec2f.Type} The new cloned vec2f.
+ */
+goog.vec.vec2f.clone = function(vec) {
+  var newVec = goog.vec.vec2f.create();
+  goog.vec.vec2f.setFromVec2f(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {!goog.vec.vec2f.Type} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @return {!goog.vec.vec2f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.setFromValues = function(vec, v0, v1) {
+  vec[0] = v0;
+  vec[1] = v1;
+  return vec;
+};
+
+
+/**
+ * Initializes vec2f vec from vec2f src.
+ *
+ * @param {!goog.vec.vec2f.Type} vec The destination vector.
+ * @param {!goog.vec.vec2f.Type} src The source vector.
+ * @return {!goog.vec.vec2f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.setFromVec2f = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  return vec;
+};
+
+
+/**
+ * Initializes vec2f vec from vec2d src (typed as a Float64Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.vec2f.Type} vec The destination vector.
+ * @param {Float64Array} src The source vector.
+ * @return {!goog.vec.vec2f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.setFromVec2d = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  return vec;
+};
+
+
+/**
+ * Initializes vec2f vec from Array src.
+ *
+ * @param {!goog.vec.vec2f.Type} vec The destination vector.
+ * @param {Array<number>} src The source vector.
+ * @return {!goog.vec.vec2f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.setFromArray = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The first addend.
+ * @param {!goog.vec.vec2f.Type} vec1 The second addend.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.add = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] + vec1[0];
+  resultVec[1] = vec0[1] + vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The minuend.
+ * @param {!goog.vec.vec2f.Type} vec1 The subtrahend.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.subtract = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] - vec1[0];
+  resultVec[1] = vec0[1] - vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with the matching element of vec0
+ * storing the products into resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2f.Type} vec1 The second vector.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.componentMultiply = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] * vec1[0];
+  resultVec[1] = vec0[1] * vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Divides each component of vec0 with the matching element of vec0
+ * storing the divisor into resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2f.Type} vec1 The second vector.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.componentDivide = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] / vec1[0];
+  resultVec[1] = vec0[1] / vec1[1];
+  return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The vector to negate.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.negate = function(vec0, resultVec) {
+  resultVec[0] = -vec0[0];
+  resultVec[1] = -vec0[1];
+  return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.scale = function(vec0, scalar, resultVec) {
+  resultVec[0] = vec0[0] * scalar;
+  resultVec[1] = vec0[1] * scalar;
+  return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec2f.magnitudeSquared = function(vec0) {
+  var x = vec0[0], y = vec0[1];
+  return x * x + y * y;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec2f.magnitude = function(vec0) {
+  var x = vec0[0], y = vec0[1];
+  return Math.sqrt(x * x + y * y);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The vector to normalize.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.normalize = function(vec0, resultVec) {
+  var x = vec0[0], y = vec0[1];
+  var ilen = 1 / Math.sqrt(x * x + y * y);
+  resultVec[0] = x * ilen;
+  resultVec[1] = y * ilen;
+  return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors vec0 and vec1.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2f.Type} vec1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.vec2f.dot = function(vec0, vec1) {
+  return vec0[0] * vec1[0] + vec0[1] * vec1[1];
+};
+
+
+/**
+ * Returns the squared distance between two points.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 First point.
+ * @param {!goog.vec.vec2f.Type} vec1 Second point.
+ * @return {number} The squared distance between the points.
+ */
+goog.vec.vec2f.distanceSquared = function(vec0, vec1) {
+  var x = vec0[0] - vec1[0];
+  var y = vec0[1] - vec1[1];
+  return x * x + y * y;
+};
+
+
+/**
+ * Returns the distance between two points.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 First point.
+ * @param {!goog.vec.vec2f.Type} vec1 Second point.
+ * @return {number} The distance between the points.
+ */
+goog.vec.vec2f.distance = function(vec0, vec1) {
+  return Math.sqrt(goog.vec.vec2f.distanceSquared(vec0, vec1));
+};
+
+
+/**
+ * Returns a unit vector pointing from one point to another.
+ * If the input points are equal then the result will be all zeros.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 Origin point.
+ * @param {!goog.vec.vec2f.Type} vec1 Target point.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or vec1).
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.direction = function(vec0, vec1, resultVec) {
+  var x = vec1[0] - vec0[0];
+  var y = vec1[1] - vec0[1];
+  var d = Math.sqrt(x * x + y * y);
+  if (d) {
+    d = 1 / d;
+    resultVec[0] = x * d;
+    resultVec[1] = y * d;
+  } else {
+    resultVec[0] = resultVec[1] = 0;
+  }
+  return resultVec;
+};
+
+
+/**
+ * Linearly interpolate from vec0 to vec1 according to f. The value of f should
+ * be in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2f.Type} vec1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or vec1).
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.lerp = function(vec0, vec1, f, resultVec) {
+  var x = vec0[0], y = vec0[1];
+  resultVec[0] = (vec1[0] - x) * f + x;
+  resultVec[1] = (vec1[1] - y) * f + y;
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec2f.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec2f.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec2f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec2f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec2f.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Returns true if the components of vec0 are equal to the components of vec1.
+ *
+ * @param {!goog.vec.vec2f.Type} vec0 The first vector.
+ * @param {!goog.vec.vec2f.Type} vec1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.vec2f.equals = function(vec0, vec1) {
+  return vec0.length == vec1.length && vec0[0] == vec1[0] && vec0[1] == vec1[1];
+};

+ 11 - 0
public/lib/closure/vec/vec2f_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.vec2fTest');</script>
+<title>Closure Unit Tests - goog.vec.vec2fTest</title></head><body></body></html>

+ 282 - 0
public/lib/closure/vec/vec2f_test.js

@@ -0,0 +1,282 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec2d_test.js by running:       //
+//   swap_type.sh vec2f_test.js > vec2d_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.vec2fTest');
+goog.setTestOnly('goog.vec.vec2fTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.vec2f');
+
+function testCreate() {
+  var v = goog.vec.vec2f.create();
+  assertElementsEquals([0, 0], v);
+}
+
+function testCreateFromArray() {
+  var v = goog.vec.vec2f.createFromArray([1, 2]);
+  assertElementsEquals([1, 2], v);
+}
+
+function testCreateFromValues() {
+  var v = goog.vec.vec2f.createFromValues(1, 2);
+  assertElementsEquals([1, 2], v);
+}
+
+function testClone() {
+  var v0 = goog.vec.vec2f.createFromValues(1, 2);
+  var v1 = goog.vec.vec2f.clone(v0);
+  assertElementsEquals([1, 2], v1);
+}
+
+function testSet() {
+  var v = goog.vec.vec2f.create();
+  goog.vec.vec2f.setFromValues(v, 1, 2);
+  assertElementsEquals([1, 2], v);
+
+  goog.vec.vec2f.setFromArray(v, [4, 5]);
+  assertElementsEquals([4, 5], v);
+
+  var w = goog.vec.vec2f.create();
+  goog.vec.vec2f.setFromValues(w, 1, 2);
+  assertElementsEquals([1, 2], w);
+
+  goog.vec.vec2f.setFromArray(w, [4, 5]);
+  assertElementsEquals([4, 5], w);
+}
+
+function testAdd() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [4, 5]);
+  var v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+
+  goog.vec.vec2f.add(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([5, 7], v2);
+
+  goog.vec.vec2f.add(goog.vec.vec2f.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([6, 9], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [4, 5]);
+  var v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+
+  goog.vec.vec2f.subtract(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([-3, -3], v2);
+
+  goog.vec.vec2f.setFromValues(v2, 0, 0);
+  goog.vec.vec2f.subtract(v1, v0, v2);
+  assertElementsEquals([3, 3], v2);
+
+  v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+  goog.vec.vec2f.subtract(v2, v1, v2);
+  assertElementsEquals([-3, -3], v2);
+
+  goog.vec.vec2f.subtract(goog.vec.vec2f.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([2, 1], v2);
+}
+
+function testMultiply() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [4, 5]);
+  var v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+
+  goog.vec.vec2f.componentMultiply(v2, v1, v2);
+  assertElementsEquals([1, 2], v0);
+  assertElementsEquals([4, 5], v1);
+  assertElementsEquals([4, 10], v2);
+
+  goog.vec.vec2f.componentMultiply(goog.vec.vec2f.componentMultiply(v0, v1, v2), v0, v2);
+  assertElementsEquals([4, 20], v2);
+}
+
+function testDivide() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [4, 5]);
+  var v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+
+  goog.vec.vec2f.componentDivide(v2, v1, v2);
+  assertElementsRoughlyEqual([1, 2], v0, 10e-5);
+  assertElementsRoughlyEqual([4, 5], v1, 10e-5);
+  assertElementsRoughlyEqual([.25, .4], v2, 10e-5);
+
+  goog.vec.vec2f.setFromValues(v2, 0, 0);
+  goog.vec.vec2f.componentDivide(v1, v0, v2);
+  assertElementsRoughlyEqual([4, 2.5], v2, 10e-5);
+
+  v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+  goog.vec.vec2f.componentDivide(v2, v1, v2);
+  assertElementsRoughlyEqual([.25, .4], v2, 10e-5);
+
+  goog.vec.vec2f.componentDivide(goog.vec.vec2f.componentDivide(v1, v0, v2), v0, v2);
+  assertElementsRoughlyEqual([4, 1.25], v2, 10e-5);
+}
+
+function testNegate() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.create();
+
+  goog.vec.vec2f.negate(v0, v1);
+  assertElementsEquals([-1, -2], v1);
+  assertElementsEquals([1, 2], v0);
+
+  goog.vec.vec2f.negate(v0, v0);
+  assertElementsEquals([-1, -2], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [-1, -2]);
+  var v1 = goog.vec.vec2f.create();
+
+  goog.vec.vec2f.abs(v0, v1);
+  assertElementsEquals([1, 2], v1);
+  assertElementsEquals([-1, -2], v0);
+
+  goog.vec.vec2f.abs(v0, v0);
+  assertElementsEquals([1, 2], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.create();
+
+  goog.vec.vec2f.scale(v0, 4, v1);
+  assertElementsEquals([4, 8], v1);
+  assertElementsEquals([1, 2], v0);
+
+  goog.vec.vec2f.setFromArray(v1, v0);
+  goog.vec.vec2f.scale(v1, 5, v1);
+  assertElementsEquals([5, 10], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  assertEquals(5, goog.vec.vec2f.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  assertEquals(Math.sqrt(5), goog.vec.vec2f.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [2, 3]);
+  var v1 = goog.vec.vec2f.create();
+  var v2 = goog.vec.vec2f.create();
+  goog.vec.vec2f.scale(
+      v0, 1 / goog.vec.vec2f.magnitude(v0), v2);
+
+  goog.vec.vec2f.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3], v0);
+
+  goog.vec.vec2f.setFromArray(v1, v0);
+  goog.vec.vec2f.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [1, 2]);
+  var v1 = goog.vec.vec2f.setFromArray(goog.vec.vec2f.create(), [4, 5]);
+  assertEquals(14, goog.vec.vec2f.dot(v0, v1));
+  assertEquals(14, goog.vec.vec2f.dot(v1, v0));
+}
+
+function testDistanceSquared() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  var v1 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  assertEquals(0, goog.vec.vec2f.distanceSquared(v0, v1));
+  goog.vec.vec2f.setFromValues(v0, 1, 2);
+  goog.vec.vec2f.setFromValues(v1, -1, -2);
+  assertEquals(20, goog.vec.vec2f.distanceSquared(v0, v1));
+}
+
+function testDistance() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  var v1 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  assertEquals(0, goog.vec.vec2f.distance(v0, v1));
+  goog.vec.vec2f.setFromValues(v0, 2, 3);
+  goog.vec.vec2f.setFromValues(v1, -2, 0);
+  assertEquals(5, goog.vec.vec2f.distance(v0, v1));
+}
+
+function testDirection() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  var v1 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  var dirVec = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 4, 5);
+  goog.vec.vec2f.direction(v0, v1, dirVec);
+  assertElementsEquals([0, 0], dirVec);
+  goog.vec.vec2f.setFromValues(v0, 0, 0);
+  goog.vec.vec2f.setFromValues(v1, 1, 0);
+  goog.vec.vec2f.direction(v0, v1, dirVec);
+  assertElementsEquals([1, 0], dirVec);
+  goog.vec.vec2f.setFromValues(v0, 1, 1);
+  goog.vec.vec2f.setFromValues(v1, 0, 0);
+  goog.vec.vec2f.direction(v0, v1, dirVec);
+  assertElementsRoughlyEqual(
+      [-0.707106781, -0.707106781],
+      dirVec, goog.vec.EPSILON);
+}
+
+function testLerp() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  var v1 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 10, 20);
+  var v2 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+
+  goog.vec.vec2f.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2], v2);
+  goog.vec.vec2f.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20], v2);
+  goog.vec.vec2f.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 10, 20);
+  var v1 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 5, 25);
+  var v2 = goog.vec.vec2f.create();
+
+  goog.vec.vec2f.max(v0, v1, v2);
+  assertElementsEquals([10, 25], v2);
+  goog.vec.vec2f.max(v1, v0, v1);
+  assertElementsEquals([10, 25], v1);
+  goog.vec.vec2f.max(v2, 20, v2);
+  assertElementsEquals([20, 25], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 10, 20);
+  var v1 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 5, 25);
+  var v2 = goog.vec.vec2f.create();
+
+  goog.vec.vec2f.min(v0, v1, v2);
+  assertElementsEquals([5, 20], v2);
+  goog.vec.vec2f.min(v1, v0, v1);
+  assertElementsEquals([5, 20], v1);
+  goog.vec.vec2f.min(v2, 10, v2);
+  assertElementsEquals([5, 10], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.vec2f.setFromValues(goog.vec.vec2f.create(), 1, 2);
+  var v1 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 4;
+  assertFalse(goog.vec.vec2f.equals(v0, v1));
+
+  v1 = goog.vec.vec2f.setFromVec2f(goog.vec.vec2f.create(), v0);
+  v1[1] = 4;
+  assertFalse(goog.vec.vec2f.equals(v0, v1));
+}

+ 82 - 13
public/lib/closure/vec/vec3.js

@@ -24,6 +24,7 @@
  */
 goog.provide('goog.vec.Vec3');
 
+/** @suppress {extraRequire} */
 goog.require('goog.vec');
 
 /** @typedef {goog.vec.Float32} */ goog.vec.Vec3.Float32;
@@ -80,7 +81,7 @@ goog.vec.Vec3.create = function() {
 
 
 /**
- * Creates a new 3 element FLoat32 vector initialized with the value from the
+ * Creates a new 3 element Float32 vector initialized with the value from the
  * given array.
  *
  * @param {goog.vec.Vec3.AnyType} vec The source 3 element array.
@@ -207,7 +208,7 @@ goog.vec.Vec3.clone = function(vec) {
  * @param {number} v0 The value for element at index 0.
  * @param {number} v1 The value for element at index 1.
  * @param {number} v2 The value for element at index 2.
- * @return {!goog.vec.Vec3.AnyType} return vec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return vec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.setFromValues = function(vec, v0, v1, v2) {
@@ -224,7 +225,7 @@ goog.vec.Vec3.setFromValues = function(vec, v0, v1, v2) {
  * @param {goog.vec.Vec3.AnyType} vec The vector to receive the
  *     values.
  * @param {goog.vec.Vec3.AnyType} values The array of values.
- * @return {!goog.vec.Vec3.AnyType} return vec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return vec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.setFromArray = function(vec, values) {
@@ -243,7 +244,7 @@ goog.vec.Vec3.setFromArray = function(vec, values) {
  * @param {goog.vec.Vec3.AnyType} vec1 The second addend.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to
  *     receive the result. May be vec0 or vec1.
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.add = function(vec0, vec1, resultVec) {
@@ -262,7 +263,7 @@ goog.vec.Vec3.add = function(vec0, vec1, resultVec) {
  * @param {goog.vec.Vec3.AnyType} vec1 The subtrahend.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to
  *     receive the result. May be vec0 or vec1.
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.subtract = function(vec0, vec1, resultVec) {
@@ -279,7 +280,7 @@ goog.vec.Vec3.subtract = function(vec0, vec1, resultVec) {
  * @param {goog.vec.Vec3.AnyType} vec0 The vector to negate.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to
  *     receive the result. May be vec0.
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.negate = function(vec0, resultVec) {
@@ -290,6 +291,24 @@ goog.vec.Vec3.negate = function(vec0, resultVec) {
 };
 
 
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec3.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  resultVec[2] = Math.abs(vec0[2]);
+  return resultVec;
+};
+
+
 /**
  * Multiplies each component of vec0 with scalar storing the product into
  * resultVec.
@@ -298,7 +317,7 @@ goog.vec.Vec3.negate = function(vec0, resultVec) {
  * @param {number} scalar The value to multiply with each component of vec0.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to
  *     receive the result. May be vec0.
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.scale = function(vec0, scalar, resultVec) {
@@ -339,7 +358,7 @@ goog.vec.Vec3.magnitude = function(vec0) {
  * @param {goog.vec.Vec3.AnyType} vec0 The vector to normalize.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to
  *     receive the result. May be vec0.
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.normalize = function(vec0, resultVec) {
@@ -371,7 +390,7 @@ goog.vec.Vec3.dot = function(v0, v1) {
  * @param {goog.vec.Vec3.AnyType} v1 The second vector.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
  *     results. May be either v0 or v1.
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.cross = function(v0, v1, resultVec) {
@@ -419,7 +438,7 @@ goog.vec.Vec3.distance = function(vec0, vec1) {
  * @param {goog.vec.Vec3.AnyType} vec1 Target point.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
  *     results (may be vec0 or vec1).
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.direction = function(vec0, vec1, resultVec) {
@@ -448,7 +467,7 @@ goog.vec.Vec3.direction = function(vec0, vec1, resultVec) {
  * @param {number} f The interpolation factor.
  * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
  *     results (may be v0 or v1).
- * @return {!goog.vec.Vec3.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec3.lerp = function(v0, v1, f, resultVec) {
@@ -460,6 +479,56 @@ goog.vec.Vec3.lerp = function(v0, v1, f, resultVec) {
 };
 
 
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec3.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec3.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+    resultVec[2] = Math.max(vec0[2], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+    resultVec[2] = Math.max(vec0[2], limit[2]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {goog.vec.Vec3.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec3.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec3.AnyType} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.Vec3.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec3.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+    resultVec[2] = Math.min(vec0[2], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+    resultVec[2] = Math.min(vec0[2], limit[2]);
+  }
+  return resultVec;
+};
+
+
 /**
  * Returns true if the components of v0 are equal to the components of v1.
  *
@@ -468,6 +537,6 @@ goog.vec.Vec3.lerp = function(v0, v1, f, resultVec) {
  * @return {boolean} True if the vectors are equal, false otherwise.
  */
 goog.vec.Vec3.equals = function(v0, v1) {
-  return v0.length == v1.length &&
-      v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2];
+  return v0.length == v1.length && v0[0] == v1[0] && v0[1] == v1[1] &&
+      v0[2] == v1[2];
 };

+ 11 - 0
public/lib/closure/vec/vec3_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Vec3Test');</script>
+<title>Closure Unit Tests - goog.vec.Vec3Test</title></head><body></body></html>

+ 281 - 0
public/lib/closure/vec/vec3_test.js

@@ -0,0 +1,281 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Vec3Test');
+goog.setTestOnly('goog.vec.Vec3Test');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Vec3');
+
+function testDeprecatedConstructor() {
+  var v = goog.vec.Vec3.create();
+  assertElementsEquals(0, v[0]);
+  assertEquals(0, v[1]);
+  assertEquals(0, v[2]);
+
+  assertElementsEquals([0, 0, 0], goog.vec.Vec3.create());
+
+  assertElementsEquals([1, 2, 3], goog.vec.Vec3.createFromValues(1, 2, 3));
+
+  assertElementsEquals([1, 2, 3], goog.vec.Vec3.createFromArray([1, 2, 3]));
+
+  v = goog.vec.Vec3.createFromValues(1, 2, 3);
+  assertElementsEquals([1, 2, 3], goog.vec.Vec3.clone(v));
+}
+
+function testConstructor() {
+  var v = goog.vec.Vec3.createFloat32();
+  assertElementsEquals(0, v[0]);
+  assertEquals(0, v[1]);
+  assertEquals(0, v[2]);
+
+  assertElementsEquals([0, 0, 0], goog.vec.Vec3.createFloat32());
+
+  goog.vec.Vec3.setFromValues(v, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], v);
+
+  var w = goog.vec.Vec3.createFloat64();
+  assertElementsEquals(0, w[0]);
+  assertEquals(0, w[1]);
+  assertEquals(0, w[2]);
+
+  assertElementsEquals([0, 0, 0], goog.vec.Vec3.createFloat64());
+
+  goog.vec.Vec3.setFromValues(w, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], w);
+}
+
+
+function testSet() {
+  var v = goog.vec.Vec3.createFloat32();
+  goog.vec.Vec3.setFromValues(v, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], v);
+
+  goog.vec.Vec3.setFromArray(v, [4, 5, 6]);
+  assertElementsEquals([4, 5, 6], v);
+
+  var w = goog.vec.Vec3.createFloat32();
+  goog.vec.Vec3.setFromValues(w, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], w);
+
+  goog.vec.Vec3.setFromArray(w, [4, 5, 6]);
+  assertElementsEquals([4, 5, 6], w);
+}
+
+function testAdd() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  var v1 = goog.vec.Vec3.createFloat32FromArray([4, 5, 6]);
+  var v2 = goog.vec.Vec3.cloneFloat32(v0);
+
+  goog.vec.Vec3.add(v2, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([5, 7, 9], v2);
+
+  goog.vec.Vec3.add(goog.vec.Vec3.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([6, 9, 12], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  var v1 = goog.vec.Vec3.createFloat32FromArray([4, 5, 6]);
+  var v2 = goog.vec.Vec3.cloneFloat32(v0);
+
+  goog.vec.Vec3.subtract(v2, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([-3, -3, -3], v2);
+
+  goog.vec.Vec3.setFromValues(v2, 0, 0, 0);
+  goog.vec.Vec3.subtract(v1, v0, v2);
+  assertElementsEquals([3, 3, 3], v2);
+
+  v2 = goog.vec.Vec3.cloneFloat32(v0);
+  goog.vec.Vec3.subtract(v2, v1, v2);
+  assertElementsEquals([-3, -3, -3], v2);
+
+  goog.vec.Vec3.subtract(goog.vec.Vec3.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([2, 1, 0], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  var v1 = goog.vec.Vec3.createFloat32();
+
+  goog.vec.Vec3.negate(v0, v1);
+  assertElementsEquals([-1, -2, -3], v1);
+  assertElementsEquals([1, 2, 3], v0);
+
+  goog.vec.Vec3.negate(v0, v0);
+  assertElementsEquals([-1, -2, -3], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(-1, -2, -3);
+  var v1 = goog.vec.Vec3.createFloat32();
+
+  goog.vec.Vec3.abs(v0, v1);
+  assertElementsEquals([1, 2, 3], v1);
+  assertElementsEquals([-1, -2, -3], v0);
+
+  goog.vec.Vec3.abs(v0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  var v1 = goog.vec.Vec3.createFloat32();
+
+  goog.vec.Vec3.scale(v0, 4, v1);
+  assertElementsEquals([4, 8, 12], v1);
+  assertElementsEquals([1, 2, 3], v0);
+
+  goog.vec.Vec3.setFromArray(v1, v0);
+  goog.vec.Vec3.scale(v1, 5, v1);
+  assertElementsEquals([5, 10, 15], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  assertEquals(14, goog.vec.Vec3.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  assertEquals(Math.sqrt(14), goog.vec.Vec3.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([2, 3, 4]);
+  var v1 = goog.vec.Vec3.create();
+  var v2 = goog.vec.Vec3.create();
+  goog.vec.Vec3.scale(
+      v0, 1 / goog.vec.Vec3.magnitude(v0), v2);
+
+  goog.vec.Vec3.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3, 4], v0);
+
+  goog.vec.Vec3.setFromArray(v1, v0);
+  goog.vec.Vec3.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  var v1 = goog.vec.Vec3.createFloat32FromArray([4, 5, 6]);
+  assertEquals(32, goog.vec.Vec3.dot(v0, v1));
+  assertEquals(32, goog.vec.Vec3.dot(v1, v0));
+}
+
+function testCross() {
+  var v0 = goog.vec.Vec3.createFloat32FromArray([1, 2, 3]);
+  var v1 = goog.vec.Vec3.createFloat32FromArray([4, 5, 6]);
+  var crossVec = goog.vec.Vec3.create();
+
+  goog.vec.Vec3.cross(v0, v1, crossVec);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([-3, 6, -3], crossVec);
+
+  goog.vec.Vec3.setFromArray(crossVec, v1);
+  goog.vec.Vec3.cross(crossVec, v0, crossVec);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([3, -6, 3], crossVec);
+
+  goog.vec.Vec3.cross(v0, v0, v0);
+  assertElementsEquals([0, 0, 0], v0);
+}
+
+function testDistanceSquared() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  var v1 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  assertEquals(0, goog.vec.Vec3.distanceSquared(v0, v1));
+  goog.vec.Vec3.setFromValues(v0, 1, 2, 3);
+  goog.vec.Vec3.setFromValues(v1, -1, -2, -1);
+  assertEquals(36, goog.vec.Vec3.distanceSquared(v0, v1));
+}
+
+function testDistance() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  var v1 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  assertEquals(0, goog.vec.Vec3.distance(v0, v1));
+  goog.vec.Vec3.setFromValues(v0, 1, 2, 3);
+  goog.vec.Vec3.setFromValues(v1, -1, -2, -1);
+  assertEquals(6, goog.vec.Vec3.distance(v0, v1));
+}
+
+function testDirection() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  var v1 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  var dirVec = goog.vec.Vec3.createFloat32FromValues(4, 5, 6);
+  goog.vec.Vec3.direction(v0, v1, dirVec);
+  assertElementsEquals([0, 0, 0], dirVec);
+  goog.vec.Vec3.setFromValues(v0, 0, 0, 0);
+  goog.vec.Vec3.setFromValues(v1, 1, 0, 0);
+  goog.vec.Vec3.direction(v0, v1, dirVec);
+  assertElementsEquals([1, 0, 0], dirVec);
+  goog.vec.Vec3.setFromValues(v0, 1, 1, 1);
+  goog.vec.Vec3.setFromValues(v1, 0, 0, 0);
+  goog.vec.Vec3.direction(v0, v1, dirVec);
+  assertElementsRoughlyEqual(
+      [-0.5773502588272095, -0.5773502588272095, -0.5773502588272095],
+      dirVec, goog.vec.EPSILON);
+}
+
+function testLerp() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  var v1 = goog.vec.Vec3.createFloat32FromValues(10, 20, 30);
+  var v2 = goog.vec.Vec3.cloneFloat32(v0);
+
+  goog.vec.Vec3.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2, 3], v2);
+  goog.vec.Vec3.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20, 30], v2);
+  goog.vec.Vec3.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11, 16.5], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(10, 20, 30);
+  var v1 = goog.vec.Vec3.createFloat32FromValues(5, 25, 35);
+  var v2 = goog.vec.Vec3.createFloat32();
+
+  goog.vec.Vec3.max(v0, v1, v2);
+  assertElementsEquals([10, 25, 35], v2);
+  goog.vec.Vec3.max(v1, v0, v1);
+  assertElementsEquals([10, 25, 35], v1);
+  goog.vec.Vec3.max(v2, 20, v2);
+  assertElementsEquals([20, 25, 35], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(10, 20, 30);
+  var v1 = goog.vec.Vec3.createFloat32FromValues(5, 25, 35);
+  var v2 = goog.vec.Vec3.createFloat32();
+
+  goog.vec.Vec3.min(v0, v1, v2);
+  assertElementsEquals([5, 20, 30], v2);
+  goog.vec.Vec3.min(v1, v0, v1);
+  assertElementsEquals([5, 20, 30], v1);
+  goog.vec.Vec3.min(v2, 20, v2);
+  assertElementsEquals([5, 20, 20], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.Vec3.createFloat32FromValues(1, 2, 3);
+  var v1 = goog.vec.Vec3.cloneFloat32(v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 4;
+  assertFalse(goog.vec.Vec3.equals(v0, v1));
+
+  v1 = goog.vec.Vec3.cloneFloat32(v0);
+  v1[1] = 4;
+  assertFalse(goog.vec.Vec3.equals(v0, v1));
+
+  v1 = goog.vec.Vec3.cloneFloat32(v0);
+  v1[2] = 4;
+  assertFalse(goog.vec.Vec3.equals(v0, v1));
+}

+ 548 - 0
public/lib/closure/vec/vec3d.js

@@ -0,0 +1,548 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec3f.js by running:            //
+//   swap_type.sh vec3d.js > vec3f.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 3 element double (64bit)
+ * vectors.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.vec3d');
+goog.provide('goog.vec.vec3d.Type');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {!goog.vec.Float64} */ goog.vec.vec3d.Type;
+
+
+/**
+ * Creates a vec3d with all elements initialized to zero.
+ *
+ * @return {!goog.vec.vec3d.Type} The new vec3d.
+ */
+goog.vec.vec3d.create = function() {
+  return new Float64Array(3);
+};
+
+
+/**
+ * Creates a new vec3d initialized with the value from the given array.
+ *
+ * @param {!Array<number>} vec The source 3 element array.
+ * @return {!goog.vec.vec3d.Type} The new vec3d.
+ */
+goog.vec.vec3d.createFromArray = function(vec) {
+  var newVec = goog.vec.vec3d.create();
+  goog.vec.vec3d.setFromArray(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Creates a new vec3d initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.vec3d.Type} The new vector.
+ */
+goog.vec.vec3d.createFromValues = function(v0, v1, v2) {
+  var vec = goog.vec.vec3d.create();
+  goog.vec.vec3d.setFromValues(vec, v0, v1, v2);
+  return vec;
+};
+
+
+/**
+ * Creates a clone of the given vec3d.
+ *
+ * @param {!goog.vec.vec3d.Type} vec The source vec3d.
+ * @return {!goog.vec.vec3d.Type} The new cloned vec3d.
+ */
+goog.vec.vec3d.clone = function(vec) {
+  var newVec = goog.vec.vec3d.create();
+  goog.vec.vec3d.setFromVec3d(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {!goog.vec.vec3d.Type} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.vec3d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.setFromValues = function(vec, v0, v1, v2) {
+  vec[0] = v0;
+  vec[1] = v1;
+  vec[2] = v2;
+  return vec;
+};
+
+
+/**
+ * Initializes vec3d vec from vec3d src.
+ *
+ * @param {!goog.vec.vec3d.Type} vec The destination vector.
+ * @param {!goog.vec.vec3d.Type} src The source vector.
+ * @return {!goog.vec.vec3d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.setFromVec3d = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  return vec;
+};
+
+
+/**
+ * Initializes vec3d vec from vec3f src (typed as a Float32Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.vec3d.Type} vec The destination vector.
+ * @param {Float32Array} src The source vector.
+ * @return {!goog.vec.vec3d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.setFromVec3f = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  return vec;
+};
+
+
+/**
+ * Initializes vec3d vec from Array src.
+ *
+ * @param {!goog.vec.vec3d.Type} vec The destination vector.
+ * @param {Array<number>} src The source vector.
+ * @return {!goog.vec.vec3d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.setFromArray = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The first addend.
+ * @param {!goog.vec.vec3d.Type} vec1 The second addend.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.add = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] + vec1[0];
+  resultVec[1] = vec0[1] + vec1[1];
+  resultVec[2] = vec0[2] + vec1[2];
+  return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The minuend.
+ * @param {!goog.vec.vec3d.Type} vec1 The subtrahend.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.subtract = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] - vec1[0];
+  resultVec[1] = vec0[1] - vec1[1];
+  resultVec[2] = vec0[2] - vec1[2];
+  return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The vector to negate.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.negate = function(vec0, resultVec) {
+  resultVec[0] = -vec0[0];
+  resultVec[1] = -vec0[1];
+  resultVec[2] = -vec0[2];
+  return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  resultVec[2] = Math.abs(vec0[2]);
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.scale = function(vec0, scalar, resultVec) {
+  resultVec[0] = vec0[0] * scalar;
+  resultVec[1] = vec0[1] * scalar;
+  resultVec[2] = vec0[2] * scalar;
+  return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec3d.magnitudeSquared = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2];
+  return x * x + y * y + z * z;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec3d.magnitude = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2];
+  return Math.sqrt(x * x + y * y + z * z);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The vector to normalize.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.normalize = function(vec0, resultVec) {
+  var x = vec0[0], y = vec0[1], z = vec0[2];
+  var ilen = 1 / Math.sqrt(x * x + y * y + z * z);
+  resultVec[0] = x * ilen;
+  resultVec[1] = y * ilen;
+  resultVec[2] = z * ilen;
+  return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors v0 and v1.
+ *
+ * @param {!goog.vec.vec3d.Type} v0 The first vector.
+ * @param {!goog.vec.vec3d.Type} v1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.vec3d.dot = function(v0, v1) {
+  return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
+};
+
+
+/**
+ * Computes the vector (cross) product of v0 and v1 storing the result into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} v0 The first vector.
+ * @param {!goog.vec.vec3d.Type} v1 The second vector.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the
+ *     results. May be either v0 or v1.
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.cross = function(v0, v1, resultVec) {
+  var x0 = v0[0], y0 = v0[1], z0 = v0[2];
+  var x1 = v1[0], y1 = v1[1], z1 = v1[2];
+  resultVec[0] = y0 * z1 - z0 * y1;
+  resultVec[1] = z0 * x1 - x0 * z1;
+  resultVec[2] = x0 * y1 - y0 * x1;
+  return resultVec;
+};
+
+
+/**
+ * Returns the squared distance between two points.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 First point.
+ * @param {!goog.vec.vec3d.Type} vec1 Second point.
+ * @return {number} The squared distance between the points.
+ */
+goog.vec.vec3d.distanceSquared = function(vec0, vec1) {
+  var x = vec0[0] - vec1[0];
+  var y = vec0[1] - vec1[1];
+  var z = vec0[2] - vec1[2];
+  return x * x + y * y + z * z;
+};
+
+
+/**
+ * Returns the distance between two points.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 First point.
+ * @param {!goog.vec.vec3d.Type} vec1 Second point.
+ * @return {number} The distance between the points.
+ */
+goog.vec.vec3d.distance = function(vec0, vec1) {
+  return Math.sqrt(goog.vec.vec3d.distanceSquared(vec0, vec1));
+};
+
+
+/**
+ * Returns a unit vector pointing from one point to another.
+ * If the input points are equal then the result will be all zeros.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 Origin point.
+ * @param {!goog.vec.vec3d.Type} vec1 Target point.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or vec1).
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.direction = function(vec0, vec1, resultVec) {
+  var x = vec1[0] - vec0[0];
+  var y = vec1[1] - vec0[1];
+  var z = vec1[2] - vec0[2];
+  var d = Math.sqrt(x * x + y * y + z * z);
+  if (d) {
+    d = 1 / d;
+    resultVec[0] = x * d;
+    resultVec[1] = y * d;
+    resultVec[2] = z * d;
+  } else {
+    resultVec[0] = resultVec[1] = resultVec[2] = 0;
+  }
+  return resultVec;
+};
+
+
+/**
+ * Linearly interpolate from vec0 to v1 according to f. The value of f should be
+ * in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {!goog.vec.vec3d.Type} v0 The first vector.
+ * @param {!goog.vec.vec3d.Type} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the
+ *     results (may be v0 or v1).
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.lerp = function(v0, v1, f, resultVec) {
+  var x = v0[0], y = v0[1], z = v0[2];
+  resultVec[0] = (v1[0] - x) * f + x;
+  resultVec[1] = (v1[1] - y) * f + y;
+  resultVec[2] = (v1[2] - z) * f + z;
+  return resultVec;
+};
+
+
+/**
+ * Perform a spherical linear interpolation from v0 to v1 according to f. The
+ * value of f should be in the range [0..1] otherwise the results are undefined.
+ *
+ * Slerp is normally used to interpolate quaternions, but there is a geometric
+ * formula for interpolating vectors directly, see "Geometric Slerp" in:
+ * https://en.wikipedia.org/wiki/Slerp.
+ *
+ * This interpolates the vectors' directions via slerp, but linearly
+ * interpolates the vectors' magnitudes.
+ *
+ * Results are undefined if v0 or v1 are of zero magnitude.
+ *
+ * @param {!goog.vec.vec3d.Type} v0 The first vector.
+ * @param {!goog.vec.vec3d.Type} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the
+ *     results (may be v0 or v1).
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.slerp = function(v0, v1, f, resultVec) {
+  var v0Magnitude = goog.vec.vec3d.magnitude(v0);
+  var v1Magnitude = goog.vec.vec3d.magnitude(v1);
+
+  var cosAngle = goog.vec.vec3d.dot(v0, v1) / (v0Magnitude * v1Magnitude);
+
+  // If v0 and v1 are almost the same direction, fall back on a straight lerp.
+  if (cosAngle > 1 - goog.vec.EPSILON) {
+    return goog.vec.vec3d.lerp(v0, v1, f, resultVec);
+  }
+
+  var angle = 0;
+  var sinAngle = 0;
+
+  // If v0 and v1 are opposite directions, pick an arbitrary 'mid' vector that
+  // is perpendicular to both, and slerp from v0 -> mid -> v1.
+  if (cosAngle < -1 + goog.vec.EPSILON) {
+    var mid = goog.vec.vec3d.create();
+    var magnitudeFactor = (v0Magnitude + v1Magnitude) / 2;
+    if (v0[0]) {  // v0 not parallel to [0,0,1].
+      magnitudeFactor /= Math.sqrt(v0[0] * v0[0] + v0[1] + v0[1]);
+      mid[0] = -v0[1] * magnitudeFactor;
+      mid[1] = v0[0] * magnitudeFactor;
+      mid[2] = 0;
+    } else {  // v0 not parallel to [1,0,0].
+      magnitudeFactor /= Math.sqrt(v0[2] * v0[2] + v0[1] + v0[1]);
+      mid[0] = 0;
+      mid[1] = -v0[2] * magnitudeFactor;
+      mid[2] = v0[1] * magnitudeFactor;
+    }
+
+    // Depending on f, slerp between either v0 and mid, or mid and v1.
+    if (f <= 0.5) {
+      v1Magnitude = v0Magnitude;
+      v1 = mid;
+      f *= 2;
+    } else {
+      v0 = mid;
+      f = 2 * f - 1;
+    }
+
+    angle = Math.PI / 2;
+    cosAngle = 0;
+    sinAngle = 1;
+  } else {
+    angle = Math.acos(cosAngle);
+    sinAngle = Math.sqrt(1 - cosAngle * cosAngle);
+  }
+
+  var coeff0 = (Math.sin((1 - f) * angle) / sinAngle) / v0Magnitude;
+  var coeff1 = (Math.sin(f * angle) / sinAngle) / v1Magnitude;
+  var magnitude = (1 - f) * v0Magnitude + f * v1Magnitude;
+
+  resultVec[0] = (v0[0] * coeff0 + v1[0] * coeff1) * magnitude;
+  resultVec[1] = (v0[1] * coeff0 + v1[1] * coeff1) * magnitude;
+  resultVec[2] = (v0[2] * coeff0 + v1[2] * coeff1) * magnitude;
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec3d.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+    resultVec[2] = Math.max(vec0[2], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+    resultVec[2] = Math.max(vec0[2], limit[2]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {!goog.vec.vec3d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec3d.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec3d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec3d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3d.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+    resultVec[2] = Math.min(vec0[2], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+    resultVec[2] = Math.min(vec0[2], limit[2]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Returns true if the components of v0 are equal to the components of v1.
+ *
+ * @param {!goog.vec.vec3d.Type} v0 The first vector.
+ * @param {!goog.vec.vec3d.Type} v1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.vec3d.equals = function(v0, v1) {
+  return v0.length == v1.length && v0[0] == v1[0] && v0[1] == v1[1] &&
+      v0[2] == v1[2];
+};

+ 11 - 0
public/lib/closure/vec/vec3d_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.vec3dTest');</script>
+<title>Closure Unit Tests - goog.vec.vec3dTest</title></head><body></body></html>

+ 386 - 0
public/lib/closure/vec/vec3d_test.js

@@ -0,0 +1,386 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec3f_test.js by running:       //
+//   swap_type.sh vec3d_test.js > vec3f_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.vec3dTest');
+goog.setTestOnly('goog.vec.vec3dTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.vec3d');
+
+function testCreate() {
+  var v = goog.vec.vec3d.create();
+  assertElementsEquals([0, 0, 0], v);
+}
+
+function testCreateFromArray() {
+  var v = goog.vec.vec3d.createFromArray([1, 2, 3]);
+  assertElementsEquals([1, 2, 3], v);
+}
+
+function testCreateFromValues() {
+  var v = goog.vec.vec3d.createFromValues(1, 2, 3);
+  assertElementsEquals([1, 2, 3], v);
+}
+
+function testClone() {
+  var v0 = goog.vec.vec3d.createFromValues(1, 2, 3);
+  var v1 = goog.vec.vec3d.clone(v0);
+  assertElementsEquals([1, 2, 3], v1);
+}
+
+function testSet() {
+  var v = goog.vec.vec3d.create();
+  goog.vec.vec3d.setFromValues(v, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], v);
+
+  goog.vec.vec3d.setFromArray(v, [4, 5, 6]);
+  assertElementsEquals([4, 5, 6], v);
+
+  var w = goog.vec.vec3d.create();
+  goog.vec.vec3d.setFromValues(w, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], w);
+
+  goog.vec.vec3d.setFromArray(w, [4, 5, 6]);
+  assertElementsEquals([4, 5, 6], w);
+}
+
+function testAdd() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [4, 5, 6]);
+  var v2 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+
+  goog.vec.vec3d.add(v2, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([5, 7, 9], v2);
+
+  goog.vec.vec3d.add(goog.vec.vec3d.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([6, 9, 12], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [4, 5, 6]);
+  var v2 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+
+  goog.vec.vec3d.subtract(v2, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([-3, -3, -3], v2);
+
+  goog.vec.vec3d.setFromValues(v2, 0, 0, 0);
+  goog.vec.vec3d.subtract(v1, v0, v2);
+  assertElementsEquals([3, 3, 3], v2);
+
+  v2 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+  goog.vec.vec3d.subtract(v2, v1, v2);
+  assertElementsEquals([-3, -3, -3], v2);
+
+  goog.vec.vec3d.subtract(goog.vec.vec3d.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([2, 1, 0], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3d.create();
+
+  goog.vec.vec3d.negate(v0, v1);
+  assertElementsEquals([-1, -2, -3], v1);
+  assertElementsEquals([1, 2, 3], v0);
+
+  goog.vec.vec3d.negate(v0, v0);
+  assertElementsEquals([-1, -2, -3], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [-1, -2, -3]);
+  var v1 = goog.vec.vec3d.create();
+
+  goog.vec.vec3d.abs(v0, v1);
+  assertElementsEquals([1, 2, 3], v1);
+  assertElementsEquals([-1, -2, -3], v0);
+
+  goog.vec.vec3d.abs(v0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3d.create();
+
+  goog.vec.vec3d.scale(v0, 4, v1);
+  assertElementsEquals([4, 8, 12], v1);
+  assertElementsEquals([1, 2, 3], v0);
+
+  goog.vec.vec3d.setFromArray(v1, v0);
+  goog.vec.vec3d.scale(v1, 5, v1);
+  assertElementsEquals([5, 10, 15], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  assertEquals(14, goog.vec.vec3d.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  assertEquals(Math.sqrt(14), goog.vec.vec3d.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [2, 3, 4]);
+  var v1 = goog.vec.vec3d.create();
+  var v2 = goog.vec.vec3d.create();
+  goog.vec.vec3d.scale(
+      v0, 1 / goog.vec.vec3d.magnitude(v0), v2);
+
+  goog.vec.vec3d.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3, 4], v0);
+
+  goog.vec.vec3d.setFromArray(v1, v0);
+  goog.vec.vec3d.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [4, 5, 6]);
+  assertEquals(32, goog.vec.vec3d.dot(v0, v1));
+  assertEquals(32, goog.vec.vec3d.dot(v1, v0));
+}
+
+function testCross() {
+  var v0 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3d.setFromArray(goog.vec.vec3d.create(), [4, 5, 6]);
+  var crossVec = goog.vec.vec3d.create();
+
+  goog.vec.vec3d.cross(v0, v1, crossVec);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([-3, 6, -3], crossVec);
+
+  goog.vec.vec3d.setFromArray(crossVec, v1);
+  goog.vec.vec3d.cross(crossVec, v0, crossVec);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([3, -6, 3], crossVec);
+
+  goog.vec.vec3d.cross(v0, v0, v0);
+  assertElementsEquals([0, 0, 0], v0);
+}
+
+function testDistanceSquared() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  assertEquals(0, goog.vec.vec3d.distanceSquared(v0, v1));
+  goog.vec.vec3d.setFromValues(v0, 1, 2, 3);
+  goog.vec.vec3d.setFromValues(v1, -1, -2, -1);
+  assertEquals(36, goog.vec.vec3d.distanceSquared(v0, v1));
+}
+
+function testDistance() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  assertEquals(0, goog.vec.vec3d.distance(v0, v1));
+  goog.vec.vec3d.setFromValues(v0, 1, 2, 3);
+  goog.vec.vec3d.setFromValues(v1, -1, -2, -1);
+  assertEquals(6, goog.vec.vec3d.distance(v0, v1));
+}
+
+function testDirection() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  var dirVec = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 4, 5, 6);
+  goog.vec.vec3d.direction(v0, v1, dirVec);
+  assertElementsEquals([0, 0, 0], dirVec);
+  goog.vec.vec3d.setFromValues(v0, 0, 0, 0);
+  goog.vec.vec3d.setFromValues(v1, 1, 0, 0);
+  goog.vec.vec3d.direction(v0, v1, dirVec);
+  assertElementsEquals([1, 0, 0], dirVec);
+  goog.vec.vec3d.setFromValues(v0, 1, 1, 1);
+  goog.vec.vec3d.setFromValues(v1, 0, 0, 0);
+  goog.vec.vec3d.direction(v0, v1, dirVec);
+  assertElementsRoughlyEqual(
+      [-0.5773502588272095, -0.5773502588272095, -0.5773502588272095],
+      dirVec, goog.vec.EPSILON);
+}
+
+function testLerp() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 10, 20, 30);
+  var v2 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+
+  goog.vec.vec3d.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2, 3], v2);
+  goog.vec.vec3d.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20, 30], v2);
+  goog.vec.vec3d.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11, 16.5], v2);
+}
+
+function testSlerp() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 0, 0, 1);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 0, 0);
+  var v2 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), -1, 0, 0);
+  var v3 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), -5, 0, 0);
+  var v4 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 0, 0, -1);
+  var v5 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+
+  // Try f == 0 and f == 1.
+  goog.vec.vec3d.slerp(v5, v1, 0, v5);
+  assertElementsEquals([0, 0, 1], v5);
+  goog.vec.vec3d.slerp(v5, v1, 1, v5);
+  assertElementsEquals([1, 0, 0], v5);
+
+  // Try slerp between perpendicular vectors.
+  goog.vec.vec3d.slerp(v0, v1, .5, v5);
+  assertElementsRoughlyEqual(
+      [ Math.sqrt(2) / 2, 0, Math.sqrt(2) / 2 ], v5,
+      goog.vec.EPSILON);
+
+  // Try slerp between vectors of opposite directions (+Z and -Z).
+  v5 = goog.vec.vec3d.slerp(v0, v4, .5, v5);
+  // Axis of rotation is arbitrary, but result should be 90 degrees from both
+  // v0 and v4 when f = 0.5.
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3d.dot(v5, v0)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3d.dot(v5, v4)),
+                      goog.vec.EPSILON);
+
+  // f == 0.25, result should be 45-degrees to v0, and 135 to v4.
+  v5 = goog.vec.vec3d.slerp(v0, v4, .25, v5);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3d.dot(v5, v0)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3d.dot(v5, v4)),
+                      goog.vec.EPSILON);
+
+  // f = 0.75, result should be 135-degrees to v0, and 45 to v4.
+  v5 = goog.vec.vec3d.slerp(v0, v4, .75, v5);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3d.dot(v5, v0)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3d.dot(v5, v4)),
+                      goog.vec.EPSILON);
+
+  // Same as above, but on opposite directions of the X-axis.
+  v5 = goog.vec.vec3d.slerp(v1, v2, .5, v5);
+  // Axis of rotation is arbitrary, but result should be 90 degrees from both
+  // v1 and v2 when f = 0.5.
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3d.dot(v5, v1)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3d.dot(v5, v2)),
+                      goog.vec.EPSILON);
+
+  // f == 0.25, result should be 45-degrees to v1, and 135 to v2.
+  v5 = goog.vec.vec3d.slerp(v1, v2, .25, v5);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3d.dot(v5, v1)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3d.dot(v5, v2)),
+                      goog.vec.EPSILON);
+
+  // f = 0.75, result should be 135-degrees to v1, and 45 to v2.
+  v5 = goog.vec.vec3d.slerp(v1, v2, .75, v5);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3d.dot(v5, v1)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3d.dot(v5, v2)),
+                      goog.vec.EPSILON);
+
+  // Try vectors that aren't perpendicular or opposite/same direction.
+  var v6 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(),
+                                        Math.sqrt(2) / 2, Math.sqrt(2) / 2, 0);
+  goog.vec.vec3d.slerp(v1, v6, .9, v5);
+
+  // The vectors are 45 degrees apart, for f == 0.9, results should be 1/10 of
+  // that from v6 and 9/10 of that away from v1.
+  assertRoughlyEquals((Math.PI / 4) * 0.9, Math.acos(goog.vec.vec3d.dot(v1, v5)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals((Math.PI / 4) * 0.1, Math.acos(goog.vec.vec3d.dot(v6, v5)),
+                      goog.vec.EPSILON);
+
+  // Between vectors of the same direction, where one is non-unit-length
+  // (magnitudes should be lerp-ed).
+  goog.vec.vec3d.slerp(v2, v3, .5, v5);
+  assertElementsEquals([-3, 0, 0], v5);
+
+  // Between perpendicular vectors, where one is non-unit length.
+  goog.vec.vec3d.slerp(v0, v3, .5, v5);
+  assertRoughlyEquals(3, goog.vec.vec3d.magnitude(v5), goog.vec.EPSILON);
+  assertElementsRoughlyEqual(
+      [ -3 * (Math.sqrt(2) / 2), 0, 3 * (Math.sqrt(2) / 2) ], v5,
+      goog.vec.EPSILON);
+
+  // And vectors of opposite directions, where one is non-unit length.
+  goog.vec.vec3d.slerp(v1, v3, .5, v5);
+  // Axis of rotation is arbitrary, but result should be 90 degrees from both
+  // v1 and v3.
+  assertRoughlyEquals(
+      Math.PI / 2,
+      Math.acos(goog.vec.vec3d.dot(v5, v1) / (goog.vec.vec3d.magnitude(v5) *
+                                              goog.vec.vec3d.magnitude(v1))),
+      goog.vec.EPSILON);
+  assertRoughlyEquals(
+      Math.PI / 2,
+      Math.acos(goog.vec.vec3d.dot(v5, v3) / (goog.vec.vec3d.magnitude(v3) *
+                                              goog.vec.vec3d.magnitude(v5))),
+      goog.vec.EPSILON);
+  // Magnitude should be linearly interpolated.
+  assertRoughlyEquals(3, goog.vec.vec3d.magnitude(v5), goog.vec.EPSILON);
+
+  // Try a case where the vectors are the same direction (the same vector in
+  // this case), but where numerical error results in a dot product
+  // slightly greater than 1. Taking the acos of this would result in NaN.
+  var v7 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 0.009, 0.147,
+                                        0.989);
+  goog.vec.vec3d.slerp(v7, v7, .25, v5);
+  assertElementsRoughlyEqual([ v7[0], v7[1], v7[2] ], v5, goog.vec.EPSILON);
+}
+
+function testMax() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 10, 20, 30);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 5, 25, 35);
+  var v2 = goog.vec.vec3d.create();
+
+  goog.vec.vec3d.max(v0, v1, v2);
+  assertElementsEquals([10, 25, 35], v2);
+  goog.vec.vec3d.max(v1, v0, v1);
+  assertElementsEquals([10, 25, 35], v1);
+  goog.vec.vec3d.max(v2, 20, v2);
+  assertElementsEquals([20, 25, 35], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 10, 20, 30);
+  var v1 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 5, 25, 35);
+  var v2 = goog.vec.vec3d.create();
+
+  goog.vec.vec3d.min(v0, v1, v2);
+  assertElementsEquals([5, 20, 30], v2);
+  goog.vec.vec3d.min(v1, v0, v1);
+  assertElementsEquals([5, 20, 30], v1);
+  goog.vec.vec3d.min(v2, 20, v2);
+  assertElementsEquals([5, 20, 20], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.vec3d.setFromValues(goog.vec.vec3d.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 4;
+  assertFalse(goog.vec.vec3d.equals(v0, v1));
+
+  v1 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+  v1[1] = 4;
+  assertFalse(goog.vec.vec3d.equals(v0, v1));
+
+  v1 = goog.vec.vec3d.setFromVec3d(goog.vec.vec3d.create(), v0);
+  v1[2] = 4;
+  assertFalse(goog.vec.vec3d.equals(v0, v1));
+}

+ 548 - 0
public/lib/closure/vec/vec3f.js

@@ -0,0 +1,548 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec3d.js by running:            //
+//   swap_type.sh vec3f.js > vec3d.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 3 element float (32bit)
+ * vectors.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.vec3f');
+goog.provide('goog.vec.vec3f.Type');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {goog.vec.Float32} */ goog.vec.vec3f.Type;
+
+
+/**
+ * Creates a vec3f with all elements initialized to zero.
+ *
+ * @return {!goog.vec.vec3f.Type} The new vec3f.
+ */
+goog.vec.vec3f.create = function() {
+  return new Float32Array(3);
+};
+
+
+/**
+ * Creates a new vec3f initialized with the value from the given array.
+ *
+ * @param {!Array<number>} vec The source 3 element array.
+ * @return {!goog.vec.vec3f.Type} The new vec3f.
+ */
+goog.vec.vec3f.createFromArray = function(vec) {
+  var newVec = goog.vec.vec3f.create();
+  goog.vec.vec3f.setFromArray(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Creates a new vec3f initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.vec3f.Type} The new vector.
+ */
+goog.vec.vec3f.createFromValues = function(v0, v1, v2) {
+  var vec = goog.vec.vec3f.create();
+  goog.vec.vec3f.setFromValues(vec, v0, v1, v2);
+  return vec;
+};
+
+
+/**
+ * Creates a clone of the given vec3f.
+ *
+ * @param {!goog.vec.vec3f.Type} vec The source vec3f.
+ * @return {!goog.vec.vec3f.Type} The new cloned vec3f.
+ */
+goog.vec.vec3f.clone = function(vec) {
+  var newVec = goog.vec.vec3f.create();
+  goog.vec.vec3f.setFromVec3f(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {!goog.vec.vec3f.Type} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @return {!goog.vec.vec3f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.setFromValues = function(vec, v0, v1, v2) {
+  vec[0] = v0;
+  vec[1] = v1;
+  vec[2] = v2;
+  return vec;
+};
+
+
+/**
+ * Initializes vec3f vec from vec3f src.
+ *
+ * @param {!goog.vec.vec3f.Type} vec The destination vector.
+ * @param {!goog.vec.vec3f.Type} src The source vector.
+ * @return {!goog.vec.vec3f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.setFromVec3f = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  return vec;
+};
+
+
+/**
+ * Initializes vec3f vec from vec3d src (typed as a Float64Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.vec3f.Type} vec The destination vector.
+ * @param {Float64Array} src The source vector.
+ * @return {!goog.vec.vec3f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.setFromVec3d = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  return vec;
+};
+
+
+/**
+ * Initializes vec3f vec from Array src.
+ *
+ * @param {!goog.vec.vec3f.Type} vec The destination vector.
+ * @param {Array<number>} src The source vector.
+ * @return {!goog.vec.vec3f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.setFromArray = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The first addend.
+ * @param {!goog.vec.vec3f.Type} vec1 The second addend.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.add = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] + vec1[0];
+  resultVec[1] = vec0[1] + vec1[1];
+  resultVec[2] = vec0[2] + vec1[2];
+  return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The minuend.
+ * @param {!goog.vec.vec3f.Type} vec1 The subtrahend.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.subtract = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] - vec1[0];
+  resultVec[1] = vec0[1] - vec1[1];
+  resultVec[2] = vec0[2] - vec1[2];
+  return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The vector to negate.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.negate = function(vec0, resultVec) {
+  resultVec[0] = -vec0[0];
+  resultVec[1] = -vec0[1];
+  resultVec[2] = -vec0[2];
+  return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  resultVec[2] = Math.abs(vec0[2]);
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.scale = function(vec0, scalar, resultVec) {
+  resultVec[0] = vec0[0] * scalar;
+  resultVec[1] = vec0[1] * scalar;
+  resultVec[2] = vec0[2] * scalar;
+  return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec3f.magnitudeSquared = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2];
+  return x * x + y * y + z * z;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec3f.magnitude = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2];
+  return Math.sqrt(x * x + y * y + z * z);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The vector to normalize.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.normalize = function(vec0, resultVec) {
+  var x = vec0[0], y = vec0[1], z = vec0[2];
+  var ilen = 1 / Math.sqrt(x * x + y * y + z * z);
+  resultVec[0] = x * ilen;
+  resultVec[1] = y * ilen;
+  resultVec[2] = z * ilen;
+  return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors v0 and v1.
+ *
+ * @param {!goog.vec.vec3f.Type} v0 The first vector.
+ * @param {!goog.vec.vec3f.Type} v1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.vec3f.dot = function(v0, v1) {
+  return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
+};
+
+
+/**
+ * Computes the vector (cross) product of v0 and v1 storing the result into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} v0 The first vector.
+ * @param {!goog.vec.vec3f.Type} v1 The second vector.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the
+ *     results. May be either v0 or v1.
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.cross = function(v0, v1, resultVec) {
+  var x0 = v0[0], y0 = v0[1], z0 = v0[2];
+  var x1 = v1[0], y1 = v1[1], z1 = v1[2];
+  resultVec[0] = y0 * z1 - z0 * y1;
+  resultVec[1] = z0 * x1 - x0 * z1;
+  resultVec[2] = x0 * y1 - y0 * x1;
+  return resultVec;
+};
+
+
+/**
+ * Returns the squared distance between two points.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 First point.
+ * @param {!goog.vec.vec3f.Type} vec1 Second point.
+ * @return {number} The squared distance between the points.
+ */
+goog.vec.vec3f.distanceSquared = function(vec0, vec1) {
+  var x = vec0[0] - vec1[0];
+  var y = vec0[1] - vec1[1];
+  var z = vec0[2] - vec1[2];
+  return x * x + y * y + z * z;
+};
+
+
+/**
+ * Returns the distance between two points.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 First point.
+ * @param {!goog.vec.vec3f.Type} vec1 Second point.
+ * @return {number} The distance between the points.
+ */
+goog.vec.vec3f.distance = function(vec0, vec1) {
+  return Math.sqrt(goog.vec.vec3f.distanceSquared(vec0, vec1));
+};
+
+
+/**
+ * Returns a unit vector pointing from one point to another.
+ * If the input points are equal then the result will be all zeros.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 Origin point.
+ * @param {!goog.vec.vec3f.Type} vec1 Target point.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or vec1).
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.direction = function(vec0, vec1, resultVec) {
+  var x = vec1[0] - vec0[0];
+  var y = vec1[1] - vec0[1];
+  var z = vec1[2] - vec0[2];
+  var d = Math.sqrt(x * x + y * y + z * z);
+  if (d) {
+    d = 1 / d;
+    resultVec[0] = x * d;
+    resultVec[1] = y * d;
+    resultVec[2] = z * d;
+  } else {
+    resultVec[0] = resultVec[1] = resultVec[2] = 0;
+  }
+  return resultVec;
+};
+
+
+/**
+ * Linearly interpolate from vec0 to v1 according to f. The value of f should be
+ * in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {!goog.vec.vec3f.Type} v0 The first vector.
+ * @param {!goog.vec.vec3f.Type} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the
+ *     results (may be v0 or v1).
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.lerp = function(v0, v1, f, resultVec) {
+  var x = v0[0], y = v0[1], z = v0[2];
+  resultVec[0] = (v1[0] - x) * f + x;
+  resultVec[1] = (v1[1] - y) * f + y;
+  resultVec[2] = (v1[2] - z) * f + z;
+  return resultVec;
+};
+
+
+/**
+ * Perform a spherical linear interpolation from v0 to v1 according to f. The
+ * value of f should be in the range [0..1] otherwise the results are undefined.
+ *
+ * Slerp is normally used to interpolate quaternions, but there is a geometric
+ * formula for interpolating vectors directly, see "Geometric Slerp" in:
+ * https://en.wikipedia.org/wiki/Slerp.
+ *
+ * This interpolates the vectors' directions via slerp, but linearly
+ * interpolates the vectors' magnitudes.
+ *
+ * Results are undefined if v0 or v1 are of zero magnitude.
+ *
+ * @param {!goog.vec.vec3f.Type} v0 The first vector.
+ * @param {!goog.vec.vec3f.Type} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the
+ *     results (may be v0 or v1).
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.slerp = function(v0, v1, f, resultVec) {
+  var v0Magnitude = goog.vec.vec3f.magnitude(v0);
+  var v1Magnitude = goog.vec.vec3f.magnitude(v1);
+
+  var cosAngle = goog.vec.vec3f.dot(v0, v1) / (v0Magnitude * v1Magnitude);
+
+  // If v0 and v1 are almost the same direction, fall back on a straight lerp.
+  if (cosAngle > 1 - goog.vec.EPSILON) {
+    return goog.vec.vec3f.lerp(v0, v1, f, resultVec);
+  }
+
+  var angle = 0;
+  var sinAngle = 0;
+
+  // If v0 and v1 are opposite directions, pick an arbitrary 'mid' vector that
+  // is perpendicular to both, and slerp from v0 -> mid -> v1.
+  if (cosAngle < -1 + goog.vec.EPSILON) {
+    var mid = goog.vec.vec3f.create();
+    var magnitudeFactor = (v0Magnitude + v1Magnitude) / 2;
+    if (v0[0]) {  // v0 not parallel to [0,0,1].
+      magnitudeFactor /= Math.sqrt(v0[0] * v0[0] + v0[1] + v0[1]);
+      mid[0] = -v0[1] * magnitudeFactor;
+      mid[1] = v0[0] * magnitudeFactor;
+      mid[2] = 0;
+    } else {  // v0 not parallel to [1,0,0].
+      magnitudeFactor /= Math.sqrt(v0[2] * v0[2] + v0[1] + v0[1]);
+      mid[0] = 0;
+      mid[1] = -v0[2] * magnitudeFactor;
+      mid[2] = v0[1] * magnitudeFactor;
+    }
+
+    // Depending on f, slerp between either v0 and mid, or mid and v1.
+    if (f <= 0.5) {
+      v1Magnitude = v0Magnitude;
+      v1 = mid;
+      f *= 2;
+    } else {
+      v0 = mid;
+      f = 2 * f - 1;
+    }
+
+    angle = Math.PI / 2;
+    cosAngle = 0;
+    sinAngle = 1;
+  } else {
+    angle = Math.acos(cosAngle);
+    sinAngle = Math.sqrt(1 - cosAngle * cosAngle);
+  }
+
+  var coeff0 = (Math.sin((1 - f) * angle) / sinAngle) / v0Magnitude;
+  var coeff1 = (Math.sin(f * angle) / sinAngle) / v1Magnitude;
+  var magnitude = (1 - f) * v0Magnitude + f * v1Magnitude;
+
+  resultVec[0] = (v0[0] * coeff0 + v1[0] * coeff1) * magnitude;
+  resultVec[1] = (v0[1] * coeff0 + v1[1] * coeff1) * magnitude;
+  resultVec[2] = (v0[2] * coeff0 + v1[2] * coeff1) * magnitude;
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec3f.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+    resultVec[2] = Math.max(vec0[2], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+    resultVec[2] = Math.max(vec0[2], limit[2]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {!goog.vec.vec3f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec3f.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec3f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec3f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec3f.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+    resultVec[2] = Math.min(vec0[2], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+    resultVec[2] = Math.min(vec0[2], limit[2]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Returns true if the components of v0 are equal to the components of v1.
+ *
+ * @param {!goog.vec.vec3f.Type} v0 The first vector.
+ * @param {!goog.vec.vec3f.Type} v1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.vec3f.equals = function(v0, v1) {
+  return v0.length == v1.length && v0[0] == v1[0] && v0[1] == v1[1] &&
+      v0[2] == v1[2];
+};

+ 11 - 0
public/lib/closure/vec/vec3f_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.vec3fTest');</script>
+<title>Closure Unit Tests - goog.vec.vec3fTest</title></head><body></body></html>

+ 386 - 0
public/lib/closure/vec/vec3f_test.js

@@ -0,0 +1,386 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec3d_test.js by running:       //
+//   swap_type.sh vec3f_test.js > vec3d_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.vec3fTest');
+goog.setTestOnly('goog.vec.vec3fTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.vec3f');
+
+function testCreate() {
+  var v = goog.vec.vec3f.create();
+  assertElementsEquals([0, 0, 0], v);
+}
+
+function testCreateFromArray() {
+  var v = goog.vec.vec3f.createFromArray([1, 2, 3]);
+  assertElementsEquals([1, 2, 3], v);
+}
+
+function testCreateFromValues() {
+  var v = goog.vec.vec3f.createFromValues(1, 2, 3);
+  assertElementsEquals([1, 2, 3], v);
+}
+
+function testClone() {
+  var v0 = goog.vec.vec3f.createFromValues(1, 2, 3);
+  var v1 = goog.vec.vec3f.clone(v0);
+  assertElementsEquals([1, 2, 3], v1);
+}
+
+function testSet() {
+  var v = goog.vec.vec3f.create();
+  goog.vec.vec3f.setFromValues(v, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], v);
+
+  goog.vec.vec3f.setFromArray(v, [4, 5, 6]);
+  assertElementsEquals([4, 5, 6], v);
+
+  var w = goog.vec.vec3f.create();
+  goog.vec.vec3f.setFromValues(w, 1, 2, 3);
+  assertElementsEquals([1, 2, 3], w);
+
+  goog.vec.vec3f.setFromArray(w, [4, 5, 6]);
+  assertElementsEquals([4, 5, 6], w);
+}
+
+function testAdd() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [4, 5, 6]);
+  var v2 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+
+  goog.vec.vec3f.add(v2, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([5, 7, 9], v2);
+
+  goog.vec.vec3f.add(goog.vec.vec3f.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([6, 9, 12], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [4, 5, 6]);
+  var v2 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+
+  goog.vec.vec3f.subtract(v2, v1, v2);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([-3, -3, -3], v2);
+
+  goog.vec.vec3f.setFromValues(v2, 0, 0, 0);
+  goog.vec.vec3f.subtract(v1, v0, v2);
+  assertElementsEquals([3, 3, 3], v2);
+
+  v2 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+  goog.vec.vec3f.subtract(v2, v1, v2);
+  assertElementsEquals([-3, -3, -3], v2);
+
+  goog.vec.vec3f.subtract(goog.vec.vec3f.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([2, 1, 0], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3f.create();
+
+  goog.vec.vec3f.negate(v0, v1);
+  assertElementsEquals([-1, -2, -3], v1);
+  assertElementsEquals([1, 2, 3], v0);
+
+  goog.vec.vec3f.negate(v0, v0);
+  assertElementsEquals([-1, -2, -3], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [-1, -2, -3]);
+  var v1 = goog.vec.vec3f.create();
+
+  goog.vec.vec3f.abs(v0, v1);
+  assertElementsEquals([1, 2, 3], v1);
+  assertElementsEquals([-1, -2, -3], v0);
+
+  goog.vec.vec3f.abs(v0, v0);
+  assertElementsEquals([1, 2, 3], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3f.create();
+
+  goog.vec.vec3f.scale(v0, 4, v1);
+  assertElementsEquals([4, 8, 12], v1);
+  assertElementsEquals([1, 2, 3], v0);
+
+  goog.vec.vec3f.setFromArray(v1, v0);
+  goog.vec.vec3f.scale(v1, 5, v1);
+  assertElementsEquals([5, 10, 15], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  assertEquals(14, goog.vec.vec3f.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  assertEquals(Math.sqrt(14), goog.vec.vec3f.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [2, 3, 4]);
+  var v1 = goog.vec.vec3f.create();
+  var v2 = goog.vec.vec3f.create();
+  goog.vec.vec3f.scale(
+      v0, 1 / goog.vec.vec3f.magnitude(v0), v2);
+
+  goog.vec.vec3f.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3, 4], v0);
+
+  goog.vec.vec3f.setFromArray(v1, v0);
+  goog.vec.vec3f.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [4, 5, 6]);
+  assertEquals(32, goog.vec.vec3f.dot(v0, v1));
+  assertEquals(32, goog.vec.vec3f.dot(v1, v0));
+}
+
+function testCross() {
+  var v0 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [1, 2, 3]);
+  var v1 = goog.vec.vec3f.setFromArray(goog.vec.vec3f.create(), [4, 5, 6]);
+  var crossVec = goog.vec.vec3f.create();
+
+  goog.vec.vec3f.cross(v0, v1, crossVec);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([-3, 6, -3], crossVec);
+
+  goog.vec.vec3f.setFromArray(crossVec, v1);
+  goog.vec.vec3f.cross(crossVec, v0, crossVec);
+  assertElementsEquals([1, 2, 3], v0);
+  assertElementsEquals([4, 5, 6], v1);
+  assertElementsEquals([3, -6, 3], crossVec);
+
+  goog.vec.vec3f.cross(v0, v0, v0);
+  assertElementsEquals([0, 0, 0], v0);
+}
+
+function testDistanceSquared() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  assertEquals(0, goog.vec.vec3f.distanceSquared(v0, v1));
+  goog.vec.vec3f.setFromValues(v0, 1, 2, 3);
+  goog.vec.vec3f.setFromValues(v1, -1, -2, -1);
+  assertEquals(36, goog.vec.vec3f.distanceSquared(v0, v1));
+}
+
+function testDistance() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  assertEquals(0, goog.vec.vec3f.distance(v0, v1));
+  goog.vec.vec3f.setFromValues(v0, 1, 2, 3);
+  goog.vec.vec3f.setFromValues(v1, -1, -2, -1);
+  assertEquals(6, goog.vec.vec3f.distance(v0, v1));
+}
+
+function testDirection() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  var dirVec = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 4, 5, 6);
+  goog.vec.vec3f.direction(v0, v1, dirVec);
+  assertElementsEquals([0, 0, 0], dirVec);
+  goog.vec.vec3f.setFromValues(v0, 0, 0, 0);
+  goog.vec.vec3f.setFromValues(v1, 1, 0, 0);
+  goog.vec.vec3f.direction(v0, v1, dirVec);
+  assertElementsEquals([1, 0, 0], dirVec);
+  goog.vec.vec3f.setFromValues(v0, 1, 1, 1);
+  goog.vec.vec3f.setFromValues(v1, 0, 0, 0);
+  goog.vec.vec3f.direction(v0, v1, dirVec);
+  assertElementsRoughlyEqual(
+      [-0.5773502588272095, -0.5773502588272095, -0.5773502588272095],
+      dirVec, goog.vec.EPSILON);
+}
+
+function testLerp() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 10, 20, 30);
+  var v2 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+
+  goog.vec.vec3f.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2, 3], v2);
+  goog.vec.vec3f.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20, 30], v2);
+  goog.vec.vec3f.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11, 16.5], v2);
+}
+
+function testSlerp() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 0, 0, 1);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 0, 0);
+  var v2 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), -1, 0, 0);
+  var v3 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), -5, 0, 0);
+  var v4 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 0, 0, -1);
+  var v5 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+
+  // Try f == 0 and f == 1.
+  goog.vec.vec3f.slerp(v5, v1, 0, v5);
+  assertElementsEquals([0, 0, 1], v5);
+  goog.vec.vec3f.slerp(v5, v1, 1, v5);
+  assertElementsEquals([1, 0, 0], v5);
+
+  // Try slerp between perpendicular vectors.
+  goog.vec.vec3f.slerp(v0, v1, .5, v5);
+  assertElementsRoughlyEqual(
+      [ Math.sqrt(2) / 2, 0, Math.sqrt(2) / 2 ], v5,
+      goog.vec.EPSILON);
+
+  // Try slerp between vectors of opposite directions (+Z and -Z).
+  v5 = goog.vec.vec3f.slerp(v0, v4, .5, v5);
+  // Axis of rotation is arbitrary, but result should be 90 degrees from both
+  // v0 and v4 when f = 0.5.
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3f.dot(v5, v0)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3f.dot(v5, v4)),
+                      goog.vec.EPSILON);
+
+  // f == 0.25, result should be 45-degrees to v0, and 135 to v4.
+  v5 = goog.vec.vec3f.slerp(v0, v4, .25, v5);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3f.dot(v5, v0)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3f.dot(v5, v4)),
+                      goog.vec.EPSILON);
+
+  // f = 0.75, result should be 135-degrees to v0, and 45 to v4.
+  v5 = goog.vec.vec3f.slerp(v0, v4, .75, v5);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3f.dot(v5, v0)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3f.dot(v5, v4)),
+                      goog.vec.EPSILON);
+
+  // Same as above, but on opposite directions of the X-axis.
+  v5 = goog.vec.vec3f.slerp(v1, v2, .5, v5);
+  // Axis of rotation is arbitrary, but result should be 90 degrees from both
+  // v1 and v2 when f = 0.5.
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3f.dot(v5, v1)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 2, Math.acos(goog.vec.vec3f.dot(v5, v2)),
+                      goog.vec.EPSILON);
+
+  // f == 0.25, result should be 45-degrees to v1, and 135 to v2.
+  v5 = goog.vec.vec3f.slerp(v1, v2, .25, v5);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3f.dot(v5, v1)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3f.dot(v5, v2)),
+                      goog.vec.EPSILON);
+
+  // f = 0.75, result should be 135-degrees to v1, and 45 to v2.
+  v5 = goog.vec.vec3f.slerp(v1, v2, .75, v5);
+  assertRoughlyEquals(Math.PI * 3 / 4, Math.acos(goog.vec.vec3f.dot(v5, v1)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals(Math.PI / 4, Math.acos(goog.vec.vec3f.dot(v5, v2)),
+                      goog.vec.EPSILON);
+
+  // Try vectors that aren't perpendicular or opposite/same direction.
+  var v6 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(),
+                                        Math.sqrt(2) / 2, Math.sqrt(2) / 2, 0);
+  goog.vec.vec3f.slerp(v1, v6, .9, v5);
+
+  // The vectors are 45 degrees apart, for f == 0.9, results should be 1/10 of
+  // that from v6 and 9/10 of that away from v1.
+  assertRoughlyEquals((Math.PI / 4) * 0.9, Math.acos(goog.vec.vec3f.dot(v1, v5)),
+                      goog.vec.EPSILON);
+  assertRoughlyEquals((Math.PI / 4) * 0.1, Math.acos(goog.vec.vec3f.dot(v6, v5)),
+                      goog.vec.EPSILON);
+
+  // Between vectors of the same direction, where one is non-unit-length
+  // (magnitudes should be lerp-ed).
+  goog.vec.vec3f.slerp(v2, v3, .5, v5);
+  assertElementsEquals([-3, 0, 0], v5);
+
+  // Between perpendicular vectors, where one is non-unit length.
+  goog.vec.vec3f.slerp(v0, v3, .5, v5);
+  assertRoughlyEquals(3, goog.vec.vec3f.magnitude(v5), goog.vec.EPSILON);
+  assertElementsRoughlyEqual(
+      [ -3 * (Math.sqrt(2) / 2), 0, 3 * (Math.sqrt(2) / 2) ], v5,
+      goog.vec.EPSILON);
+
+  // And vectors of opposite directions, where one is non-unit length.
+  goog.vec.vec3f.slerp(v1, v3, .5, v5);
+  // Axis of rotation is arbitrary, but result should be 90 degrees from both
+  // v1 and v3.
+  assertRoughlyEquals(
+      Math.PI / 2,
+      Math.acos(goog.vec.vec3f.dot(v5, v1) / (goog.vec.vec3f.magnitude(v5) *
+                                              goog.vec.vec3f.magnitude(v1))),
+      goog.vec.EPSILON);
+  assertRoughlyEquals(
+      Math.PI / 2,
+      Math.acos(goog.vec.vec3f.dot(v5, v3) / (goog.vec.vec3f.magnitude(v3) *
+                                              goog.vec.vec3f.magnitude(v5))),
+      goog.vec.EPSILON);
+  // Magnitude should be linearly interpolated.
+  assertRoughlyEquals(3, goog.vec.vec3f.magnitude(v5), goog.vec.EPSILON);
+
+  // Try a case where the vectors are the same direction (the same vector in
+  // this case), but where numerical error results in a dot product
+  // slightly greater than 1. Taking the acos of this would result in NaN.
+  var v7 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 0.009, 0.147,
+                                        0.989);
+  goog.vec.vec3f.slerp(v7, v7, .25, v5);
+  assertElementsRoughlyEqual([ v7[0], v7[1], v7[2] ], v5, goog.vec.EPSILON);
+}
+
+function testMax() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 10, 20, 30);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 5, 25, 35);
+  var v2 = goog.vec.vec3f.create();
+
+  goog.vec.vec3f.max(v0, v1, v2);
+  assertElementsEquals([10, 25, 35], v2);
+  goog.vec.vec3f.max(v1, v0, v1);
+  assertElementsEquals([10, 25, 35], v1);
+  goog.vec.vec3f.max(v2, 20, v2);
+  assertElementsEquals([20, 25, 35], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 10, 20, 30);
+  var v1 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 5, 25, 35);
+  var v2 = goog.vec.vec3f.create();
+
+  goog.vec.vec3f.min(v0, v1, v2);
+  assertElementsEquals([5, 20, 30], v2);
+  goog.vec.vec3f.min(v1, v0, v1);
+  assertElementsEquals([5, 20, 30], v1);
+  goog.vec.vec3f.min(v2, 20, v2);
+  assertElementsEquals([5, 20, 20], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.vec3f.setFromValues(goog.vec.vec3f.create(), 1, 2, 3);
+  var v1 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 4;
+  assertFalse(goog.vec.vec3f.equals(v0, v1));
+
+  v1 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+  v1[1] = 4;
+  assertFalse(goog.vec.vec3f.equals(v0, v1));
+
+  v1 = goog.vec.vec3f.setFromVec3f(goog.vec.vec3f.create(), v0);
+  v1[2] = 4;
+  assertFalse(goog.vec.vec3f.equals(v0, v1));
+}

+ 84 - 10
public/lib/closure/vec/vec4.js

@@ -24,6 +24,7 @@
  */
 goog.provide('goog.vec.Vec4');
 
+/** @suppress {extraRequire} */
 goog.require('goog.vec');
 
 /** @typedef {goog.vec.Float32} */ goog.vec.Vec4.Float32;
@@ -207,7 +208,7 @@ goog.vec.Vec4.clone = goog.vec.Vec4.createFromArray;
  * @param {number} v1 The value for element at index 1.
  * @param {number} v2 The value for element at index 2.
  * @param {number} v3 The value for element at index 3.
- * @return {!goog.vec.Vec4.AnyType} return vec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return vec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.setFromValues = function(vec, v0, v1, v2, v3) {
@@ -225,7 +226,7 @@ goog.vec.Vec4.setFromValues = function(vec, v0, v1, v2, v3) {
  * @param {goog.vec.Vec4.AnyType} vec The vector to receive the
  *     values.
  * @param {goog.vec.Vec4.AnyType} values The array of values.
- * @return {!goog.vec.Vec4.AnyType} return vec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return vec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.setFromArray = function(vec, values) {
@@ -245,7 +246,7 @@ goog.vec.Vec4.setFromArray = function(vec, values) {
  * @param {goog.vec.Vec4.AnyType} vec1 The second addend.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to
  *     receive the result. May be vec0 or vec1.
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.add = function(vec0, vec1, resultVec) {
@@ -265,7 +266,7 @@ goog.vec.Vec4.add = function(vec0, vec1, resultVec) {
  * @param {goog.vec.Vec4.AnyType} vec1 The subtrahend.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to
  *     receive the result. May be vec0 or vec1.
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.subtract = function(vec0, vec1, resultVec) {
@@ -283,7 +284,7 @@ goog.vec.Vec4.subtract = function(vec0, vec1, resultVec) {
  * @param {goog.vec.Vec4.AnyType} vec0 The vector to negate.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to
  *     receive the result. May be vec0.
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.negate = function(vec0, resultVec) {
@@ -295,6 +296,25 @@ goog.vec.Vec4.negate = function(vec0, resultVec) {
 };
 
 
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec4.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  resultVec[2] = Math.abs(vec0[2]);
+  resultVec[3] = Math.abs(vec0[3]);
+  return resultVec;
+};
+
+
 /**
  * Multiplies each component of vec0 with scalar storing the product into
  * resultVec.
@@ -303,7 +323,7 @@ goog.vec.Vec4.negate = function(vec0, resultVec) {
  * @param {number} scalar The value to multiply with each component of vec0.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to
  *     receive the result. May be vec0.
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.scale = function(vec0, scalar, resultVec) {
@@ -345,7 +365,7 @@ goog.vec.Vec4.magnitude = function(vec0) {
  * @param {goog.vec.Vec4.AnyType} vec0 The vector to normalize.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to
  *     receive the result. May be vec0.
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.normalize = function(vec0, resultVec) {
@@ -379,7 +399,7 @@ goog.vec.Vec4.dot = function(v0, v1) {
  * @param {number} f The interpolation factor.
  * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the
  *     results (may be v0 or v1).
- * @return {!goog.vec.Vec4.AnyType} return resultVec so that operations can be
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
  *     chained together.
  */
 goog.vec.Vec4.lerp = function(v0, v1, f, resultVec) {
@@ -392,6 +412,60 @@ goog.vec.Vec4.lerp = function(v0, v1, f, resultVec) {
 };
 
 
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec4.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec4.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+    resultVec[2] = Math.max(vec0[2], limit);
+    resultVec[3] = Math.max(vec0[3], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+    resultVec[2] = Math.max(vec0[2], limit[2]);
+    resultVec[3] = Math.max(vec0[3], limit[3]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {goog.vec.Vec4.AnyType} vec0 The source vector.
+ * @param {goog.vec.Vec4.AnyType|number} limit The limit vector or scalar.
+ * @param {goog.vec.Vec4.AnyType} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.Vec4.AnyType} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.Vec4.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+    resultVec[2] = Math.min(vec0[2], limit);
+    resultVec[3] = Math.min(vec0[3], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+    resultVec[2] = Math.min(vec0[2], limit[2]);
+    resultVec[3] = Math.min(vec0[3], limit[3]);
+  }
+  return resultVec;
+};
+
+
 /**
  * Returns true if the components of v0 are equal to the components of v1.
  *
@@ -400,6 +474,6 @@ goog.vec.Vec4.lerp = function(v0, v1, f, resultVec) {
  * @return {boolean} True if the vectors are equal, false otherwise.
  */
 goog.vec.Vec4.equals = function(v0, v1) {
-  return v0.length == v1.length &&
-      v0[0] == v1[0] && v0[1] == v1[1] && v0[2] == v1[2] && v0[3] == v1[3];
+  return v0.length == v1.length && v0[0] == v1[0] && v0[1] == v1[1] &&
+      v0[2] == v1[2] && v0[3] == v1[3];
 };

+ 11 - 0
public/lib/closure/vec/vec4_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.Vec4Test');</script>
+<title>Closure Unit Tests - goog.vec.Vec4Test</title></head><body></body></html>

+ 215 - 0
public/lib/closure/vec/vec4_test.js

@@ -0,0 +1,215 @@
+// Copyright 2011 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+goog.provide('goog.vec.Vec4Test');
+goog.setTestOnly('goog.vec.Vec4Test');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.Vec4');
+
+function testDeprecatedConstructor() {
+  assertElementsEquals([0, 0, 0, 0], goog.vec.Vec4.create());
+
+  assertElementsEquals([1, 2, 3, 4],
+      goog.vec.Vec4.createFromValues(1, 2, 3, 4));
+
+  assertElementsEquals([1, 2, 3, 4],
+      goog.vec.Vec4.createFromArray([1, 2, 3, 4]));
+
+  var v = goog.vec.Vec4.createFromValues(1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], goog.vec.Vec4.clone(v));
+}
+
+function testConstructor() {
+  assertElementsEquals([0, 0, 0, 0], goog.vec.Vec4.createFloat32());
+
+  assertElementsEquals([1, 2, 3, 4],
+      goog.vec.Vec4.createFloat32FromValues(1, 2, 3, 4));
+
+  assertElementsEquals([1, 2, 3, 4],
+      goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]));
+
+  var v = goog.vec.Vec4.createFloat32FromValues(1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], goog.vec.Vec4.cloneFloat32(v));
+
+  assertElementsEquals([0, 0, 0, 0], goog.vec.Vec4.createFloat64());
+
+  assertElementsEquals([1, 2, 3, 4],
+      goog.vec.Vec4.createFloat64FromValues(1, 2, 3, 4));
+
+  assertElementsEquals([1, 2, 3, 4],
+      goog.vec.Vec4.createFloat64FromArray([1, 2, 3, 4]));
+
+  var w = goog.vec.Vec4.createFloat64FromValues(1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], goog.vec.Vec4.cloneFloat64(w));
+}
+
+function testSet() {
+  var v = goog.vec.Vec4.createFloat32();
+  goog.vec.Vec4.setFromValues(v, 1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], v);
+
+  goog.vec.Vec4.setFromArray(v, [4, 5, 6, 7]);
+  assertElementsEquals([4, 5, 6, 7], v);
+}
+
+function testAdd() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]);
+  var v1 = goog.vec.Vec4.createFloat32FromArray([5, 6, 7, 8]);
+  var v2 = goog.vec.Vec4.cloneFloat32(v0);
+
+  goog.vec.Vec4.add(v2, v1, v2);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([6, 8, 10, 12], v2);
+
+  goog.vec.Vec4.add(goog.vec.Vec4.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([7, 10, 13, 16], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([4, 3, 2, 1]);
+  var v1 = goog.vec.Vec4.createFloat32FromArray([5, 6, 7, 8]);
+  var v2 = goog.vec.Vec4.cloneFloat32(v0);
+
+  goog.vec.Vec4.subtract(v2, v1, v2);
+  assertElementsEquals([4, 3, 2, 1], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([-1, -3, -5, -7], v2);
+
+  goog.vec.Vec4.setFromValues(v2, 0, 0, 0, 0);
+  goog.vec.Vec4.subtract(v1, v0, v2);
+  assertElementsEquals([1, 3, 5, 7], v2);
+
+  goog.vec.Vec4.subtract(goog.vec.Vec4.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([-3, 0, 3, 6], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]);
+  var v1 = goog.vec.Vec4.createFloat32();
+
+  goog.vec.Vec4.negate(v0, v1);
+  assertElementsEquals([-1, -2, -3, -4], v1);
+  assertElementsEquals([1, 2, 3, 4], v0);
+
+  goog.vec.Vec4.negate(v0, v0);
+  assertElementsEquals([-1, -2, -3, -4], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.Vec4.createFloat32FromValues(-1, -2, -3, -4);
+  var v1 = goog.vec.Vec4.createFloat32();
+
+  goog.vec.Vec4.abs(v0, v1);
+  assertElementsEquals([1, 2, 3, 4], v1);
+  assertElementsEquals([-1, -2, -3, -4], v0);
+
+  goog.vec.Vec4.abs(v0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]);
+  var v1 = goog.vec.Vec4.createFloat32();
+
+  goog.vec.Vec4.scale(v0, 4, v1);
+  assertElementsEquals([4, 8, 12, 16], v1);
+  assertElementsEquals([1, 2, 3, 4], v0);
+
+  goog.vec.Vec4.setFromArray(v1, v0);
+  goog.vec.Vec4.scale(v1, 5, v1);
+  assertElementsEquals([5, 10, 15, 20], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]);
+  assertEquals(30, goog.vec.Vec4.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]);
+  assertEquals(Math.sqrt(30), goog.vec.Vec4.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([2, 3, 4, 5]);
+  var v1 = goog.vec.Vec4.createFloat32();
+  var v2 = goog.vec.Vec4.createFloat32();
+  goog.vec.Vec4.scale(v0, 1 / goog.vec.Vec4.magnitude(v0), v2);
+
+  goog.vec.Vec4.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3, 4, 5], v0);
+
+  goog.vec.Vec4.setFromArray(v1, v0);
+  goog.vec.Vec4.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.Vec4.createFloat32FromArray([1, 2, 3, 4]);
+  var v1 = goog.vec.Vec4.createFloat32FromArray([5, 6, 7, 8]);
+  assertEquals(70, goog.vec.Vec4.dot(v0, v1));
+  assertEquals(70, goog.vec.Vec4.dot(v1, v0));
+}
+
+function testLerp() {
+  var v0 = goog.vec.Vec4.createFloat32FromValues(1, 2, 3, 4);
+  var v1 = goog.vec.Vec4.createFloat32FromValues(10, 20, 30, 40);
+  var v2 = goog.vec.Vec4.cloneFloat32(v0);
+
+  goog.vec.Vec4.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2, 3, 4], v2);
+  goog.vec.Vec4.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20, 30, 40], v2);
+  goog.vec.Vec4.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11, 16.5, 22], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.Vec4.createFloat32FromValues(10, 20, 30, 40);
+  var v1 = goog.vec.Vec4.createFloat32FromValues(5, 25, 35, 30);
+  var v2 = goog.vec.Vec4.createFloat32();
+
+  goog.vec.Vec4.max(v0, v1, v2);
+  assertElementsEquals([10, 25, 35, 40], v2);
+  goog.vec.Vec4.max(v1, v0, v1);
+  assertElementsEquals([10, 25, 35, 40], v1);
+  goog.vec.Vec4.max(v2, 20, v2);
+  assertElementsEquals([20, 25, 35, 40], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.Vec4.createFloat32FromValues(10, 20, 30, 40);
+  var v1 = goog.vec.Vec4.createFloat32FromValues(5, 25, 35, 30);
+  var v2 = goog.vec.Vec4.createFloat32();
+
+  goog.vec.Vec4.min(v0, v1, v2);
+  assertElementsEquals([5, 20, 30, 30], v2);
+  goog.vec.Vec4.min(v1, v0, v1);
+  assertElementsEquals([5, 20, 30, 30], v1);
+  goog.vec.Vec4.min(v2, 20, v2);
+  assertElementsEquals([5, 20, 20, 20], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.Vec4.createFloat32FromValues(1, 2, 3, 4);
+  var v1 = goog.vec.Vec4.cloneFloat32(v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 5;
+  assertFalse(goog.vec.Vec4.equals(v0, v1));
+
+  v1 = goog.vec.Vec4.cloneFloat32(v0);
+  v1[1] = 5;
+  assertFalse(goog.vec.Vec4.equals(v0, v1));
+
+  v1 = goog.vec.Vec4.cloneFloat32(v0);
+  v1[2] = 5;
+  assertFalse(goog.vec.Vec4.equals(v0, v1));
+
+  v1 = goog.vec.Vec4.cloneFloat32(v0);
+  v1[3] = 5;
+  assertFalse(goog.vec.Vec4.equals(v0, v1));
+}

+ 408 - 0
public/lib/closure/vec/vec4d.js

@@ -0,0 +1,408 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec4f.js by running:            //
+//   swap_type.sh vec4d.js > vec4f.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 4 element double (64bit)
+ * vectors.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.vec4d');
+goog.provide('goog.vec.vec4d.Type');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {goog.vec.Float64} */ goog.vec.vec4d.Type;
+
+
+/**
+ * Creates a vec4d with all elements initialized to zero.
+ *
+ * @return {!goog.vec.vec4d.Type} The new vec4d.
+ */
+goog.vec.vec4d.create = function() {
+  return new Float64Array(4);
+};
+
+
+/**
+ * Creates a new vec4d initialized with the value from the given array.
+ *
+ * @param {!Array<number>} vec The source 4 element array.
+ * @return {!goog.vec.vec4d.Type} The new vec4d.
+ */
+goog.vec.vec4d.createFromArray = function(vec) {
+  var newVec = goog.vec.vec4d.create();
+  goog.vec.vec4d.setFromArray(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Creates a new vec4d initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.vec4d.Type} The new vector.
+ */
+goog.vec.vec4d.createFromValues = function(v0, v1, v2, v3) {
+  var vec = goog.vec.vec4d.create();
+  goog.vec.vec4d.setFromValues(vec, v0, v1, v2, v3);
+  return vec;
+};
+
+
+/**
+ * Creates a clone of the given vec4d.
+ *
+ * @param {!goog.vec.vec4d.Type} vec The source vec4d.
+ * @return {!goog.vec.vec4d.Type} The new cloned vec4d.
+ */
+goog.vec.vec4d.clone = function(vec) {
+  var newVec = goog.vec.vec4d.create();
+  goog.vec.vec4d.setFromVec4d(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {!goog.vec.vec4d.Type} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.vec4d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.setFromValues = function(vec, v0, v1, v2, v3) {
+  vec[0] = v0;
+  vec[1] = v1;
+  vec[2] = v2;
+  vec[3] = v3;
+  return vec;
+};
+
+
+/**
+ * Initializes vec4d vec from vec4d src.
+ *
+ * @param {!goog.vec.vec4d.Type} vec The destination vector.
+ * @param {!goog.vec.vec4d.Type} src The source vector.
+ * @return {!goog.vec.vec4d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.setFromVec4d = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  vec[3] = src[3];
+  return vec;
+};
+
+
+/**
+ * Initializes vec4d vec from vec4f src (typed as a Float32Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.vec4d.Type} vec The destination vector.
+ * @param {Float32Array} src The source vector.
+ * @return {!goog.vec.vec4d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.setFromVec4f = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  vec[3] = src[3];
+  return vec;
+};
+
+
+/**
+ * Initializes vec4d vec from Array src.
+ *
+ * @param {!goog.vec.vec4d.Type} vec The destination vector.
+ * @param {Array<number>} src The source vector.
+ * @return {!goog.vec.vec4d.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.setFromArray = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  vec[3] = src[3];
+  return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The first addend.
+ * @param {!goog.vec.vec4d.Type} vec1 The second addend.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.add = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] + vec1[0];
+  resultVec[1] = vec0[1] + vec1[1];
+  resultVec[2] = vec0[2] + vec1[2];
+  resultVec[3] = vec0[3] + vec1[3];
+  return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The minuend.
+ * @param {!goog.vec.vec4d.Type} vec1 The subtrahend.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.subtract = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] - vec1[0];
+  resultVec[1] = vec0[1] - vec1[1];
+  resultVec[2] = vec0[2] - vec1[2];
+  resultVec[3] = vec0[3] - vec1[3];
+  return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The vector to negate.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.negate = function(vec0, resultVec) {
+  resultVec[0] = -vec0[0];
+  resultVec[1] = -vec0[1];
+  resultVec[2] = -vec0[2];
+  resultVec[3] = -vec0[3];
+  return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  resultVec[2] = Math.abs(vec0[2]);
+  resultVec[3] = Math.abs(vec0[3]);
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.scale = function(vec0, scalar, resultVec) {
+  resultVec[0] = vec0[0] * scalar;
+  resultVec[1] = vec0[1] * scalar;
+  resultVec[2] = vec0[2] * scalar;
+  resultVec[3] = vec0[3] * scalar;
+  return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec4d.magnitudeSquared = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+  return x * x + y * y + z * z + w * w;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec4d.magnitude = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+  return Math.sqrt(x * x + y * y + z * z + w * w);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The vector to normalize.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.normalize = function(vec0, resultVec) {
+  var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+  var ilen = 1 / Math.sqrt(x * x + y * y + z * z + w * w);
+  resultVec[0] = x * ilen;
+  resultVec[1] = y * ilen;
+  resultVec[2] = z * ilen;
+  resultVec[3] = w * ilen;
+  return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors v0 and v1.
+ *
+ * @param {!goog.vec.vec4d.Type} v0 The first vector.
+ * @param {!goog.vec.vec4d.Type} v1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.vec4d.dot = function(v0, v1) {
+  return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2] + v0[3] * v1[3];
+};
+
+
+/**
+ * Linearly interpolate from v0 to v1 according to f. The value of f should be
+ * in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {!goog.vec.vec4d.Type} v0 The first vector.
+ * @param {!goog.vec.vec4d.Type} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to receive the
+ *     results (may be v0 or v1).
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.lerp = function(v0, v1, f, resultVec) {
+  var x = v0[0], y = v0[1], z = v0[2], w = v0[3];
+  resultVec[0] = (v1[0] - x) * f + x;
+  resultVec[1] = (v1[1] - y) * f + y;
+  resultVec[2] = (v1[2] - z) * f + z;
+  resultVec[3] = (v1[3] - w) * f + w;
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec4d.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+    resultVec[2] = Math.max(vec0[2], limit);
+    resultVec[3] = Math.max(vec0[3], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+    resultVec[2] = Math.max(vec0[2], limit[2]);
+    resultVec[3] = Math.max(vec0[3], limit[3]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {!goog.vec.vec4d.Type} vec0 The source vector.
+ * @param {!goog.vec.vec4d.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec4d.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec4d.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4d.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+    resultVec[2] = Math.min(vec0[2], limit);
+    resultVec[3] = Math.min(vec0[3], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+    resultVec[2] = Math.min(vec0[2], limit[2]);
+    resultVec[3] = Math.min(vec0[3], limit[3]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Returns true if the components of v0 are equal to the components of v1.
+ *
+ * @param {!goog.vec.vec4d.Type} v0 The first vector.
+ * @param {!goog.vec.vec4d.Type} v1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.vec4d.equals = function(v0, v1) {
+  return v0.length == v1.length && v0[0] == v1[0] && v0[1] == v1[1] &&
+      v0[2] == v1[2] && v0[3] == v1[3];
+};

+ 11 - 0
public/lib/closure/vec/vec4d_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.vec4dTest');</script>
+<title>Closure Unit Tests - goog.vec.vec4dTest</title></head><body></body></html>

+ 206 - 0
public/lib/closure/vec/vec4d_test.js

@@ -0,0 +1,206 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec4f_test.js by running:       //
+//   swap_type.sh vec4d_test.js > vec4f_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.vec4dTest');
+goog.setTestOnly('goog.vec.vec4dTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.vec4d');
+
+function testCreate() {
+  var v = goog.vec.vec4d.create();
+  assertElementsEquals([0, 0, 0, 0], v);
+}
+
+function testCreateFromArray() {
+  var v = goog.vec.vec4d.createFromArray([1, 2, 3, 4]);
+  assertElementsEquals([1, 2, 3, 4], v);
+}
+
+function testCreateFromValues() {
+  var v = goog.vec.vec4d.createFromValues(1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], v);
+}
+
+function testClone() {
+  var v0 = goog.vec.vec4d.createFromValues(1, 2, 3, 4);
+  var v1 = goog.vec.vec4d.clone(v0);
+  assertElementsEquals([1, 2, 3, 4], v1);
+}
+
+function testSet() {
+  var v = goog.vec.vec4d.create();
+  goog.vec.vec4d.setFromValues(v, 1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], v);
+
+  goog.vec.vec4d.setFromArray(v, [4, 5, 6, 7]);
+  assertElementsEquals([4, 5, 6, 7], v);
+}
+
+function testAdd() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [5, 6, 7, 8]);
+  var v2 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+
+  goog.vec.vec4d.add(v2, v1, v2);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([6, 8, 10, 12], v2);
+
+  goog.vec.vec4d.add(goog.vec.vec4d.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([7, 10, 13, 16], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [4, 3, 2, 1]);
+  var v1 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [5, 6, 7, 8]);
+  var v2 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+
+  goog.vec.vec4d.subtract(v2, v1, v2);
+  assertElementsEquals([4, 3, 2, 1], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([-1, -3, -5, -7], v2);
+
+  goog.vec.vec4d.setFromValues(v2, 0, 0, 0, 0);
+  goog.vec.vec4d.subtract(v1, v0, v2);
+  assertElementsEquals([1, 3, 5, 7], v2);
+
+  goog.vec.vec4d.subtract(goog.vec.vec4d.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([-3, 0, 3, 6], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4d.create();
+
+  goog.vec.vec4d.negate(v0, v1);
+  assertElementsEquals([-1, -2, -3, -4], v1);
+  assertElementsEquals([1, 2, 3, 4], v0);
+
+  goog.vec.vec4d.negate(v0, v0);
+  assertElementsEquals([-1, -2, -3, -4], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [-1, -2, -3, -4]);
+  var v1 = goog.vec.vec4d.create();
+
+  goog.vec.vec4d.abs(v0, v1);
+  assertElementsEquals([1, 2, 3, 4], v1);
+  assertElementsEquals([-1, -2, -3, -4], v0);
+
+  goog.vec.vec4d.abs(v0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4d.create();
+
+  goog.vec.vec4d.scale(v0, 4, v1);
+  assertElementsEquals([4, 8, 12, 16], v1);
+  assertElementsEquals([1, 2, 3, 4], v0);
+
+  goog.vec.vec4d.setFromArray(v1, v0);
+  goog.vec.vec4d.scale(v1, 5, v1);
+  assertElementsEquals([5, 10, 15, 20], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [1, 2, 3, 4]);
+  assertEquals(30, goog.vec.vec4d.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [1, 2, 3, 4]);
+  assertEquals(Math.sqrt(30), goog.vec.vec4d.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [2, 3, 4, 5]);
+  var v1 = goog.vec.vec4d.create();
+  var v2 = goog.vec.vec4d.create();
+  goog.vec.vec4d.scale(v0, 1 / goog.vec.vec4d.magnitude(v0), v2);
+
+  goog.vec.vec4d.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3, 4, 5], v0);
+
+  goog.vec.vec4d.setFromArray(v1, v0);
+  goog.vec.vec4d.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4d.setFromArray(goog.vec.vec4d.create(), [5, 6, 7, 8]);
+  assertEquals(70, goog.vec.vec4d.dot(v0, v1));
+  assertEquals(70, goog.vec.vec4d.dot(v1, v0));
+}
+
+function testLerp() {
+  var v0 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 1, 2, 3, 4);
+  var v1 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 10, 20, 30, 40);
+  var v2 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+
+  goog.vec.vec4d.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2, 3, 4], v2);
+  goog.vec.vec4d.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20, 30, 40], v2);
+  goog.vec.vec4d.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11, 16.5, 22], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 10, 20, 30, 40);
+  var v1 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 5, 25, 35, 30);
+  var v2 = goog.vec.vec4d.create();
+
+  goog.vec.vec4d.max(v0, v1, v2);
+  assertElementsEquals([10, 25, 35, 40], v2);
+  goog.vec.vec4d.max(v1, v0, v1);
+  assertElementsEquals([10, 25, 35, 40], v1);
+  goog.vec.vec4d.max(v2, 20, v2);
+  assertElementsEquals([20, 25, 35, 40], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 10, 20, 30, 40);
+  var v1 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 5, 25, 35, 30);
+  var v2 = goog.vec.vec4d.create();
+
+  goog.vec.vec4d.min(v0, v1, v2);
+  assertElementsEquals([5, 20, 30, 30], v2);
+  goog.vec.vec4d.min(v1, v0, v1);
+  assertElementsEquals([5, 20, 30, 30], v1);
+  goog.vec.vec4d.min(v2, 20, v2);
+  assertElementsEquals([5, 20, 20, 20], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.vec4d.setFromValues(goog.vec.vec4d.create(), 1, 2, 3, 4);
+  var v1 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 5;
+  assertFalse(goog.vec.vec4d.equals(v0, v1));
+
+  v1 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+  v1[1] = 5;
+  assertFalse(goog.vec.vec4d.equals(v0, v1));
+
+  v1 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+  v1[2] = 5;
+  assertFalse(goog.vec.vec4d.equals(v0, v1));
+
+  v1 = goog.vec.vec4d.setFromVec4d(goog.vec.vec4d.create(), v0);
+  v1[3] = 5;
+  assertFalse(goog.vec.vec4d.equals(v0, v1));
+}

+ 408 - 0
public/lib/closure/vec/vec4f.js

@@ -0,0 +1,408 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS-IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec4d.js by running:            //
+//   swap_type.sh vec4f.js > vec4d.js                                        //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+
+/**
+ * @fileoverview Provides functions for operating on 4 element float (32bit)
+ * vectors.
+ *
+ * The last parameter will typically be the output object and an object
+ * can be both an input and output parameter to all methods except where
+ * noted.
+ *
+ * See the README for notes about the design and structure of the API
+ * (especially related to performance).
+ *
+ */
+goog.provide('goog.vec.vec4f');
+goog.provide('goog.vec.vec4f.Type');
+
+/** @suppress {extraRequire} */
+goog.require('goog.vec');
+
+/** @typedef {goog.vec.Float32} */ goog.vec.vec4f.Type;
+
+
+/**
+ * Creates a vec4f with all elements initialized to zero.
+ *
+ * @return {!goog.vec.vec4f.Type} The new vec4f.
+ */
+goog.vec.vec4f.create = function() {
+  return new Float32Array(4);
+};
+
+
+/**
+ * Creates a new vec4f initialized with the value from the given array.
+ *
+ * @param {!Array<number>} vec The source 4 element array.
+ * @return {!goog.vec.vec4f.Type} The new vec4f.
+ */
+goog.vec.vec4f.createFromArray = function(vec) {
+  var newVec = goog.vec.vec4f.create();
+  goog.vec.vec4f.setFromArray(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Creates a new vec4f initialized with the supplied values.
+ *
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.vec4f.Type} The new vector.
+ */
+goog.vec.vec4f.createFromValues = function(v0, v1, v2, v3) {
+  var vec = goog.vec.vec4f.create();
+  goog.vec.vec4f.setFromValues(vec, v0, v1, v2, v3);
+  return vec;
+};
+
+
+/**
+ * Creates a clone of the given vec4f.
+ *
+ * @param {!goog.vec.vec4f.Type} vec The source vec4f.
+ * @return {!goog.vec.vec4f.Type} The new cloned vec4f.
+ */
+goog.vec.vec4f.clone = function(vec) {
+  var newVec = goog.vec.vec4f.create();
+  goog.vec.vec4f.setFromVec4f(newVec, vec);
+  return newVec;
+};
+
+
+/**
+ * Initializes the vector with the given values.
+ *
+ * @param {!goog.vec.vec4f.Type} vec The vector to receive the values.
+ * @param {number} v0 The value for element at index 0.
+ * @param {number} v1 The value for element at index 1.
+ * @param {number} v2 The value for element at index 2.
+ * @param {number} v3 The value for element at index 3.
+ * @return {!goog.vec.vec4f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.setFromValues = function(vec, v0, v1, v2, v3) {
+  vec[0] = v0;
+  vec[1] = v1;
+  vec[2] = v2;
+  vec[3] = v3;
+  return vec;
+};
+
+
+/**
+ * Initializes vec4f vec from vec4f src.
+ *
+ * @param {!goog.vec.vec4f.Type} vec The destination vector.
+ * @param {!goog.vec.vec4f.Type} src The source vector.
+ * @return {!goog.vec.vec4f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.setFromVec4f = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  vec[3] = src[3];
+  return vec;
+};
+
+
+/**
+ * Initializes vec4f vec from vec4d src (typed as a Float64Array to
+ * avoid circular goog.requires).
+ *
+ * @param {!goog.vec.vec4f.Type} vec The destination vector.
+ * @param {Float64Array} src The source vector.
+ * @return {!goog.vec.vec4f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.setFromVec4d = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  vec[3] = src[3];
+  return vec;
+};
+
+
+/**
+ * Initializes vec4f vec from Array src.
+ *
+ * @param {!goog.vec.vec4f.Type} vec The destination vector.
+ * @param {Array<number>} src The source vector.
+ * @return {!goog.vec.vec4f.Type} Return vec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.setFromArray = function(vec, src) {
+  vec[0] = src[0];
+  vec[1] = src[1];
+  vec[2] = src[2];
+  vec[3] = src[3];
+  return vec;
+};
+
+
+/**
+ * Performs a component-wise addition of vec0 and vec1 together storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The first addend.
+ * @param {!goog.vec.vec4f.Type} vec1 The second addend.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.add = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] + vec1[0];
+  resultVec[1] = vec0[1] + vec1[1];
+  resultVec[2] = vec0[2] + vec1[2];
+  resultVec[3] = vec0[3] + vec1[3];
+  return resultVec;
+};
+
+
+/**
+ * Performs a component-wise subtraction of vec1 from vec0 storing the
+ * result into resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The minuend.
+ * @param {!goog.vec.vec4f.Type} vec1 The subtrahend.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to
+ *     receive the result. May be vec0 or vec1.
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.subtract = function(vec0, vec1, resultVec) {
+  resultVec[0] = vec0[0] - vec1[0];
+  resultVec[1] = vec0[1] - vec1[1];
+  resultVec[2] = vec0[2] - vec1[2];
+  resultVec[3] = vec0[3] - vec1[3];
+  return resultVec;
+};
+
+
+/**
+ * Negates vec0, storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The vector to negate.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.negate = function(vec0, resultVec) {
+  resultVec[0] = -vec0[0];
+  resultVec[1] = -vec0[1];
+  resultVec[2] = -vec0[2];
+  resultVec[3] = -vec0[3];
+  return resultVec;
+};
+
+
+/**
+ * Takes the absolute value of each component of vec0 storing the result in
+ * resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to receive the result.
+ *     May be vec0.
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.abs = function(vec0, resultVec) {
+  resultVec[0] = Math.abs(vec0[0]);
+  resultVec[1] = Math.abs(vec0[1]);
+  resultVec[2] = Math.abs(vec0[2]);
+  resultVec[3] = Math.abs(vec0[3]);
+  return resultVec;
+};
+
+
+/**
+ * Multiplies each component of vec0 with scalar storing the product into
+ * resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The source vector.
+ * @param {number} scalar The value to multiply with each component of vec0.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.scale = function(vec0, scalar, resultVec) {
+  resultVec[0] = vec0[0] * scalar;
+  resultVec[1] = vec0[1] * scalar;
+  resultVec[2] = vec0[2] * scalar;
+  resultVec[3] = vec0[3] * scalar;
+  return resultVec;
+};
+
+
+/**
+ * Returns the magnitudeSquared of the given vector.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec4f.magnitudeSquared = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+  return x * x + y * y + z * z + w * w;
+};
+
+
+/**
+ * Returns the magnitude of the given vector.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The vector.
+ * @return {number} The magnitude of the vector.
+ */
+goog.vec.vec4f.magnitude = function(vec0) {
+  var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+  return Math.sqrt(x * x + y * y + z * z + w * w);
+};
+
+
+/**
+ * Normalizes the given vector storing the result into resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The vector to normalize.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to
+ *     receive the result. May be vec0.
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.normalize = function(vec0, resultVec) {
+  var x = vec0[0], y = vec0[1], z = vec0[2], w = vec0[3];
+  var ilen = 1 / Math.sqrt(x * x + y * y + z * z + w * w);
+  resultVec[0] = x * ilen;
+  resultVec[1] = y * ilen;
+  resultVec[2] = z * ilen;
+  resultVec[3] = w * ilen;
+  return resultVec;
+};
+
+
+/**
+ * Returns the scalar product of vectors v0 and v1.
+ *
+ * @param {!goog.vec.vec4f.Type} v0 The first vector.
+ * @param {!goog.vec.vec4f.Type} v1 The second vector.
+ * @return {number} The scalar product.
+ */
+goog.vec.vec4f.dot = function(v0, v1) {
+  return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2] + v0[3] * v1[3];
+};
+
+
+/**
+ * Linearly interpolate from v0 to v1 according to f. The value of f should be
+ * in the range [0..1] otherwise the results are undefined.
+ *
+ * @param {!goog.vec.vec4f.Type} v0 The first vector.
+ * @param {!goog.vec.vec4f.Type} v1 The second vector.
+ * @param {number} f The interpolation factor.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to receive the
+ *     results (may be v0 or v1).
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.lerp = function(v0, v1, f, resultVec) {
+  var x = v0[0], y = v0[1], z = v0[2], w = v0[3];
+  resultVec[0] = (v1[0] - x) * f + x;
+  resultVec[1] = (v1[1] - y) * f + y;
+  resultVec[2] = (v1[2] - z) * f + z;
+  resultVec[3] = (v1[3] - w) * f + w;
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the larger values in resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec4f.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.max = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.max(vec0[0], limit);
+    resultVec[1] = Math.max(vec0[1], limit);
+    resultVec[2] = Math.max(vec0[2], limit);
+    resultVec[3] = Math.max(vec0[3], limit);
+  } else {
+    resultVec[0] = Math.max(vec0[0], limit[0]);
+    resultVec[1] = Math.max(vec0[1], limit[1]);
+    resultVec[2] = Math.max(vec0[2], limit[2]);
+    resultVec[3] = Math.max(vec0[3], limit[3]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Compares the components of vec0 with the components of another vector or
+ * scalar, storing the smaller values in resultVec.
+ *
+ * @param {!goog.vec.vec4f.Type} vec0 The source vector.
+ * @param {!goog.vec.vec4f.Type|number} limit The limit vector or scalar.
+ * @param {!goog.vec.vec4f.Type} resultVec The vector to receive the
+ *     results (may be vec0 or limit).
+ * @return {!goog.vec.vec4f.Type} Return resultVec so that operations can be
+ *     chained together.
+ */
+goog.vec.vec4f.min = function(vec0, limit, resultVec) {
+  if (goog.isNumber(limit)) {
+    resultVec[0] = Math.min(vec0[0], limit);
+    resultVec[1] = Math.min(vec0[1], limit);
+    resultVec[2] = Math.min(vec0[2], limit);
+    resultVec[3] = Math.min(vec0[3], limit);
+  } else {
+    resultVec[0] = Math.min(vec0[0], limit[0]);
+    resultVec[1] = Math.min(vec0[1], limit[1]);
+    resultVec[2] = Math.min(vec0[2], limit[2]);
+    resultVec[3] = Math.min(vec0[3], limit[3]);
+  }
+  return resultVec;
+};
+
+
+/**
+ * Returns true if the components of v0 are equal to the components of v1.
+ *
+ * @param {!goog.vec.vec4f.Type} v0 The first vector.
+ * @param {!goog.vec.vec4f.Type} v1 The second vector.
+ * @return {boolean} True if the vectors are equal, false otherwise.
+ */
+goog.vec.vec4f.equals = function(v0, v1) {
+  return v0.length == v1.length && v0[0] == v1[0] && v0[1] == v1[1] &&
+      v0[2] == v1[2] && v0[3] == v1[3];
+};

+ 11 - 0
public/lib/closure/vec/vec4f_test.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html><!-- DO NOT EDIT. This file auto-generated by generate_closure_unit_tests.js --><!--
+Copyright 2017 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+--><html><head><meta charset="UTF-8">
+
+<script src="../base.js"></script>
+
+<script>goog.require('goog.vec.vec4fTest');</script>
+<title>Closure Unit Tests - goog.vec.vec4fTest</title></head><body></body></html>

+ 206 - 0
public/lib/closure/vec/vec4f_test.js

@@ -0,0 +1,206 @@
+// Copyright 2013 The Closure Library Authors. All Rights Reserved.
+// Use of this source code is governed by the Apache License, Version 2.0.
+
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+//                                                                           //
+// Any edits to this file must be applied to vec4d_test.js by running:       //
+//   swap_type.sh vec4f_test.js > vec4d_test.js                              //
+//                                                                           //
+////////////////////////// NOTE ABOUT EDITING THIS FILE ///////////////////////
+
+goog.provide('goog.vec.vec4fTest');
+goog.setTestOnly('goog.vec.vec4fTest');
+
+goog.require('goog.testing.jsunit');
+goog.require('goog.vec.vec4f');
+
+function testCreate() {
+  var v = goog.vec.vec4f.create();
+  assertElementsEquals([0, 0, 0, 0], v);
+}
+
+function testCreateFromArray() {
+  var v = goog.vec.vec4f.createFromArray([1, 2, 3, 4]);
+  assertElementsEquals([1, 2, 3, 4], v);
+}
+
+function testCreateFromValues() {
+  var v = goog.vec.vec4f.createFromValues(1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], v);
+}
+
+function testClone() {
+  var v0 = goog.vec.vec4f.createFromValues(1, 2, 3, 4);
+  var v1 = goog.vec.vec4f.clone(v0);
+  assertElementsEquals([1, 2, 3, 4], v1);
+}
+
+function testSet() {
+  var v = goog.vec.vec4f.create();
+  goog.vec.vec4f.setFromValues(v, 1, 2, 3, 4);
+  assertElementsEquals([1, 2, 3, 4], v);
+
+  goog.vec.vec4f.setFromArray(v, [4, 5, 6, 7]);
+  assertElementsEquals([4, 5, 6, 7], v);
+}
+
+function testAdd() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [5, 6, 7, 8]);
+  var v2 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+
+  goog.vec.vec4f.add(v2, v1, v2);
+  assertElementsEquals([1, 2, 3, 4], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([6, 8, 10, 12], v2);
+
+  goog.vec.vec4f.add(goog.vec.vec4f.add(v0, v1, v2), v0, v2);
+  assertElementsEquals([7, 10, 13, 16], v2);
+}
+
+function testSubtract() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [4, 3, 2, 1]);
+  var v1 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [5, 6, 7, 8]);
+  var v2 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+
+  goog.vec.vec4f.subtract(v2, v1, v2);
+  assertElementsEquals([4, 3, 2, 1], v0);
+  assertElementsEquals([5, 6, 7, 8], v1);
+  assertElementsEquals([-1, -3, -5, -7], v2);
+
+  goog.vec.vec4f.setFromValues(v2, 0, 0, 0, 0);
+  goog.vec.vec4f.subtract(v1, v0, v2);
+  assertElementsEquals([1, 3, 5, 7], v2);
+
+  goog.vec.vec4f.subtract(goog.vec.vec4f.subtract(v1, v0, v2), v0, v2);
+  assertElementsEquals([-3, 0, 3, 6], v2);
+}
+
+function testNegate() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4f.create();
+
+  goog.vec.vec4f.negate(v0, v1);
+  assertElementsEquals([-1, -2, -3, -4], v1);
+  assertElementsEquals([1, 2, 3, 4], v0);
+
+  goog.vec.vec4f.negate(v0, v0);
+  assertElementsEquals([-1, -2, -3, -4], v0);
+}
+
+function testAbs() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [-1, -2, -3, -4]);
+  var v1 = goog.vec.vec4f.create();
+
+  goog.vec.vec4f.abs(v0, v1);
+  assertElementsEquals([1, 2, 3, 4], v1);
+  assertElementsEquals([-1, -2, -3, -4], v0);
+
+  goog.vec.vec4f.abs(v0, v0);
+  assertElementsEquals([1, 2, 3, 4], v0);
+}
+
+function testScale() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4f.create();
+
+  goog.vec.vec4f.scale(v0, 4, v1);
+  assertElementsEquals([4, 8, 12, 16], v1);
+  assertElementsEquals([1, 2, 3, 4], v0);
+
+  goog.vec.vec4f.setFromArray(v1, v0);
+  goog.vec.vec4f.scale(v1, 5, v1);
+  assertElementsEquals([5, 10, 15, 20], v1);
+}
+
+function testMagnitudeSquared() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [1, 2, 3, 4]);
+  assertEquals(30, goog.vec.vec4f.magnitudeSquared(v0));
+}
+
+function testMagnitude() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [1, 2, 3, 4]);
+  assertEquals(Math.sqrt(30), goog.vec.vec4f.magnitude(v0));
+}
+
+function testNormalize() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [2, 3, 4, 5]);
+  var v1 = goog.vec.vec4f.create();
+  var v2 = goog.vec.vec4f.create();
+  goog.vec.vec4f.scale(v0, 1 / goog.vec.vec4f.magnitude(v0), v2);
+
+  goog.vec.vec4f.normalize(v0, v1);
+  assertElementsEquals(v2, v1);
+  assertElementsEquals([2, 3, 4, 5], v0);
+
+  goog.vec.vec4f.setFromArray(v1, v0);
+  goog.vec.vec4f.normalize(v1, v1);
+  assertElementsEquals(v2, v1);
+}
+
+function testDot() {
+  var v0 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [1, 2, 3, 4]);
+  var v1 = goog.vec.vec4f.setFromArray(goog.vec.vec4f.create(), [5, 6, 7, 8]);
+  assertEquals(70, goog.vec.vec4f.dot(v0, v1));
+  assertEquals(70, goog.vec.vec4f.dot(v1, v0));
+}
+
+function testLerp() {
+  var v0 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 1, 2, 3, 4);
+  var v1 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 10, 20, 30, 40);
+  var v2 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+
+  goog.vec.vec4f.lerp(v2, v1, 0, v2);
+  assertElementsEquals([1, 2, 3, 4], v2);
+  goog.vec.vec4f.lerp(v2, v1, 1, v2);
+  assertElementsEquals([10, 20, 30, 40], v2);
+  goog.vec.vec4f.lerp(v0, v1, .5, v2);
+  assertElementsEquals([5.5, 11, 16.5, 22], v2);
+}
+
+function testMax() {
+  var v0 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 10, 20, 30, 40);
+  var v1 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 5, 25, 35, 30);
+  var v2 = goog.vec.vec4f.create();
+
+  goog.vec.vec4f.max(v0, v1, v2);
+  assertElementsEquals([10, 25, 35, 40], v2);
+  goog.vec.vec4f.max(v1, v0, v1);
+  assertElementsEquals([10, 25, 35, 40], v1);
+  goog.vec.vec4f.max(v2, 20, v2);
+  assertElementsEquals([20, 25, 35, 40], v2);
+}
+
+function testMin() {
+  var v0 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 10, 20, 30, 40);
+  var v1 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 5, 25, 35, 30);
+  var v2 = goog.vec.vec4f.create();
+
+  goog.vec.vec4f.min(v0, v1, v2);
+  assertElementsEquals([5, 20, 30, 30], v2);
+  goog.vec.vec4f.min(v1, v0, v1);
+  assertElementsEquals([5, 20, 30, 30], v1);
+  goog.vec.vec4f.min(v2, 20, v2);
+  assertElementsEquals([5, 20, 20, 20], v2);
+}
+
+function testEquals() {
+  var v0 = goog.vec.vec4f.setFromValues(goog.vec.vec4f.create(), 1, 2, 3, 4);
+  var v1 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+  assertElementsEquals(v0, v1);
+
+  v1[0] = 5;
+  assertFalse(goog.vec.vec4f.equals(v0, v1));
+
+  v1 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+  v1[1] = 5;
+  assertFalse(goog.vec.vec4f.equals(v0, v1));
+
+  v1 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+  v1[2] = 5;
+  assertFalse(goog.vec.vec4f.equals(v0, v1));
+
+  v1 = goog.vec.vec4f.setFromVec4f(goog.vec.vec4f.create(), v0);
+  v1[3] = 5;
+  assertFalse(goog.vec.vec4f.equals(v0, v1));
+}

+ 443 - 0
public/lib/closure/vec/vec_array_perf.html

@@ -0,0 +1,443 @@
+<!DOCTYPE html>
+<html>
+<!--
+Copyright 2011 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+
+-->
+<head>
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <title>Closure Performance Tests - Vector Array math</title>
+  <link rel="stylesheet" type="text/css"
+        href="../testing/performancetable.css"/>
+  <script type="text/javascript" src="../base.js"></script>
+  <script type="text/javascript">
+    goog.require('goog.testing.jsunit');
+    goog.require('goog.testing.PerformanceTable');
+    goog.require('goog.vec.Vec4');
+    goog.require('goog.vec.Mat4');
+  </script>
+</head>
+<body>
+  <h1>Closure Performance Tests - Vector Array Math</h1>
+  <p>
+    <strong>User-agent:</strong>
+    <script type="text/javascript">document.write(navigator.userAgent);</script>
+  </p>
+  <p>
+    These tests compare various methods of performing vector operations on
+    arrays of vectors.
+  </p>
+  <div id="perfTable"></div>
+  <hr>
+ <script type="text/javascript">
+
+var table = new goog.testing.PerformanceTable(
+    goog.dom.getElement('perfTable'));
+
+function createRandomFloat32Array(length) {
+  var array = new Float32Array(length);
+  for (var i = 0; i < length; i++) {
+    array[i] = Math.random();
+  }
+  return array;
+}
+
+function createRandomIndexArray(length) {
+  var array = [];
+  for (var i = 0; i < length; i++) {
+    array[i] = Math.floor(Math.random() * length);
+    array[i] = Math.min(length - 1, array[i]);
+  }
+  return array;
+}
+
+function createRandomVec4Array(length) {
+  var a = [];
+  for (var i = 0; i < length; i++) {
+    a[i] = goog.vec.Vec4.createFromValues(
+        Math.random(), Math.random(), Math.random(), Math.random());
+  }
+  return a;
+}
+
+function createRandomMat4() {
+  var m = goog.vec.Mat4.createFromValues(
+      Math.random(), Math.random(), Math.random(), Math.random(),
+      Math.random(), Math.random(), Math.random(), Math.random(),
+      Math.random(), Math.random(), Math.random(), Math.random(),
+      Math.random(), Math.random(), Math.random(), Math.random());
+  return m;
+}
+
+function createRandomMat4Array(length) {
+  var m = [];
+  for (var i = 0; i < length; i++) {
+    m[i] = createRandomMat4();
+  }
+  return m;
+}
+
+/**
+ * Vec4Object is a 4-vector object with x,y,z,w components.
+ * @param {number} x The x component.
+ * @param {number} y The y component.
+ * @param {number} z The z component.
+ * @param {number} w The w component.
+ * @constructor
+ */
+Vec4Object = function(x, y, z, w) {
+  this.x = x;
+  this.y = y;
+  this.z = z;
+  this.w = w;
+};
+
+/**
+ * Add two vectors.
+ * @param {Vec4Object} v0 A vector.
+ * @param {Vec4Object} v1 Another vector.
+ * @param {Vec4Object} r The result.
+ */
+Vec4Object.add = function(v0, v1, r) {
+  r.x = v0.x + v1.x;
+  r.y = v0.y + v1.y;
+  r.z = v0.z + v1.z;
+  r.w = v0.w + v1.w;
+};
+
+function createRandomVec4ObjectArray(length) {
+  var a = [];
+  for (var i = 0; i < length; i++) {
+    a[i] = new Vec4Object(
+        Math.random(), Math.random(), Math.random(), Math.random());
+  }
+  return a;
+}
+
+function setVec4FromArray(v, a, o) {
+  v[0] = a[o + 0];
+  v[1] = a[o + 1];
+  v[2] = a[o + 2];
+  v[3] = a[o + 3];
+}
+
+function setArrayFromVec4(a, o, v) {
+  a[o + 0] = v[0];
+  a[o + 1] = v[1];
+  a[o + 2] = v[2];
+  a[o + 3] = v[3];
+}
+
+/**
+ * This is the same as goog.vec.Vec4.add().  Use this to avoid namespace lookup
+ * overheads.
+ * @param {goog.vec.Vec4.Vec4Like} v0 A vector.
+ * @param {goog.vec.Vec4.Vec4Like} v1 Another vector.
+ * @param {goog.vec.Vec4.Vec4Like} r The result.
+ */
+function addVec4(v0, v1, r) {
+  r[0] = v0[0] + v1[0];
+  r[1] = v0[1] + v1[1];
+  r[2] = v0[2] + v1[2];
+  r[3] = v0[3] + v1[3];
+}
+
+function addVec4ByOffset(v0Buf, v0Off, v1Buf, v1Off, rBuf, rOff) {
+  rBuf[rOff + 0] = v0Buf[v0Off + 0] + v1Buf[v1Off + 0];
+  rBuf[rOff + 1] = v0Buf[v0Off + 1] + v1Buf[v1Off + 1];
+  rBuf[rOff + 2] = v0Buf[v0Off + 2] + v1Buf[v1Off + 2];
+  rBuf[rOff + 3] = v0Buf[v0Off + 3] + v1Buf[v1Off + 3];
+}
+
+function addVec4ByOptionalOffset(v0, v1, r, opt_v0Off, opt_v1Off, opt_rOff) {
+  if (opt_v0Off && opt_v1Off && opt_rOff) {
+    r[opt_rOff + 0] = v0[opt_v0Off + 0] + v1[opt_v1Off + 0];
+    r[opt_rOff + 1] = v0[opt_v0Off + 1] + v1[opt_v1Off + 1];
+    r[opt_rOff + 2] = v0[opt_v0Off + 2] + v1[opt_v1Off + 2];
+    r[opt_rOff + 3] = v0[opt_v0Off + 3] + v1[opt_v1Off + 3];
+  } else {
+    r[0] = v0[0] + v1[0];
+    r[1] = v0[1] + v1[1];
+    r[2] = v0[2] + v1[2];
+    r[3] = v0[3] + v1[3];
+  }
+}
+
+function mat4MultVec4ByOffset(mBuf, mOff, vBuf, vOff, rBuf, rOff) {
+  var x = vBuf[vOff + 0], y = vBuf[vOff + 1],
+      z = vBuf[vOff + 2], w = vBuf[vOff + 3];
+  rBuf[rOff + 0] = x * mBuf[mOff + 0] + y * mBuf[mOff + 4] +
+      z * mBuf[mOff + 8] + w * mBuf[mOff + 12];
+  rBuf[rOff + 1] = x * mBuf[mOff + 1] + y * mBuf[mOff + 5] +
+      z * mBuf[mOff + 9] + w * mBuf[mOff + 13];
+  rBuf[rOff + 2] = x * mBuf[mOff + 2] + y * mBuf[mOff + 6] +
+      z * mBuf[mOff + 10] + w * mBuf[mOff + 14];
+  rBuf[rOff + 3] = x * mBuf[mOff + 3] + y * mBuf[mOff + 7] +
+      z * mBuf[mOff + 11] + w * mBuf[mOff + 15];
+}
+
+var NUM_ITERATIONS = 200000;
+
+function testAddVec4ByOffset() {
+  var nVecs = NUM_ITERATIONS;
+  var nVals = nVecs * 4;
+  var a0 = createRandomFloat32Array(nVals);
+  var a1 = createRandomFloat32Array(nVals);
+  var a2 = new Float32Array(nVals);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          addVec4ByOffset(a0, i * 4, a1, i * 4, a2, i * 4);
+        }
+      },
+      'Add vectors using offsets');
+}
+
+function testAddVec4ByOptionalOffset() {
+  var nVecs = NUM_ITERATIONS;
+  var nVals = nVecs * 4;
+  var a0 = createRandomFloat32Array(nVals);
+  var a1 = createRandomFloat32Array(nVals);
+  var a2 = new Float32Array(nVals);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          addVec4ByOptionalOffset(a0, a1, a2, i * 4, i * 4, i * 4);
+        }
+      },
+      'Add vectors with optional offsets (requires branch)');
+}
+
+/**
+ * Check the overhead of using an array of individual
+ * Vec4s (Float32Arrays of length 4).
+ */
+function testAddVec4ByVec4s() {
+  var nVecs = NUM_ITERATIONS;
+  var a0 = createRandomVec4Array(nVecs);
+  var a1 = createRandomVec4Array(nVecs);
+  var a2 = createRandomVec4Array(nVecs);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          addVec4(a0[i], a1[i], a2[i]);
+        }
+      },
+      'Add vectors using an array of Vec4s (Float32Arrays of length 4)');
+}
+
+function testAddVec4ByTmp() {
+  var nVecs = NUM_ITERATIONS;
+  var nVals = nVecs * 4;
+  var a0 = createRandomFloat32Array(nVals);
+  var a1 = createRandomFloat32Array(nVals);
+  var a2 = new Float32Array(nVals);
+
+  table.run(
+      function() {
+        var t0 = new Float32Array(4);
+        var t1 = new Float32Array(4);
+        for (var i = 0; i < nVecs; i++) {
+          setVec4FromArray(t0, a0, i * 4);
+          setVec4FromArray(t1, a1, i * 4);
+          addVec4(t0, t1, t0);
+          setArrayFromVec4(a2, i * 4, t0);
+        }
+      },
+      'Add vectors using tmps');
+}
+
+/**
+ * Check the overhead of using an array of Objects with the implicit hash
+ * lookups for the x,y,z,w components.
+ */
+function testAddVec4ByObjects() {
+  var nVecs = NUM_ITERATIONS;
+  var a0 = createRandomVec4ObjectArray(nVecs);
+  var a1 = createRandomVec4ObjectArray(nVecs);
+  var a2 = createRandomVec4ObjectArray(nVecs);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          Vec4Object.add(a0[i], a1[i], a2[i]);
+        }
+      },
+      'Add vectors using an array of Objects ' +
+      '(with implicit hash lookups for the x,y,z,w components)');
+}
+
+function testAddVec4BySubarray() {
+  var nVecs = NUM_ITERATIONS;
+  var nVals = nVecs * 4;
+  var a0 = createRandomFloat32Array(nVals);
+  var a1 = createRandomFloat32Array(nVals);
+  var a2 = new Float32Array(nVals);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          var t0 = a0.subarray(i * 4 * 4);
+          var t1 = a1.subarray(i * 4 * 4);
+          var t2 = a2.subarray(i * 4 * 4);
+          addVec4(t0, t1, t2);
+        }
+      },
+      'Add vectors using Float32Array.subarray()');
+}
+
+function testAddVec4ByView() {
+  var nVecs = NUM_ITERATIONS;
+  var nVals = nVecs * 4;
+  var a0 = createRandomFloat32Array(nVals);
+  var a1 = createRandomFloat32Array(nVals);
+  var a2 = new Float32Array(nVals);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          var t0 = new Float32Array(a0.buffer, i * 4 * 4);
+          var t1 = new Float32Array(a1.buffer, i * 4 * 4);
+          var t2 = new Float32Array(a2.buffer, i * 4 * 4);
+          addVec4(t0, t1, t2);
+        }
+      },
+      'Add vectors using Float32 view');
+}
+
+function testMat4MultVec4ByOffset() {
+  var nVecs = NUM_ITERATIONS;
+  var nVecVals = nVecs * 4;
+  var nMatVals = nVecs * 16;
+  var m = createRandomFloat32Array(nMatVals);
+  var a0 = createRandomFloat32Array(nVecVals);
+  var a1 = new Float32Array(nVecVals);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          mat4MultVec4ByOffset(m, i * 16, a0, i * 4, a1, i * 4);
+        }
+      },
+      'vec4 = mat4 * vec4 using offsets.');
+}
+
+/**
+ * Check the overhead of using an array of individual
+ * Vec4s (Float32Arrays of length 4).
+ */
+function testMat4MultVec4ByVec4s() {
+  var nVecs = NUM_ITERATIONS;
+  var a0 = createRandomVec4Array(nVecs);
+  var a1 = createRandomVec4Array(nVecs);
+  var m = createRandomMat4Array(nVecs);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          goog.vec.Mat4.multVec4(m[i], a0[i], a1[i]);
+        }
+      },
+      'vec4 = mat4 * vec4  using arrays of Vec4s and Mat4s');
+}
+
+/**
+ * Do 10x as many for the one vector tests.
+ * @type {number}
+ */
+var NUM_ONE_ITERATIONS = NUM_ITERATIONS * 10;
+
+function testAddOneVec4ByOffset() {
+  var a0 = createRandomFloat32Array(4);
+  var a1 = createRandomFloat32Array(4);
+  var a2 = new Float32Array(4);
+
+  table.run(
+      function() {
+        for (var i = 0; i < NUM_ONE_ITERATIONS; i++) {
+          addVec4ByOffset(a0, 0, a1, 0, a2, 0);
+        }
+      },
+      'Add one vector using offset of 0');
+}
+
+function testAddOneVec4() {
+  var a0 = createRandomFloat32Array(4);
+  var a1 = createRandomFloat32Array(4);
+  var a2 = new Float32Array(4);
+
+  table.run(
+      function() {
+        for (var i = 0; i < NUM_ONE_ITERATIONS; i++) {
+          addVec4(a0, a1, a2);
+        }
+      },
+      'Add one vector');
+}
+
+function testAddOneVec4ByOptionalOffset() {
+  var a0 = createRandomFloat32Array(4);
+  var a1 = createRandomFloat32Array(4);
+  var a2 = new Float32Array(4);
+
+  table.run(
+      function() {
+        for (var i = 0; i < NUM_ONE_ITERATIONS; i++) {
+          addVec4ByOptionalOffset(a0, a1, a2);
+        }
+      },
+      'Add one vector with optional offsets (requires branch)');
+}
+
+function testAddRandomVec4ByOffset() {
+  var nVecs = NUM_ITERATIONS;
+  var nVals = nVecs * 4;
+  var a0 = createRandomFloat32Array(nVals);
+  var a1 = createRandomFloat32Array(nVals);
+  var a2 = new Float32Array(nVals);
+  var i0 = createRandomIndexArray(nVecs);
+  var i1 = createRandomIndexArray(nVecs);
+  var i2 = createRandomIndexArray(nVecs);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          addVec4ByOffset(a0, i0[i] * 4, a1, i1[i] * 4, a2, i2[i] * 4);
+        }
+      },
+      'Add random vectors using offsets');
+}
+
+function testAddRandomVec4ByVec4s() {
+  var nVecs = NUM_ITERATIONS;
+  var a0 = createRandomVec4Array(nVecs);
+  var a1 = createRandomVec4Array(nVecs);
+  var a2 = createRandomVec4Array(nVecs);
+  var i0 = createRandomIndexArray(nVecs);
+  var i1 = createRandomIndexArray(nVecs);
+  var i2 = createRandomIndexArray(nVecs);
+
+  table.run(
+      function() {
+        for (var i = 0; i < nVecs; i++) {
+          addVec4(a0[i0[i]], a1[i1[i]], a2[i2[i]]);
+        }
+      },
+      'Add random vectors using an array of Vec4s');
+}
+
+// Make sure the tests are run in the order they are defined.
+var testCase = new goog.testing.TestCase(document.title);
+testCase.order = goog.testing.TestCase.Order.NATURAL;
+testCase.autoDiscoverTests();
+G_testRunner.initialize(testCase);
+
+ </script>
+</body>
+</html>

+ 101 - 0
public/lib/closure/vec/vec_perf.html

@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<!--
+Copyright 2011 The Closure Library Authors. All Rights Reserved.
+
+Use of this source code is governed by the Apache License, Version 2.0.
+See the COPYING file for details.
+
+Author: nicksantos@google.com (Nick Santos)
+-->
+<head>
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <title>Closure Performance Tests - Vector math</title>
+  <link rel="stylesheet" type="text/css"
+        href="../testing/performancetable.css"/>
+  <script src="../base.js"></script>
+  <script>
+    goog.require('goog.crypt');
+    goog.require('goog.string');
+    goog.require('goog.testing.PerformanceTable');
+    goog.require('goog.testing.jsunit');
+    goog.require('goog.vec.Vec4');
+  </script>
+</head>
+<body>
+  <h1>Closure Performance Tests - Vector Math</h1>
+  <p>
+    <strong>User-agent:</strong>
+    <script>document.write(navigator.userAgent);</script>
+  </p>
+  <div id="perfTable"></div>
+  <hr>
+
+  <script>
+    var table = new goog.testing.PerformanceTable(
+        goog.dom.getElement('perfTable'));
+    var createVec4FromValues = goog.vec.Vec4.createFromValues;
+    var scaleVec4 = goog.vec.Vec4.scale;
+
+    var negateVec4ByScaling = function(v, result) {
+      return scaleVec4(v, -1, result);
+    };
+
+    var negateVec4ByNegation = function(v, result) {
+      result[0] = -v[0];
+      result[1] = -v[1];
+      result[2] = -v[2];
+      result[3] = -v[3];
+      return result;
+    };
+
+    var negateVec4ByMultiplication = function(v, result) {
+      result[0] = -1 * v[0];
+      result[1] = -1 * v[1];
+      result[2] = -1 * v[2];
+      result[3] = -1 * v[3];
+      return result;
+    };
+
+    function createRandomVec4() {
+      return createVec4FromValues(
+          Math.random(),
+          Math.random(),
+          Math.random(),
+          Math.random());
+    }
+
+    function testNegateVec4ByScaling() {
+      var v = createRandomVec4();
+      for (var i = 0; i < 2000000; i++) {
+        // Warm the trace tree to see if that makes a difference.
+        scaleVec4(v, 1, v);
+      }
+
+      table.run(
+          function() {
+            for (var i = 0; i < 2000000; i++) {
+              negateVec4ByScaling(v, v);
+            }
+          },
+          'Negate vector by calling scale()');
+    }
+
+    function testNegateVec4ByNegation() {
+      var v = createRandomVec4();
+      for (var i = 0; i < 2000000; i++) {
+        // Warm the trace tree to see if that makes a difference.
+        scaleVec4(v, 1, v);
+      }
+
+      table.run(
+          function() {
+            for (var i = 0; i < 2000000; i++) {
+              negateVec4ByNegation(v, v);
+            }
+          },
+          'Negate vector by negating directly');
+    }
+  </script>
+</body>
+</html>

+ 8 - 0
public/vwf/model/aframe/addon/aframe-components.js

@@ -131,6 +131,14 @@ AFRAME.registerComponent('gizmo', {
 
                     break;
                 case 'rotate':
+                // let q = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
+                //     (object.rotation.x),
+                //     (object.rotation.y),
+                //     (object.rotation.z), 'XYZ'
+                //   ));
+                // let angle = (new THREE.Euler()).setFromQuaternion(q, 'YXZ');
+
+                // vwf_view.kernel.setProperty(object.el.id, 'rotation', [THREE.Math.radToDeg(angle.x), THREE.Math.radToDeg(angle.y), THREE.Math.radToDeg(angle.z)])
                     vwf_view.kernel.setProperty(object.el.id, 'rotation',
                         [THREE.Math.radToDeg(object.rotation.x), THREE.Math.radToDeg(object.rotation.y), THREE.Math.radToDeg(object.rotation.z)])
 

+ 3 - 3
public/vwf/model/aframe/addon/aframe-interpolation.js

@@ -182,20 +182,20 @@ AFRAME.registerComponent('interpolation', {
         let lastV = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
           (last.x),
           (last.y),
-          (last.z), 'YXZ'
+          (last.z), last.order//'YXZ'
         ));
 
         let nowV = (new THREE.Quaternion()).setFromEuler(new THREE.Euler(
           (now.x),
           (now.y),
-          (now.z), 'YXZ'
+          (now.z), last.order//'YXZ'
         ));
 
         let q = new THREE.Quaternion();
         let e = new THREE.Euler();
 
         THREE.Quaternion.slerp(lastV, nowV, q, step || 0);
-        let interp = e.setFromQuaternion(q, 'YXZ');
+        let interp = e.setFromQuaternion(q, last.order); //'YXZ');
 
         //this.el.object3D.rotation.set(interp.x, interp.y, interp.z);
         this.setRotation(interp);

Some files were not shown because too many files changed in this diff