diff --git a/css/main.css b/css/main.css
index 8c7744dd44a4ce0f8eb74c7f88c6e59cd4a8f0c7..cc656e0d2d3d9045828c90061246ae75a0b812cc 100644
--- a/css/main.css
+++ b/css/main.css
@@ -287,3 +287,8 @@ svg{
     text-align: center;
     margin: 0 18px;
 }
+
+#aboutError{
+    margin-right: 10px;
+    color: #34495e;
+}
diff --git a/index.html b/index.html
index b99f719e14e7c78d2aff3143ee299322d1986e0a..b8905a3fe834dd67098c8c53576b5020f63602ce 100644
--- a/index.html
+++ b/index.html
@@ -102,9 +102,9 @@
                 return;
             }
 
-            vec3 velocity = texture2D(u_velocity, scaledFragCoord).xyz;
-            vec3 position = velocity*u_dt + lastPosition;
-            gl_FragColor = vec4(position, 0.0);
+            vec4 velocityData = texture2D(u_velocity, scaledFragCoord);
+            vec3 position = velocityData.xyz*u_dt + lastPosition;
+            gl_FragColor = vec4(position, velocityData.a);//velocity.a has error info
         }
     </script>
 
@@ -146,6 +146,8 @@
             vec4 neighborIndices = texture2D(u_meta, scaledFragCoord);
             vec4 meta = texture2D(u_meta, scaledFragCoord);
 
+            float nodeError = 0.0;
+
             for (int j=0;j<100;j++){//for all beams (up to 100, had to put a const int in here)
                 if (j >= int(meta[1])) break;
 
@@ -164,11 +166,13 @@
                 vec3 nominalDist = neighborOriginalPosition-originalPosition;
                 vec3 deltaP = neighborLastPosition-lastPosition+nominalDist;
                 deltaP -= normalize(deltaP)*beamMeta[2];
+                nodeError += length(deltaP)/length(nominalDist);
                 vec3 deltaV = neighborLastVelocity-lastVelocity;
 
                 vec3 _force = deltaP*beamMeta[0] + deltaV*beamMeta[1];
                 force += _force;
             }
+            nodeError /= meta[1];
 
             for (int j=0;j<100;j++){//for all creases (up to 100, had to put a const int in here)
                 if (j >= int(meta[3])) break;
@@ -227,7 +231,7 @@
             }
 
             vec3 velocity = force*u_dt/mass[0] + lastVelocity;
-            gl_FragColor = vec4(velocity,0.0);
+            gl_FragColor = vec4(velocity,nodeError);
         }
     </script>
 
@@ -451,6 +455,9 @@
         <span class="label-slider">Damping (0-1): </span><div class="flat-slider ui-slider ui-corner-all ui-slider-horizontal ui-widget ui-widget-content"></div>
         <input value="" placeholder="" class="form-control" type="text">
     </div>
+    <br/><br/>
+    <b>Error:</b><br/>
+    <div class="indent"> Average Error (per node): <span id="globalError"></span><a class="floatRight" href="#" id="aboutError"><span class="fui-question-circle"></span></a></div>
 
     <div class="extraSpace"></div>
 </div>
@@ -479,6 +486,10 @@
                         <input name="colorMode" value="normal" data-toggle="radio" class="custom-radio" type="radio"><span class="icons"><span class="icon-unchecked"></span><span class="icon-checked"></span></span>
                         Face Normals Material
                     </label>
+                    <label class="radio">
+                        <input name="colorMode" value="error" data-toggle="radio" class="custom-radio" type="radio"><span class="icons"><span class="icon-unchecked"></span><span class="icon-checked"></span></span>
+                        Node Error Material
+                    </label>
                 </div>
             </div><br/>
             Edges:
@@ -592,5 +603,27 @@
         </div><!-- /.modal-content -->
     </div><!-- /.modal-dialog -->
 </div><!-- /.modal -->
+<div class="modal fade" id="aboutErrorModal" tabindex="-1" role="dialog">
+    <div class="modal-dialog modal-med">
+        <div class="modal-content">
+            <div class="modal-body">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                  <span aria-hidden="true">&times;</span>
+                </button>
+                <p><b>Simulation Error</b><br/><br/>
+                    "Average Error (per node)" gives a sense of how much the distance constraints in the
+                    origami pattern are being violated.  The error at each node is evaluated by averaging the
+                    percent deviation of all its distance constraints with adjacent nodes.  This error is
+                    reported as a percent of the total length of the distance constraint to remove scaling effects.
+                    Increasing the "Axial Stiffness" of the simulation will tighten these constraints and
+                    lower the error in the simulation.<br/>
+                    <br/>
+                    To visualize the error of each node graphically, select "Node Error Material" under "Mesh Material"
+                     in the left menu.
+                </p>
+            </div>
+        </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+</div><!-- /.modal -->
 </body>
 </html>
\ No newline at end of file
diff --git a/js/controls.js b/js/controls.js
index 85cb9ef35880c1ab94b0bace4bff9bf8062f114e..37a8e8c7fac4457ac3d7076f862545f25d2dbd0f 100644
--- a/js/controls.js
+++ b/js/controls.js
@@ -393,6 +393,10 @@ function initControls(globals){
         else $("#meshMaterialOptions").hide();
     });
 
