From 0d9dde8cbc401dfb3762c474e1ac4c95496108fc Mon Sep 17 00:00:00 2001
From: raed22 <abdennadher.raed@gmail.com>
Date: Fri, 30 Oct 2020 12:46:47 +0100
Subject: [PATCH] Add lab3 files

---
 lab3/README.md                |   6 +
 lab3/docs/Cours 3D Labo 3.pdf | Bin 0 -> 32952 bytes
 lab3/lib/cuon-utils.js        | 113 ++++++
 lab3/lib/webgl-debug.js       | 677 ++++++++++++++++++++++++++++++++++
 lab3/lib/webgl-utils.js       | 197 ++++++++++
 lab3/src/lab3.html            |  16 +
 lab3/src/lab3.js              |  18 +
 7 files changed, 1027 insertions(+)
 create mode 100644 lab3/README.md
 create mode 100644 lab3/docs/Cours 3D Labo 3.pdf
 create mode 100644 lab3/lib/cuon-utils.js
 create mode 100644 lab3/lib/webgl-debug.js
 create mode 100644 lab3/lib/webgl-utils.js
 create mode 100644 lab3/src/lab3.html
 create mode 100644 lab3/src/lab3.js

diff --git a/lab3/README.md b/lab3/README.md
new file mode 100644
index 0000000..37ef2aa
--- /dev/null
+++ b/lab3/README.md
@@ -0,0 +1,6 @@
+# Lab3 IHM
+
+* **docs**: contains the lab's statement
+* **libs**: contains WebGL libraries and utilities
+* **src**: contains the source code of the lab2 that you should complete (`main()` function in **lab3.js** file)
+* **DO NOT CHANGE** the name of the file `lab3.html`, however, you are free to change its content.
diff --git a/lab3/docs/Cours 3D Labo 3.pdf b/lab3/docs/Cours 3D Labo 3.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..95be1783746c753299fd5ae3d412b90a2c39ad9d
GIT binary patch
literal 32952
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

literal 0
HcmV?d00001

diff --git a/lab3/lib/cuon-utils.js b/lab3/lib/cuon-utils.js
new file mode 100644
index 0000000..dc08b2e
--- /dev/null
+++ b/lab3/lib/cuon-utils.js
@@ -0,0 +1,113 @@
+// cuon-utils.js (c) 2012 kanda and matsuda
+/**
+ * Create a program object and make current
+ * @param gl GL context
+ * @param vshader a vertex shader program (string)
+ * @param fshader a fragment shader program (string)
+ * @return true, if the program object was created and successfully made current 
+ */
+function initShaders(gl, vshader, fshader) {
+  var program = createProgram(gl, vshader, fshader);
+  if (!program) {
+    console.log('Failed to create program');
+    return false;
+  }
+
+  gl.useProgram(program);
+  gl.program = program;
+
+  return true;
+}
+
+/**
+ * Create the linked program object
+ * @param gl GL context
+ * @param vshader a vertex shader program (string)
+ * @param fshader a fragment shader program (string)
+ * @return created program object, or null if the creation has failed
+ */
+function createProgram(gl, vshader, fshader) {
+  // Create shader object
+  var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
+  var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
+  if (!vertexShader || !fragmentShader) {
+    return null;
+  }
+
+  // Create a program object
+  var program = gl.createProgram();
+  if (!program) {
+    return null;
+  }
+
+  // Attach the shader objects
+  gl.attachShader(program, vertexShader);
+  gl.attachShader(program, fragmentShader);
+
+  // Link the program object
+  gl.linkProgram(program);
+
+  // Check the result of linking
+  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+  if (!linked) {
+    var error = gl.getProgramInfoLog(program);
+    console.log('Failed to link program: ' + error);
+    gl.deleteProgram(program);
+    gl.deleteShader(fragmentShader);
+    gl.deleteShader(vertexShader);
+    return null;
+  }
+  return program;
+}
+
+/**
+ * Create a shader object
+ * @param gl GL context
+ * @param type the type of the shader object to be created
+ * @param source shader program (string)
+ * @return created shader object, or null if the creation has failed.
+ */
+function loadShader(gl, type, source) {
+  // Create shader object
+  var shader = gl.createShader(type);
+  if (shader == null) {
+    console.log('unable to create shader');
+    return null;
+  }
+
+  // Set the shader program
+  gl.shaderSource(shader, source);
+
+  // Compile the shader
+  gl.compileShader(shader);
+
+  // Check the result of compilation
+  var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+  if (!compiled) {
+    var error = gl.getShaderInfoLog(shader);
+    console.log('Failed to compile shader: ' + error);
+    gl.deleteShader(shader);
+    return null;
+  }
+
+  return shader;
+}
+
+/** 
+ * Initialize and get the rendering for WebglInstance
+ * @param canvas <cavnas> element
+ * @param opt_debug flag to initialize the context for debugging
+ * @return the rendering context for WebglInstance
+ */
+function getWebGLContext(canvas, opt_debug) {
+  // Get the rendering context for WebglInstance
+  var gl = WebGLUtils.setupWebGL(canvas);
+  if (!gl) return null;
+
+  // if opt_debug is explicitly false, create the context for debugging
+  if (arguments.length < 2 || opt_debug) {
+    gl = WebGLDebugUtils.makeDebugContext(gl);
+  }
+
+  return gl;
+}
diff --git a/lab3/lib/webgl-debug.js b/lab3/lib/webgl-debug.js
new file mode 100644
index 0000000..685868d
--- /dev/null
+++ b/lab3/lib/webgl-debug.js
@@ -0,0 +1,677 @@
+//Copyright (c) 2009 The Chromium Authors. All rights reserved.
+//Use of this source code is governed by a BSD-style license that can be
+//found in the LICENSE file.
+
+// Various functions for helping debug WebglInstance apps.
+
+WebGLDebugUtils = function() {
+
+/**
+ * Wrapped logging function.
+ * @param {string} msg Message to log.
+ */
+var log = function(msg) {
+  if (window.console && window.console.log) {
+    window.console.log(msg);
+  }
+};
+
+/**
+ * Which arguements are enums.
+ * @type {!Object.<number, string>}
+ */
+var glValidEnumContexts = {
+
+  // Generic setters and getters
+
+  'enable': { 0:true },
+  'disable': { 0:true },
+  'getParameter': { 0:true },
+
+  // Rendering
+
+  'drawArrays': { 0:true },
+  'drawElements': { 0:true, 2:true },
+
+  // Shaders
+
+  'createShader': { 0:true },
+  'getShaderParameter': { 1:true },
+  'getProgramParameter': { 1:true },
+
+  // Vertex attributes
+
+  'getVertexAttrib': { 1:true },
+  'vertexAttribPointer': { 2:true },
+
+  // Textures
+
+  'bindTexture': { 0:true },
+  'activeTexture': { 0:true },
+  'getTexParameter': { 0:true, 1:true },
+  'texParameterf': { 0:true, 1:true },
+  'texParameteri': { 0:true, 1:true, 2:true },
+  'texImage2D': { 0:true, 2:true, 6:true, 7:true },
+  'texSubImage2D': { 0:true, 6:true, 7:true },
+  'copyTexImage2D': { 0:true, 2:true },
+  'copyTexSubImage2D': { 0:true },
+  'generateMipmap': { 0:true },
+
+  // Buffer objects
+
+  'bindBuffer': { 0:true },
+  'bufferData': { 0:true, 2:true },
+  'bufferSubData': { 0:true },
+  'getBufferParameter': { 0:true, 1:true },
+
+  // Renderbuffers and framebuffers
+
+  'pixelStorei': { 0:true, 1:true },
+  'readPixels': { 4:true, 5:true },
+  'bindRenderbuffer': { 0:true },
+  'bindFramebuffer': { 0:true },
+  'checkFramebufferStatus': { 0:true },
+  'framebufferRenderbuffer': { 0:true, 1:true, 2:true },
+  'framebufferTexture2D': { 0:true, 1:true, 2:true },
+  'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true },
+  'getRenderbufferParameter': { 0:true, 1:true },
+  'renderbufferStorage': { 0:true, 1:true },
+
+  // Frame buffer operations (clear, blend, depth test, stencil)
+
+  'clear': { 0:true },
+  'depthFunc': { 0:true },
+  'blendFunc': { 0:true, 1:true },
+  'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true },
+  'blendEquation': { 0:true },
+  'blendEquationSeparate': { 0:true, 1:true },
+  'stencilFunc': { 0:true },
+  'stencilFuncSeparate': { 0:true, 1:true },
+  'stencilMaskSeparate': { 0:true },
+  'stencilOp': { 0:true, 1:true, 2:true },
+  'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true },
+
+  // Culling
+
+  'cullFace': { 0:true },
+  'frontFace': { 0:true },
+};
+
+/**
+ * Map of numbers to names.
+ * @type {Object}
+ */
+var glEnums = null;
+
+/**
+ * Initializes this module. Safe to call more than once.
+ * @param {!WebGLRenderingContext} ctx A WebglInstance context. If
+ *    you have more than one context it doesn't matter which one
+ *    you pass in, it is only used to pull out constants.
+ */
+function init(ctx) {
+  if (glEnums == null) {
+    glEnums = { };
+    for (var propertyName in ctx) {
+      if (typeof ctx[propertyName] == 'number') {
+        glEnums[ctx[propertyName]] = propertyName;
+      }
+    }
+  }
+}
+
+/**
+ * Checks the utils have been initialized.
+ */
+function checkInit() {
+  if (glEnums == null) {
+    throw 'WebGLDebugUtils.init(ctx) not called';
+  }
+}
+
+/**
+ * Returns true or false if value matches any WebglInstance enum
+ * @param {*} value Value to check if it might be an enum.
+ * @return {boolean} True if value matches one of the WebglInstance defined enums
+ */
+function mightBeEnum(value) {
+  checkInit();
+  return (glEnums[value] !== undefined);
+}
+
+/**
+ * Gets an string version of an WebglInstance enum.
+ *
+ * Example:
+ *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
+ *
+ * @param {number} value Value to return an enum for
+ * @return {string} The string version of the enum.
+ */
+function glEnumToString(value) {
+  checkInit();
+  var name = glEnums[value];
+  return (name !== undefined) ? name :
+      ("*UNKNOWN WebglInstance ENUM (0x" + value.toString(16) + ")");
+}
+
+/**
+ * Returns the string version of a WebglInstance argument.
+ * Attempts to convert enum arguments to strings.
+ * @param {string} functionName the name of the WebglInstance function.
+ * @param {number} argumentIndx the index of the argument.
+ * @param {*} value The value of the argument.
+ * @return {string} The value as a string.
+ */
+function glFunctionArgToString(functionName, argumentIndex, value) {
+  var funcInfo = glValidEnumContexts[functionName];
+  if (funcInfo !== undefined) {
+    if (funcInfo[argumentIndex]) {
+      return glEnumToString(value);
+    }
+  }
+  return value.toString();
+}
+
+/**
+ * Given a WebglInstance context returns a wrapped context that calls
+ * gl.getError after every command and calls a function if the
+ * result is not gl.NO_ERROR.
+ *
+ * @param {!WebGLRenderingContext} ctx The webgl context to
+ *        wrap.
+ * @param {!function(err, funcName, args): void} opt_onErrorFunc
+ *        The function to call when gl.getError returns an
+ *        error. If not specified the default function calls
+ *        console.log with a message.
+ */
+function makeDebugContext(ctx, opt_onErrorFunc) {
+  init(ctx);
+  opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) {
+        // apparently we can't do args.join(",");
+        var argStr = "";
+        for (var ii = 0; ii < args.length; ++ii) {
+          argStr += ((ii == 0) ? '' : ', ') +
+              glFunctionArgToString(functionName, ii, args[ii]);
+        }
+        log("WebglInstance error "+ glEnumToString(err) + " in "+ functionName +
+            "(" + argStr + ")");
+      };
+
+  // Holds booleans for each GL error so after we get the error ourselves
+  // we can still return it to the client app.
+  var glErrorShadow = { };
+
+  // Makes a function that calls a WebglInstance function and then calls getError.
+  function makeErrorWrapper(ctx, functionName) {
+    return function() {
+      var result = ctx[functionName].apply(ctx, arguments);
+      var err = ctx.getError();
+      if (err != 0) {
+        glErrorShadow[err] = true;
+        opt_onErrorFunc(err, functionName, arguments);
+      }
+      return result;
+    };
+  }
+
+  // Make a an object that has a copy of every property of the WebglInstance context
+  // but wraps all functions.
+  var wrapper = {};
+  for (var propertyName in ctx) {
+    if (typeof ctx[propertyName] == 'function') {
+       wrapper[propertyName] = makeErrorWrapper(ctx, propertyName);
+     } else {
+       wrapper[propertyName] = ctx[propertyName];
+     }
+  }
+
+  // Override the getError function with one that returns our saved results.
+  wrapper.getError = function() {
+    for (var err in glErrorShadow) {
+      if (glErrorShadow[err]) {
+        glErrorShadow[err] = false;
+        return err;
+      }
+    }
+    return ctx.NO_ERROR;
+  };
+
+  return wrapper;
+}
+
+function resetToInitialState(ctx) {
+  var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
+  var tmp = ctx.createBuffer();
+  ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp);
+  for (var ii = 0; ii < numAttribs; ++ii) {
+    ctx.disableVertexAttribArray(ii);
+    ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0);
+    ctx.vertexAttrib1f(ii, 0);
+  }
+  ctx.deleteBuffer(tmp);
+
+  var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
+  for (var ii = 0; ii < numTextureUnits; ++ii) {
+    ctx.activeTexture(ctx.TEXTURE0 + ii);
+    ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null);
+    ctx.bindTexture(ctx.TEXTURE_2D, null);
+  }
+
+  ctx.activeTexture(ctx.TEXTURE0);
+  ctx.useProgram(null);
+  ctx.bindBuffer(ctx.ARRAY_BUFFER, null);
+  ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null);
+  ctx.bindFramebuffer(ctx.FRAMEBUFFER, null);
+  ctx.bindRenderbuffer(ctx.RENDERBUFFER, null);
+  ctx.disable(ctx.BLEND);
+  ctx.disable(ctx.CULL_FACE);
+  ctx.disable(ctx.DEPTH_TEST);
+  ctx.disable(ctx.DITHER);
+  ctx.disable(ctx.SCISSOR_TEST);
+  ctx.blendColor(0, 0, 0, 0);
+  ctx.blendEquation(ctx.FUNC_ADD);
+  ctx.blendFunc(ctx.ONE, ctx.ZERO);
+  ctx.clearColor(0, 0, 0, 0);
+  ctx.clearDepth(1);
+  ctx.clearStencil(-1);
+  ctx.colorMask(true, true, true, true);
+  ctx.cullFace(ctx.BACK);
+  ctx.depthFunc(ctx.LESS);
+  ctx.depthMask(true);
+  ctx.depthRange(0, 1);
+  ctx.frontFace(ctx.CCW);
+  ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE);
+  ctx.lineWidth(1);
+  ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4);
+  ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4);
+  ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false);
+  ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+  // TODO: Delete this IF.
+  if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) {
+    ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL);
+  }
+  ctx.polygonOffset(0, 0);
+  ctx.sampleCoverage(1, false);
+  ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height);
+  ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF);
+  ctx.stencilMask(0xFFFFFFFF);
+  ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP);
+  ctx.viewport(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
+  ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT);
+
+  // TODO: This should NOT be needed but Firefox fails with 'hint'
+  while(ctx.getError());
+}
+
+function makeLostContextSimulatingContext(ctx) {
+  var wrapper_ = {};
+  var contextId_ = 1;
+  var contextLost_ = false;
+  var resourceId_ = 0;
+  var resourceDb_ = [];
+  var onLost_ = undefined;
+  var onRestored_ = undefined;
+  var nextOnRestored_ = undefined;
+
+  // Holds booleans for each GL error so can simulate errors.
+  var glErrorShadow_ = { };
+
+  function isWebGLObject(obj) {
+    //return false;
+    return (obj instanceof WebGLBuffer ||
+            obj instanceof WebGLFramebuffer ||
+            obj instanceof WebGLProgram ||
+            obj instanceof WebGLRenderbuffer ||
+            obj instanceof WebGLShader ||
+            obj instanceof WebGLTexture);
+  }
+
+  function checkResources(args) {
+    for (var ii = 0; ii < args.length; ++ii) {
+      var arg = args[ii];
+      if (isWebGLObject(arg)) {
+        return arg.__webglDebugContextLostId__ == contextId_;
+      }
+    }
+    return true;
+  }
+
+  function clearErrors() {
+    var k = Object.keys(glErrorShadow_);
+    for (var ii = 0; ii < k.length; ++ii) {
+      delete glErrorShdow_[k];
+    }
+  }
+
+  // Makes a function that simulates WebglInstance when out of context.
+  function makeLostContextWrapper(ctx, functionName) {
+    var f = ctx[functionName];
+    return function() {
+      // Only call the functions if the context is not lost.
+      if (!contextLost_) {
+        if (!checkResources(arguments)) {
+          glErrorShadow_[ctx.INVALID_OPERATION] = true;
+          return;
+        }
+        var result = f.apply(ctx, arguments);
+        return result;
+      }
+    };
+  }
+
+  for (var propertyName in ctx) {
+    if (typeof ctx[propertyName] == 'function') {
+       wrapper_[propertyName] = makeLostContextWrapper(ctx, propertyName);
+     } else {
+       wrapper_[propertyName] = ctx[propertyName];
+     }
+  }
+
+  function makeWebGLContextEvent(statusMessage) {
+    return {statusMessage: statusMessage};
+  }
+
+  function freeResources() {
+    for (var ii = 0; ii < resourceDb_.length; ++ii) {
+      var resource = resourceDb_[ii];
+      if (resource instanceof WebGLBuffer) {
+        ctx.deleteBuffer(resource);
+      } else if (resource instanceof WebctxFramebuffer) {
+        ctx.deleteFramebuffer(resource);
+      } else if (resource instanceof WebctxProgram) {
+        ctx.deleteProgram(resource);
+      } else if (resource instanceof WebctxRenderbuffer) {
+        ctx.deleteRenderbuffer(resource);
+      } else if (resource instanceof WebctxShader) {
+        ctx.deleteShader(resource);
+      } else if (resource instanceof WebctxTexture) {
+        ctx.deleteTexture(resource);
+      }
+    }
+  }
+
+  wrapper_.loseContext = function() {
+    if (!contextLost_) {
+      contextLost_ = true;
+      ++contextId_;
+      while (ctx.getError());
+      clearErrors();
+      glErrorShadow_[ctx.CONTEXT_LOST_WEBGL] = true;
+      setTimeout(function() {
+          if (onLost_) {
+            onLost_(makeWebGLContextEvent("context lost"));
+          }
+        }, 0);
+    }
+  };
+
+  wrapper_.restoreContext = function() {
+    if (contextLost_) {
+      if (onRestored_) {
+        setTimeout(function() {
+            freeResources();
+            resetToInitialState(ctx);
+            contextLost_ = false;
+            if (onRestored_) {
+              var callback = onRestored_;
+              onRestored_ = nextOnRestored_;
+              nextOnRestored_ = undefined;
+              callback(makeWebGLContextEvent("context restored"));
+            }
+          }, 0);
+      } else {
+        throw "You can not restore the context without a listener"
+      }
+    }
+  };
+
+  // Wrap a few functions specially.
+  wrapper_.getError = function() {
+    if (!contextLost_) {
+      var err;
+      while (err = ctx.getError()) {
+        glErrorShadow_[err] = true;
+      }
+    }
+    for (var err in glErrorShadow_) {
+      if (glErrorShadow_[err]) {
+        delete glErrorShadow_[err];
+        return err;
+      }
+    }
+    return ctx.NO_ERROR;
+  };
+
+  var creationFunctions = [
+    "createBuffer",
+    "createFramebuffer",
+    "createProgram",
+    "createRenderbuffer",
+    "createShader",
+    "createTexture"
+  ];
+  for (var ii = 0; ii < creationFunctions.length; ++ii) {
+    var functionName = creationFunctions[ii];
+    wrapper_[functionName] = function(f) {
+      return function() {
+        if (contextLost_) {
+          return null;
+        }
+        var obj = f.apply(ctx, arguments);
+        obj.__webglDebugContextLostId__ = contextId_;
+        resourceDb_.push(obj);
+        return obj;
+      };
+    }(ctx[functionName]);
+  }
+
+  var functionsThatShouldReturnNull = [
+    "getActiveAttrib",
+    "getActiveUniform",
+    "getBufferParameter",
+    "getContextAttributes",
+    "getAttachedShaders",
+    "getFramebufferAttachmentParameter",
+    "getParameter",
+    "getProgramParameter",
+    "getProgramInfoLog",
+    "getRenderbufferParameter",
+    "getShaderParameter",
+    "getShaderInfoLog",
+    "getShaderSource",
+    "getTexParameter",
+    "getUniform",
+    "getUniformLocation",
+    "getVertexAttrib"
+  ];
+  for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) {
+    var functionName = functionsThatShouldReturnNull[ii];
+    wrapper_[functionName] = function(f) {
+      return function() {
+        if (contextLost_) {
+          return null;
+        }
+        return f.apply(ctx, arguments);
+      }
+    }(wrapper_[functionName]);
+  }
+
+  var isFunctions = [
+    "isBuffer",
+    "isEnabled",
+    "isFramebuffer",
+    "isProgram",
+    "isRenderbuffer",
+    "isShader",
+    "isTexture"
+  ];
+  for (var ii = 0; ii < isFunctions.length; ++ii) {
+    var functionName = isFunctions[ii];
+    wrapper_[functionName] = function(f) {
+      return function() {
+        if (contextLost_) {
+          return false;
+        }
+        return f.apply(ctx, arguments);
+      }
+    }(wrapper_[functionName]);
+  }
+
+  wrapper_.checkFramebufferStatus = function(f) {
+    return function() {
+      if (contextLost_) {
+        return ctx.FRAMEBUFFER_UNSUPPORTED;
+      }
+      return f.apply(ctx, arguments);
+    };
+  }(wrapper_.checkFramebufferStatus);
+
+  wrapper_.getAttribLocation = function(f) {
+    return function() {
+      if (contextLost_) {
+        return -1;
+      }
+      return f.apply(ctx, arguments);
+    };
+  }(wrapper_.getAttribLocation);
+
+  wrapper_.getVertexAttribOffset = function(f) {
+    return function() {
+      if (contextLost_) {
+        return 0;
+      }
+      return f.apply(ctx, arguments);
+    };
+  }(wrapper_.getVertexAttribOffset);
+
+  wrapper_.isContextLost = function() {
+    return contextLost_;
+  };
+
+  function wrapEvent(listener) {
+    if (typeof(listener) == "function") {
+      return listener;
+    } else {
+      return function(info) {
+        listener.handleEvent(info);
+      }
+    }
+  }
+
+  wrapper_.registerOnContextLostListener = function(listener) {
+    onLost_ = wrapEvent(listener);
+  };
+
+  wrapper_.registerOnContextRestoredListener = function(listener) {
+    if (contextLost_) {
+      nextOnRestored_ = wrapEvent(listener);
+    } else {
+      onRestored_ = wrapEvent(listener);
+    }
+  }
+
+  return wrapper_;
+}
+
+return {
+  /**
+   * Initializes this module. Safe to call more than once.
+   * @param {!WebGLRenderingContext} ctx A WebglInstance context. If
+   *    you have more than one context it doesn't matter which one
+   *    you pass in, it is only used to pull out constants.
+   */
+  'init': init,
+
+  /**
+   * Returns true or false if value matches any WebglInstance enum
+   * @param {*} value Value to check if it might be an enum.
+   * @return {boolean} True if value matches one of the WebglInstance defined enums
+   */
+  'mightBeEnum': mightBeEnum,
+
+  /**
+   * Gets an string version of an WebglInstance enum.
+   *
+   * Example:
+   *   WebGLDebugUtil.init(ctx);
+   *   var str = WebGLDebugUtil.glEnumToString(ctx.getError());
+   *
+   * @param {number} value Value to return an enum for
+   * @return {string} The string version of the enum.
+   */
+  'glEnumToString': glEnumToString,
+
+  /**
+   * Converts the argument of a WebglInstance function to a string.
+   * Attempts to convert enum arguments to strings.
+   *
+   * Example:
+   *   WebGLDebugUtil.init(ctx);
+   *   var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 0, gl.TEXTURE_2D);
+   *
+   * would return 'TEXTURE_2D'
+   *
+   * @param {string} functionName the name of the WebglInstance function.
+   * @param {number} argumentIndx the index of the argument.
+   * @param {*} value The value of the argument.
+   * @return {string} The value as a string.
+   */
+  'glFunctionArgToString': glFunctionArgToString,
+
+  /**
+   * Given a WebglInstance context returns a wrapped context that calls
+   * gl.getError after every command and calls a function if the
+   * result is not NO_ERROR.
+   *
+   * You can supply your own function if you want. For example, if you'd like
+   * an exception thrown on any GL error you could do this
+   *
+   *    function throwOnGLError(err, funcName, args) {
+   *      throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to" +
+   *            funcName;
+   *    };
+   *
+   *    ctx = WebGLDebugUtils.makeDebugContext(
+   *        canvas.getContext("webgl"), throwOnGLError);
+   *
+   * @param {!WebGLRenderingContext} ctx The webgl context to wrap.
+   * @param {!function(err, funcName, args): void} opt_onErrorFunc The function
+   *     to call when gl.getError returns an error. If not specified the default
+   *     function calls console.log with a message.
+   */
+  'makeDebugContext': makeDebugContext,
+
+  /**
+   * Given a WebglInstance context returns a wrapped context that adds 4
+   * functions.
+   *
+   * ctx.loseContext:
+   *   simulates a lost context event.
+   *
+   * ctx.restoreContext:
+   *   simulates the context being restored.
+   *
+   * ctx.registerOnContextLostListener(listener):
+   *   lets you register a listener for context lost. Use instead
+   *   of addEventListener('webglcontextlostevent', listener);
+   *
+   * ctx.registerOnContextRestoredListener(listener):
+   *   lets you register a listener for context restored. Use
+   *   instead of addEventListener('webglcontextrestored',
+   *   listener);
+   *
+   * @param {!WebGLRenderingContext} ctx The webgl context to wrap.
+   */
+  'makeLostContextSimulatingContext': makeLostContextSimulatingContext,
+
+  /**
+   * Resets a context to the initial state.
+   * @param {!WebGLRenderingContext} ctx The webgl context to
+   *     reset.
+   */
+  'resetToInitialState': resetToInitialState
+};
+
+}();
+
diff --git a/lab3/lib/webgl-utils.js b/lab3/lib/webgl-utils.js
new file mode 100644
index 0000000..26ed37e
--- /dev/null
+++ b/lab3/lib/webgl-utils.js
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2010, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/**
+ * @fileoverview This file contains functions every webgl program will need
+ * a version of one way or another.
+ *
+ * Instead of setting up a context manually it is recommended to
+ * use. This will check for success or failure. On failure it
+ * will attempt to present an approriate message to the user.
+ *
+ *       gl = WebGLUtils.setupWebGL(canvas);
+ *
+ * For animated WebglInstance apps use of setTimeout or setInterval are
+ * discouraged. It is recommended you structure your rendering
+ * loop like this.
+ *
+ *       function render() {
+ *         window.requestAnimationFrame(render, canvas);
+ *
+ *         // do rendering
+ *         ...
+ *       }
+ *       render();
+ *
+ * This will call your rendering function up to the refresh rate
+ * of your display but will stop rendering if your app is not
+ * visible.
+ */
+
+WebGLUtils = function() {
+
+/**
+ * Creates the HTLM for a failure message
+ * @param {string} canvasContainerId id of container of th
+ *        canvas.
+ * @return {string} The html.
+ */
+var makeFailHTML = function(msg) {
+  return '' +
+        '<div style="margin: auto; width:500px;z-index:10000;margin-top:20em;text-align:center;">' + msg + '</div>';
+  return '' +
+    '<table style="background-color: #8CE; width: 100%; height: 100%;"><tr>' +
+    '<td align="center">' +
+    '<div style="display: table-cell; vertical-align: middle;">' +
+    '<div style="">' + msg + '</div>' +
+    '</div>' +
+    '</td></tr></table>';
+};
+
+/**
+ * Mesasge for getting a webgl browser
+ * @type {string}
+ */
+var GET_A_WEBGL_BROWSER = '' +
+  'This page requires a browser that supports WebglInstance.<br/>' +
+  '<a href="http://get.webgl.org">Click here to upgrade your browser.</a>';
+
+/**
+ * Mesasge for need better hardware
+ * @type {string}
+ */
+var OTHER_PROBLEM = '' +
+  "It doesn't appear your computer can support WebglInstance.<br/>" +
+  '<a href="http://get.webgl.org">Click here for more information.</a>';
+
+/**
+ * Creates a webgl context. If creation fails it will
+ * change the contents of the container of the <canvas>
+ * tag to an error message with the correct links for WebglInstance.
+ * @param {Element} canvas. The canvas element to create a
+ *     context from.
+ * @param {WebGLContextCreationAttirbutes} opt_attribs Any
+ *     creation attributes you want to pass in.
+ * @param {function:(msg)} opt_onError An function to call
+ *     if there is an error during creation.
+ * @return {WebGLRenderingContext} The created context.
+ */
+var setupWebGL = function(canvas, opt_attribs, opt_onError) {
+  function handleCreationError(msg) {
+      var container = document.getElementsByTagName("body")[0];
+    //var container = canvas.parentNode;
+    if (container) {
+      var str = window.WebGLRenderingContext ?
+           OTHER_PROBLEM :
+           GET_A_WEBGL_BROWSER;
+      if (msg) {
+        str += "<br/><br/>Status: " + msg;
+      }
+      container.innerHTML = makeFailHTML(str);
+    }
+  };
+
+  opt_onError = opt_onError || handleCreationError;
+
+  if (canvas.addEventListener) {
+    canvas.addEventListener("webglcontextcreationerror", function(event) {
+          opt_onError(event.statusMessage);
+        }, false);
+  }
+  var context = create3DContext(canvas, opt_attribs);
+  if (!context) {
+    if (!window.WebGLRenderingContext) {
+      opt_onError("");
+    } else {
+      opt_onError("");
+    }
+  }
+
+  return context;
+};
+
+/**
+ * Creates a webgl context.
+ * @param {!Canvas} canvas The canvas tag to get context
+ *     from. If one is not passed in one will be created.
+ * @return {!WebGLContext} The created context.
+ */
+var create3DContext = function(canvas, opt_attribs) {
+  var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
+  var context = null;
+  for (var ii = 0; ii < names.length; ++ii) {
+    try {
+      context = canvas.getContext(names[ii], opt_attribs);
+    } catch(e) {}
+    if (context) {
+      break;
+    }
+  }
+  return context;
+}
+
+return {
+  create3DContext: create3DContext,
+  setupWebGL: setupWebGL
+};
+}();
+
+/**
+ * Provides requestAnimationFrame in a cross browser
+ * way.
+ */
+if (!window.requestAnimationFrame) {
+  window.requestAnimationFrame = (function() {
+    return window.requestAnimationFrame ||
+           window.webkitRequestAnimationFrame ||
+           window.mozRequestAnimationFrame ||
+           window.oRequestAnimationFrame ||
+           window.msRequestAnimationFrame ||
+           function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
+             window.setTimeout(callback, 1000/60);
+           };
+  })();
+}
+
+/** * ERRATA: 'cancelRequestAnimationFrame' renamed to 'cancelAnimationFrame' to reflect an update to the W3C Animation-Timing Spec. 
+ * 
+ * Cancels an animation frame request. 
+ * Checks for cross-browser support, falls back to clearTimeout. 
+ * @param {number}  Animation frame request. */
+if (!window.cancelAnimationFrame) {
+  window.cancelAnimationFrame = (window.cancelRequestAnimationFrame ||
+                                 window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame ||
+                                 window.mozCancelAnimationFrame || window.mozCancelRequestAnimationFrame ||
+                                 window.msCancelAnimationFrame || window.msCancelRequestAnimationFrame ||
+                                 window.oCancelAnimationFrame || window.oCancelRequestAnimationFrame ||
+                                 window.clearTimeout);
+}
\ No newline at end of file
diff --git a/lab3/src/lab3.html b/lab3/src/lab3.html
new file mode 100644
index 0000000..bc58e57
--- /dev/null
+++ b/lab3/src/lab3.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Lab 3</title>
+</head>
+<body onload="main()">
+<canvas width="400" height="600" id="my-canvas">
+  Please use a browser that supports "canvas"
+</canvas>
+<script src="../lib/webgl-utils.js"></script>
+<script src="../lib/webgl-debug.js"></script>
+<script src="../lib/cuon-utils.js"></script>
+<script src="lab3.js"></script>
+</body>
+</html>
\ No newline at end of file
diff --git a/lab3/src/lab3.js b/lab3/src/lab3.js
new file mode 100644
index 0000000..105a227
--- /dev/null
+++ b/lab3/src/lab3.js
@@ -0,0 +1,18 @@
+// Vertex shader program
+const VSHADER_SOURCE =
+  '\n' +
+  // TODO: Implement your vertex shader code here
+  '\n';
+
+// Fragment shader program
+const FSHADER_SOURCE =
+  '\n' +
+  // TODO: Implement your fragment shader code here
+  '\n';
+
+function main() {
+  // Retrieve <canvas> element
+  const canvas = document.getElementById('my-canvas');
+
+  // TODO: Complete with your code here
+}
\ No newline at end of file
-- 
GitLab