+    setLink("#aboutError", function(){
+        $("#aboutErrorModal").modal("show");
+    });
+
     function setButtonGroup(id, callback){
         $(id+" a").click(function(e){
             e.preventDefault();
diff --git a/js/dynamic/dynamicSolver.js b/js/dynamic/dynamicSolver.js
index 7f85643e5a4f71c754044d569d3ad9f88521cdfc..71ee2aee6fa82c158aaf1bcc8c913fd0230aa3aa 100644
--- a/js/dynamic/dynamicSolver.js
+++ b/js/dynamic/dynamicSolver.js
@@ -128,6 +128,8 @@ function initDynamicSolver(globals){
         gpuMath.swapTextures("u_position", "u_lastPosition");
     }
 
+    var $errorOutput = $("#globalError");
+
     function render(){
 
         // var vectorLength = 2;
@@ -148,7 +150,7 @@ function initDynamicSolver(globals){
         //     console.log("here");
         // }
 
-        var vectorLength = 3;
+        var vectorLength = 4;
         globals.gpuMath.setProgram("packToBytes");
         globals.gpuMath.setUniformForProgram("packToBytes", "u_vectorLength", vectorLength, "1f");
         globals.gpuMath.setUniformForProgram("packToBytes", "u_floatTextureDim", [textureDim, textureDim], "2f");
@@ -161,11 +163,14 @@ function initDynamicSolver(globals){
             var pixels = new Uint8Array(height*textureDim*4*vectorLength);
             globals.gpuMath.readPixels(0, 0, textureDim * vectorLength, height, pixels);
             var parsedPixels = new Float32Array(pixels.buffer);
+            var globalError = 0;
             for (var i = 0; i < nodes.length; i++) {
                 var rgbaIndex = i * vectorLength;
+                globalError += parsedPixels[rgbaIndex+3];
                 var nodePosition = new THREE.Vector3(parsedPixels[rgbaIndex], parsedPixels[rgbaIndex + 1], parsedPixels[rgbaIndex + 2]);
                 nodes[i].render(nodePosition);
             }
+            $errorOutput.html((globalError/nodes.length*100).toFixed(7) + " %");
             for (var i=0;i<edges.length;i++){
                 edges[i].render();
             }
diff --git a/js/model.js b/js/model.js
index c4e1e9c63502190994ebefc8f16f4b01348147ec..3e1031860adcb6b63ba48498fa03b47698ae4460 100644
--- a/js/model.js
+++ b/js/model.js
@@ -5,12 +5,22 @@
 //wireframe model and folding structure
 function initModel(globals){
 
+    var doubleGeoFaces = [];
+    var faces = [];
+    var geometry = new THREE.Geometry();
+    geometry.dynamic = true;
+
     var material;
     setMeshMaterial();
-    function setMeshMaterial(){
-        if (globals.colorMode == "normal"){
+    function setMeshMaterial() {
+        if (globals.colorMode == "normal") {
+            // geometry.faces = faces;
             material = new THREE.MeshNormalMaterial({side: THREE.DoubleSide});
+        } else if (globals.colorMode == "error"){
+            // geometry.faces = faces;
+            material = new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors, side:THREE.DoubleSide});
         } else {
+            // geometry.faces = doubleGeoFaces;
             material = new THREE.MultiMaterial([
                 new THREE.MeshLambertMaterial({shading:THREE.FlatShading, color:0xff0000, side:THREE.FrontSide}),
                 new THREE.MeshLambertMaterial({shading:THREE.FlatShading, color:0x0000ff, side:THREE.FrontSide})
@@ -38,14 +48,11 @@ function initModel(globals){
         object3D.visible = globals.meshVisible;
     }
 
-    var geometry = new THREE.Geometry();
-    geometry.dynamic = true;
-    var object3D = new THREE.Mesh(geometry, material);
-
     function getGeometry(){
         return geometry;
     }
 
+    var object3D = new THREE.Mesh(geometry, material);
     var allNodeObject3Ds = [];
 
     var nodes = [];
@@ -74,7 +81,6 @@ function initModel(globals){
     // edges.push(new Beam([nodes[4], nodes[1]]));
     // edges.push(new Beam([nodes[3], nodes[4]]));
 
-    var faces = [];
     // faces.push(new THREE.Face3(0,1,2));
     // faces.push(new THREE.Face3(0,2,3));
     // faces.push(new THREE.Face3(4,3,2));
@@ -195,14 +201,14 @@ function initModel(globals){
             vertices.push(nodes[i].getPosition());
         }
 
-        var geofaces = faces.slice();
+        doubleGeoFaces = faces.slice();
         for (var i=0;i<faces.length;i++){
-            geofaces[i].materialIndex = 1;
-            geofaces.push(new THREE.Face3(faces[i].a, faces[i].c, faces[i].b));
+            doubleGeoFaces[i].materialIndex = 1;
+            doubleGeoFaces.push(new THREE.Face3(faces[i].a, faces[i].c, faces[i].b));
         }
 
         geometry.vertices = vertices;
-        geometry.faces = geofaces;
+        geometry.faces = doubleGeoFaces;
         geometry.verticesNeedUpdate = true;
         geometry.elementsNeedUpdate = true;
         geometry.computeFaceNormals();