diff --git a/dependencies/VRControls.js b/dependencies/VRControls.js
new file mode 100644
index 0000000000000000000000000000000000000000..8829c06266e0474e0f5ab607803b63b411bc8355
--- /dev/null
+++ b/dependencies/VRControls.js
@@ -0,0 +1,149 @@
+/**
+ * @author dmarcos / https://github.com/dmarcos
+ * @author mrdoob / http://mrdoob.com
+ */
+
+THREE.VRControls = function ( object, onError ) {
+
+	var scope = this;
+
+	var vrDisplay, vrDisplays;
+
+	var standingMatrix = new THREE.Matrix4();
+
+	var frameData = null;
+
+	if ( 'VRFrameData' in window ) {
+
+		frameData = new VRFrameData();
+
+	}
+
+	function gotVRDisplays( displays ) {
+
+		vrDisplays = displays;
+
+		if ( displays.length > 0 ) {
+
+			vrDisplay = displays[ 0 ];
+
+		} else {
+
+			if ( onError ) onError( 'VR input not available.' );
+
+		}
+
+	}
+
+	if ( navigator.getVRDisplays ) {
+
+		navigator.getVRDisplays().then( gotVRDisplays ).catch( function () {
+
+			console.warn( 'THREE.VRControls: Unable to get VR Displays' );
+
+		} );
+
+	}
+
+	// the Rift SDK returns the position in meters
+	// this scale factor allows the user to define how meters
+	// are converted to scene units.
+
+	this.scale = 1;
+
+	// If true will use "standing space" coordinate system where y=0 is the
+	// floor and x=0, z=0 is the center of the room.
+	this.standing = false;
+
+	// Distance from the users eyes to the floor in meters. Used when
+	// standing=true but the VRDisplay doesn't provide stageParameters.
+	this.userHeight = 1.6;
+
+	this.getVRDisplay = function () {
+
+		return vrDisplay;
+
+	};
+
+	this.setVRDisplay = function ( value ) {
+
+		vrDisplay = value;
+
+	};
+
+	this.getVRDisplays = function () {
+
+		console.warn( 'THREE.VRControls: getVRDisplays() is being deprecated.' );
+		return vrDisplays;
+
+	};
+
+	this.getStandingMatrix = function () {
+
+		return standingMatrix;
+
+	};
+
+	this.update = function () {
+
+		if ( vrDisplay ) {
+
+			var pose;
+
+			if ( vrDisplay.getFrameData ) {
+
+				vrDisplay.getFrameData( frameData );
+				pose = frameData.pose;
+
+			} else if ( vrDisplay.getPose ) {
+
+				pose = vrDisplay.getPose();
+
+			}
+
+			if ( pose.orientation !== null ) {
+
+				object.quaternion.fromArray( pose.orientation );
+
+			}
+
+			if ( pose.position !== null ) {
+
+				object.position.fromArray( pose.position );
+
+			} else {
+
+				object.position.set( 0, 0, 0 );
+
+			}
+
+			if ( this.standing ) {
+
+				if ( vrDisplay.stageParameters ) {
+
+					object.updateMatrix();
+
+					standingMatrix.fromArray( vrDisplay.stageParameters.sittingToStandingTransform );
+					object.applyMatrix( standingMatrix );
+
+				} else {
+
+					object.position.setY( object.position.y + this.userHeight );
+
+				}
+
+			}
+
+			object.position.multiplyScalar( scope.scale );
+
+		}
+
+	};
+
+	this.dispose = function () {
+
+		vrDisplay = null;
+
+	};
+
+};
diff --git a/dependencies/VREffect.js b/dependencies/VREffect.js
new file mode 100644
index 0000000000000000000000000000000000000000..19bcba51359f8933045745ad230168712399b295
--- /dev/null
+++ b/dependencies/VREffect.js
@@ -0,0 +1,476 @@
+/**
+ * @author dmarcos / https://github.com/dmarcos
+ * @author mrdoob / http://mrdoob.com
+ *
+ * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html
+ *
+ * Firefox: http://mozvr.com/downloads/
+ * Chromium: https://webvr.info/get-chrome
+ */
+
+THREE.VREffect = function ( renderer, onError ) {
+
+	var vrDisplay, vrDisplays;
+	var eyeTranslationL = new THREE.Vector3();
+	var eyeTranslationR = new THREE.Vector3();
+	var renderRectL, renderRectR;
+
+	var frameData = null;
+
+	if ( 'VRFrameData' in window ) {
+
+		frameData = new window.VRFrameData();
+
+	}
+
+	function gotVRDisplays( displays ) {
+
+		vrDisplays = displays;
+
+		if ( displays.length > 0 ) {
+
+			vrDisplay = displays[ 0 ];
+
+		} else {
+
+			if ( onError ) onError( 'HMD not available' );
+
+		}
+
+	}
+
+	if ( navigator.getVRDisplays ) {
+
+		navigator.getVRDisplays().then( gotVRDisplays ).catch( function () {
+
+			console.warn( 'THREE.VREffect: Unable to get VR Displays' );
+
+		} );
+
+	}
+
+	//
+
+	this.isPresenting = false;
+
+	var scope = this;
+
+	var rendererSize = renderer.getSize();
+	var rendererUpdateStyle = false;
+	var rendererPixelRatio = renderer.getPixelRatio();
+
+	this.getVRDisplay = function () {
+
+		return vrDisplay;
+
+	};
+
+	this.setVRDisplay = function ( value ) {
+
+		vrDisplay = value;
+
+	};
+
+	this.getVRDisplays = function () {
+
+		console.warn( 'THREE.VREffect: getVRDisplays() is being deprecated.' );
+		return vrDisplays;
+
+	};
+
+	this.setSize = function ( width, height, updateStyle ) {
+
+		rendererSize = { width: width, height: height };
+		rendererUpdateStyle = updateStyle;
+
+		if ( scope.isPresenting ) {
+
+			var eyeParamsL = vrDisplay.getEyeParameters( 'left' );
+			renderer.setPixelRatio( 1 );
+			renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false );
+
+		} else {
+
+			renderer.setPixelRatio( rendererPixelRatio );
+			renderer.setSize( width, height, updateStyle );
+
+		}
+
+	};
+
+	// VR presentation
+
+	var canvas = renderer.domElement;
+	var defaultLeftBounds = [ 0.0, 0.0, 0.5, 1.0 ];
+	var defaultRightBounds = [ 0.5, 0.0, 0.5, 1.0 ];
+
+	function onVRDisplayPresentChange() {
+
+		var wasPresenting = scope.isPresenting;
+		scope.isPresenting = vrDisplay !== undefined && vrDisplay.isPresenting;
+
+		if ( scope.isPresenting ) {
+
+			var eyeParamsL = vrDisplay.getEyeParameters( 'left' );
+			var eyeWidth = eyeParamsL.renderWidth;
+			var eyeHeight = eyeParamsL.renderHeight;
+
+			if ( ! wasPresenting ) {
+
+				rendererPixelRatio = renderer.getPixelRatio();
+				rendererSize = renderer.getSize();
+
+				renderer.setPixelRatio( 1 );
+				renderer.setSize( eyeWidth * 2, eyeHeight, false );
+
+			}
+
+		} else if ( wasPresenting ) {
+
+			renderer.setPixelRatio( rendererPixelRatio );
+			renderer.setSize( rendererSize.width, rendererSize.height, rendererUpdateStyle );
+
+		}
+
+	}
+
+	window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );
+
+	this.setFullScreen = function ( boolean ) {
+
+		return new Promise( function ( resolve, reject ) {
+
+			if ( vrDisplay === undefined ) {
+
+				reject( new Error( 'No VR hardware found.' ) );
+				return;
+
+			}
+
+			if ( scope.isPresenting === boolean ) {
+
+				resolve();
+				return;
+
+			}
+
+			if ( boolean ) {
+
+				resolve( vrDisplay.requestPresent( [ { source: canvas } ] ) );
+
+			} else {
+
+				resolve( vrDisplay.exitPresent() );
+
+			}
+
+		} );
+
+	};
+
+	this.requestPresent = function () {
+
+		return this.setFullScreen( true );
+
+	};
+
+	this.exitPresent = function () {
+
+		return this.setFullScreen( false );
+
+	};
+
+	this.requestAnimationFrame = function ( f ) {
+
+		if ( vrDisplay !== undefined ) {
+
+			return vrDisplay.requestAnimationFrame( f );
+
+		} else {
+
+			return window.requestAnimationFrame( f );
+
+		}
+
+	};
+
+	this.cancelAnimationFrame = function ( h ) {
+
+		if ( vrDisplay !== undefined ) {
+
+			vrDisplay.cancelAnimationFrame( h );
+
+		} else {
+
+			window.cancelAnimationFrame( h );
+
+		}
+
+	};
+
+	this.submitFrame = function () {
+
+		if ( vrDisplay !== undefined && scope.isPresenting ) {
+
+			vrDisplay.submitFrame();
+
+		}
+
+	};
+
+	this.autoSubmitFrame = true;
+
+	// render
+
+	var cameraL = new THREE.PerspectiveCamera();
+	cameraL.layers.enable( 1 );
+
+	var cameraR = new THREE.PerspectiveCamera();
+	cameraR.layers.enable( 2 );
+
+	this.render = function ( scene, camera, renderTarget, forceClear ) {
+
+		if ( vrDisplay && scope.isPresenting ) {
+
+			var autoUpdate = scene.autoUpdate;
+
+			if ( autoUpdate ) {
+
+				scene.updateMatrixWorld();
+				scene.autoUpdate = false;
+
+			}
+
+			var eyeParamsL = vrDisplay.getEyeParameters( 'left' );
+			var eyeParamsR = vrDisplay.getEyeParameters( 'right' );
+
+			eyeTranslationL.fromArray( eyeParamsL.offset );
+			eyeTranslationR.fromArray( eyeParamsR.offset );
+
+			if ( Array.isArray( scene ) ) {
+
+				console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' );
+				scene = scene[ 0 ];
+
+			}
+
+			// When rendering we don't care what the recommended size is, only what the actual size
+			// of the backbuffer is.
+			var size = renderer.getSize();
+			var layers = vrDisplay.getLayers();
+			var leftBounds;
+			var rightBounds;
+
+			if ( layers.length ) {
+
+				var layer = layers[ 0 ];
+
+				leftBounds = layer.leftBounds !== null && layer.leftBounds.length === 4 ? layer.leftBounds : defaultLeftBounds;
+				rightBounds = layer.rightBounds !== null && layer.rightBounds.length === 4 ? layer.rightBounds : defaultRightBounds;
+
+			} else {
+
+				leftBounds = defaultLeftBounds;
+				rightBounds = defaultRightBounds;
+
+			}
+
+			renderRectL = {
+				x: Math.round( size.width * leftBounds[ 0 ] ),
+				y: Math.round( size.height * leftBounds[ 1 ] ),
+				width: Math.round( size.width * leftBounds[ 2 ] ),
+				height: Math.round( size.height * leftBounds[ 3 ] )
+			};
+			renderRectR = {
+				x: Math.round( size.width * rightBounds[ 0 ] ),
+				y: Math.round( size.height * rightBounds[ 1 ] ),
+				width: Math.round( size.width * rightBounds[ 2 ] ),
+				height: Math.round( size.height * rightBounds[ 3 ] )
+			};
+
+			if ( renderTarget ) {
+
+				renderer.setRenderTarget( renderTarget );
+				renderTarget.scissorTest = true;
+
+			} else {
+
+				renderer.setRenderTarget( null );
+				renderer.setScissorTest( true );
+
+			}
+
+			if ( renderer.autoClear || forceClear ) renderer.clear();
+
+			if ( camera.parent === null ) camera.updateMatrixWorld();
+
+			camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale );
+
+			cameraR.position.copy( cameraL.position );
+			cameraR.quaternion.copy( cameraL.quaternion );
+			cameraR.scale.copy( cameraL.scale );
+
+			cameraL.translateOnAxis( eyeTranslationL, cameraL.scale.x );
+			cameraR.translateOnAxis( eyeTranslationR, cameraR.scale.x );
+
+			if ( vrDisplay.getFrameData ) {
+
+				vrDisplay.depthNear = camera.near;
+				vrDisplay.depthFar = camera.far;
+
+				vrDisplay.getFrameData( frameData );
+
+				cameraL.projectionMatrix.elements = frameData.leftProjectionMatrix;
+				cameraR.projectionMatrix.elements = frameData.rightProjectionMatrix;
+
+			} else {
+
+				cameraL.projectionMatrix = fovToProjection( eyeParamsL.fieldOfView, true, camera.near, camera.far );
+				cameraR.projectionMatrix = fovToProjection( eyeParamsR.fieldOfView, true, camera.near, camera.far );
+
+			}
+
+			// render left eye
+			if ( renderTarget ) {
+
+				renderTarget.viewport.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
+				renderTarget.scissor.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
+
+			} else {
+
+				renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
+				renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
+
+			}
+			renderer.render( scene, cameraL, renderTarget, forceClear );
+
+			// render right eye
+			if ( renderTarget ) {
+
+				renderTarget.viewport.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
+				renderTarget.scissor.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
+
+			} else {
+
+				renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
+				renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
+
+			}
+			renderer.render( scene, cameraR, renderTarget, forceClear );
+
+			if ( renderTarget ) {
+
+				renderTarget.viewport.set( 0, 0, size.width, size.height );
+				renderTarget.scissor.set( 0, 0, size.width, size.height );
+				renderTarget.scissorTest = false;
+				renderer.setRenderTarget( null );
+
+			} else {
+
+				renderer.setViewport( 0, 0, size.width, size.height );
+				renderer.setScissorTest( false );
+
+			}
+
+			if ( autoUpdate ) {
+
+				scene.autoUpdate = true;
+
+			}
+
+			if ( scope.autoSubmitFrame ) {
+
+				scope.submitFrame();
+
+			}
+
+			return;
+
+		}
+
+		// Regular render mode if not HMD
+
+		renderer.render( scene, camera, renderTarget, forceClear );
+
+	};
+
+	this.dispose = function () {
+
+		window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );
+
+	};
+
+	//
+
+	function fovToNDCScaleOffset( fov ) {
+
+		var pxscale = 2.0 / ( fov.leftTan + fov.rightTan );
+		var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5;
+		var pyscale = 2.0 / ( fov.upTan + fov.downTan );
+		var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5;
+		return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] };
+
+	}
+
+	function fovPortToProjection( fov, rightHanded, zNear, zFar ) {
+
+		rightHanded = rightHanded === undefined ? true : rightHanded;
+		zNear = zNear === undefined ? 0.01 : zNear;
+		zFar = zFar === undefined ? 10000.0 : zFar;
+
+		var handednessScale = rightHanded ? - 1.0 : 1.0;
+
+		// start with an identity matrix
+		var mobj = new THREE.Matrix4();
+		var m = mobj.elements;
+
+		// and with scale/offset info for normalized device coords
+		var scaleAndOffset = fovToNDCScaleOffset( fov );
+
+		// X result, map clip edges to [-w,+w]
+		m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ];
+		m[ 0 * 4 + 1 ] = 0.0;
+		m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale;
+		m[ 0 * 4 + 3 ] = 0.0;
+
+		// Y result, map clip edges to [-w,+w]
+		// Y offset is negated because this proj matrix transforms from world coords with Y=up,
+		// but the NDC scaling has Y=down (thanks D3D?)
+		m[ 1 * 4 + 0 ] = 0.0;
+		m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ];
+		m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale;
+		m[ 1 * 4 + 3 ] = 0.0;
+
+		// Z result (up to the app)
+		m[ 2 * 4 + 0 ] = 0.0;
+		m[ 2 * 4 + 1 ] = 0.0;
+		m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale;
+		m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar );
+
+		// W result (= Z in)
+		m[ 3 * 4 + 0 ] = 0.0;
+		m[ 3 * 4 + 1 ] = 0.0;
+		m[ 3 * 4 + 2 ] = handednessScale;
+		m[ 3 * 4 + 3 ] = 0.0;
+
+		mobj.transpose();
+		return mobj;
+
+	}
+
+	function fovToProjection( fov, rightHanded, zNear, zFar ) {
+
+		var DEG2RAD = Math.PI / 180.0;
+
+		var fovPort = {
+			upTan: Math.tan( fov.upDegrees * DEG2RAD ),
+			downTan: Math.tan( fov.downDegrees * DEG2RAD ),
+			leftTan: Math.tan( fov.leftDegrees * DEG2RAD ),
+			rightTan: Math.tan( fov.rightDegrees * DEG2RAD )
+		};
+
+		return fovPortToProjection( fovPort, rightHanded, zNear, zFar );
+
+	}
+
+};
diff --git a/dependencies/ViveController.js b/dependencies/ViveController.js
new file mode 100644
index 0000000000000000000000000000000000000000..7d70e73be0f98db83c47d02ae2249cb8c507248f
--- /dev/null
+++ b/dependencies/ViveController.js
@@ -0,0 +1,128 @@
+/**
+ * @author mrdoob / http://mrdoob.com
+ * @author stewdio / http://stewd.io
+ */
+
+THREE.ViveController = function ( id ) {
+
+	THREE.Object3D.call( this );
+
+	var scope = this;
+	var gamepad;
+
+	var axes = [ 0, 0 ];
+	var thumbpadIsPressed = false;
+	var triggerIsPressed = false;
+	var gripsArePressed = false;
+	var menuIsPressed = false;
+
+	function findGamepad( id ) {
+
+		// Iterate across gamepads as Vive Controllers may not be
+		// in position 0 and 1.
+
+		var gamepads = navigator.getGamepads();
+
+		for ( var i = 0, j = 0; i < 4; i ++ ) {
+
+			var gamepad = gamepads[ i ];
+
+			if ( gamepad && ( gamepad.id === 'OpenVR Gamepad' || gamepad.id === 'Oculus Touch (Left)' || gamepad.id === 'Oculus Touch (Right)' ) ) {
+
+				if ( j === id ) return gamepad;
+
+				j ++;
+
+			}
+
+		}
+
+	}
+
+	this.matrixAutoUpdate = false;
+	this.standingMatrix = new THREE.Matrix4();
+
+	this.getGamepad = function () {
+
+		return gamepad;
+
+	};
+
+	this.getButtonState = function ( button ) {
+
+		if ( button === 'thumbpad' ) return thumbpadIsPressed;
+		if ( button === 'trigger' ) return triggerIsPressed;
+		if ( button === 'grips' ) return gripsArePressed;
+		if ( button === 'menu' ) return menuIsPressed;
+
+	};
+
+	this.update = function () {
+
+		gamepad = findGamepad( id );
+
+		if ( gamepad !== undefined && gamepad.pose !== undefined ) {
+
+			if ( gamepad.pose === null ) return; // No user action yet
+
+			//  Position and orientation.
+
+			var pose = gamepad.pose;
+
+			if ( pose.position !== null ) scope.position.fromArray( pose.position );
+			if ( pose.orientation !== null ) scope.quaternion.fromArray( pose.orientation );
+			scope.matrix.compose( scope.position, scope.quaternion, scope.scale );
+			scope.matrix.multiplyMatrices( scope.standingMatrix, scope.matrix );
+			scope.matrixWorldNeedsUpdate = true;
+			scope.visible = true;
+
+			//  Thumbpad and Buttons.
+
+			if ( axes[ 0 ] !== gamepad.axes[ 0 ] || axes[ 1 ] !== gamepad.axes[ 1 ] ) {
+
+				axes[ 0 ] = gamepad.axes[ 0 ]; //  X axis: -1 = Left, +1 = Right.
+				axes[ 1 ] = gamepad.axes[ 1 ]; //  Y axis: -1 = Bottom, +1 = Top.
+				scope.dispatchEvent( { type: 'axischanged', axes: axes } );
+
+			}
+
+			if ( thumbpadIsPressed !== gamepad.buttons[ 0 ].pressed ) {
+
+				thumbpadIsPressed = gamepad.buttons[ 0 ].pressed;
+				scope.dispatchEvent( { type: thumbpadIsPressed ? 'thumbpaddown' : 'thumbpadup' } );
+
+			}
+
+			if ( triggerIsPressed !== gamepad.buttons[ 1 ].pressed ) {
+
+				triggerIsPressed = gamepad.buttons[ 1 ].pressed;
+				scope.dispatchEvent( { type: triggerIsPressed ? 'triggerdown' : 'triggerup' } );
+
+			}
+
+			if ( gripsArePressed !== gamepad.buttons[ 2 ].pressed ) {
+
+				gripsArePressed = gamepad.buttons[ 2 ].pressed;
+				scope.dispatchEvent( { type: gripsArePressed ? 'gripsdown' : 'gripsup' } );
+
+			}
+
+			if ( menuIsPressed !== gamepad.buttons[ 3 ].pressed ) {
+
+				menuIsPressed = gamepad.buttons[ 3 ].pressed;
+				scope.dispatchEvent( { type: menuIsPressed ? 'menudown' : 'menuup' } );
+
+			}
+
+		} else {
+
+			scope.visible = false;
+
+		}
+
+	};
+
+};
+
+THREE.ViveController.prototype = Object.create( THREE.Object3D.prototype );
+THREE.ViveController.prototype.constructor = THREE.ViveController;
diff --git a/dependencies/WebVR.js b/dependencies/WebVR.js
new file mode 100644
index 0000000000000000000000000000000000000000..80b3243c6c0d9ceed0bbb47e7bd651344adc2587
--- /dev/null
+++ b/dependencies/WebVR.js
@@ -0,0 +1,131 @@
+/**
+ * @author mrdoob / http://mrdoob.com
+ * Based on @tojiro's vr-samples-utils.js
+ */
+
+var WEBVR = {
+
+	isLatestAvailable: function () {
+
+		console.warn( 'WEBVR: isLatestAvailable() is being deprecated. Use .isAvailable() instead.' );
+		return this.isAvailable();
+
+	},
+
+	isAvailable: function () {
+
+		return navigator.getVRDisplays !== undefined;
+
+	},
+
+	getVRDisplay: function ( onDisplay ) {
+
+		if ( 'getVRDisplays' in navigator ) {
+
+			navigator.getVRDisplays()
+				.then( function ( displays ) {
+					onDisplay( displays[ 0 ] );
+				} );
+
+		}
+
+	},
+
+	getMessage: function () {
+
+		var message;
+
+		if ( navigator.getVRDisplays ) {
+
+			navigator.getVRDisplays().then( function ( displays ) {
+
+				if ( displays.length === 0 ) message = 'WebVR supported, but no VRDisplays found.';
+
+			} );
+
+		} else {
+
+			message = 'Your browser does not support WebVR. See <a href="http://webvr.info">webvr.info</a> for assistance.';
+
+		}
+
+		if ( message !== undefined ) {
+
+			var container = document.createElement( 'div' );
+			container.style.position = 'absolute';
+			container.style.left = '0';
+			container.style.top = '0';
+			container.style.right = '0';
+			container.style.zIndex = '999';
+			container.align = 'center';
+
+			var error = document.createElement( 'div' );
+			error.style.fontFamily = 'sans-serif';
+			error.style.fontSize = '16px';
+			error.style.fontStyle = 'normal';
+			error.style.lineHeight = '26px';
+			error.style.backgroundColor = '#fff';
+			error.style.color = '#000';
+			error.style.padding = '10px 20px';
+			error.style.margin = '50px';
+			error.style.display = 'inline-block';
+			error.innerHTML = message;
+			container.appendChild( error );
+
+			return container;
+
+		}
+
+	},
+
+	getButton: function ( display, canvas ) {
+
+		if ( 'VREffect' in THREE && display instanceof THREE.VREffect ) {
+
+			console.error( 'WebVR.getButton() now expects a VRDisplay.' );
+			return document.createElement( 'button' );
+
+		}
+
+		var button = document.createElement( 'button' );
+		button.style.position = 'absolute';
+		button.style.left = 'calc(50% - 50px)';
+		button.style.bottom = '20px';
+		button.style.width = '100px';
+		button.style.border = '0';
+		button.style.padding = '8px';
+		button.style.cursor = 'pointer';
+		button.style.backgroundColor = '#000';
+		button.style.color = '#fff';
+		button.style.fontFamily = 'sans-serif';
+		button.style.fontSize = '13px';
+		button.style.fontStyle = 'normal';
+		button.style.textAlign = 'center';
+		button.style.zIndex = '999';
+
+		if ( display ) {
+
+			button.textContent = 'ENTER VR';
+			button.onclick = function () {
+
+				display.isPresenting ? display.exitPresent() : display.requestPresent( [ { source: canvas } ] );
+
+			};
+
+			window.addEventListener( 'vrdisplaypresentchange', function () {
+
+				button.textContent = display.isPresenting ? 'EXIT VR' : 'ENTER VR';
+
+			}, false );
+
+		} else {
+
+			button.textContent = 'NO VR DISPLAY';
+
+		}
+
+		return button;
+
+	}
+
+};
diff --git a/index.html b/index.html
index 9f3d182746f9d2e027595463c799bd53583c5ccd..64b80141cef501e89a0d642b5fa4ae03c6dc6ddc 100644
--- a/index.html
+++ b/index.html
@@ -284,7 +284,6 @@
             float dotNormals = dot(normal1, normal2);//normals are already normalized, no need to divide by length
             if (dotNormals < -1.0) dotNormals = -1.0;
             else if (dotNormals > 1.0) dotNormals = 1.0;
-            //float theta = acos(dotNormals);
 
             vec2 creaseVectorIndices = texture2D(u_creaseVectors, scaledFragCoord).xy;
             vec2 creaseNodeIndex = vec2(mod(creaseVectorIndices[0], u_textureDim.x)+0.5, floor(creaseVectorIndices[0]/u_textureDim.x)+0.5);
@@ -301,10 +300,7 @@
 
             float theta = atan(y, x);
 
-            //float sign = dot(cross(normal1, normal2), creaseVector);
-            //if (sign < 0.0) theta *= -1.0;
             float diff = theta-lastTheta[0];
-            float projectedTheta = lastTheta[0] + lastTheta[1]*u_dt;
             if (diff < -5.0) {
                 diff += TWO_PI;
                 theta = lastTheta[0] + diff;
@@ -312,7 +308,7 @@
                 diff  -= TWO_PI;
                 theta = lastTheta[0] + diff;
             }
-            gl_FragColor = vec4(theta, creaseVectorIndices[1], lastTheta[2], lastTheta[3]);//[theta, w, normal1Index, normal2Index]
+            gl_FragColor = vec4(theta, theta, lastTheta[2], lastTheta[3]);//[theta, w, normal1Index, normal2Index]
         }
     </script>
 
@@ -345,7 +341,9 @@
             scaledNodeIndex = nodeIndex/u_textureDim;
             vec3 c = texture2D(u_lastPosition, scaledNodeIndex).xyz + texture2D(u_originalPosition, scaledNodeIndex).xyz;
 
-            gl_FragColor = vec4(normalize(cross(b-a, c-a)), 0.0);
+            vec3 normal = normalize(cross(b-a, c-a));
+
+            gl_FragColor = vec4(normal, 0.0);
         }
     </script>
 
@@ -372,6 +370,11 @@
     <script type="text/javascript" src="dependencies/Detector.js"></script>
     <script type="text/javascript" src="dependencies/RenderPass.js"></script>
 
+    <script type="text/javascript" src="dependencies/WebVR.js"></script>
+    <script type="text/javascript" src="dependencies/VREffect.js"></script>
+    <script type="text/javascript" src="dependencies/ViveController.js"></script>
+    <script type="text/javascript" src="dependencies/VRControls.js"></script>
+
     <script type="text/javascript" src="js/dynamic/GLBoilerplate.js"></script>
     <script type="text/javascript" src="js/dynamic/GPUMath.js"></script>
     <script type="text/javascript" src="js/controls.js"></script>
@@ -388,7 +391,7 @@
     <script type="text/javascript" src="js/saveSTL.js"></script>
     <script type="text/javascript" src="js/saveFOLD.js"></script>
     <script type="text/javascript" src="js/importer.js"></script>
-
+    <script type="text/javascript" src="js/ViveInterface.js"></script>
 
     <script type="text/javascript" src="js/main.js"></script>
 </head>
@@ -605,7 +608,7 @@
             <!--</div>-->
             VR:
             <div class="indent smallTxt">
-                Status: &nbsp;<span id="VRstatus">No device connected</span>
+                Status: &nbsp;<span id="VRstatus"></span>
                 <a class="about floatRight" href="#" id="aboutVR"><span class="fui-question-circle"></span></a><br/>
                 <div id="VRoptions">
                     <label class="checkbox" for="vrEnabled">
diff --git a/js/ViveInterface.js b/js/ViveInterface.js
new file mode 100644
index 0000000000000000000000000000000000000000..96e57b4aac906af501ca2863f2591552bca3c05f
--- /dev/null
+++ b/js/ViveInterface.js
@@ -0,0 +1,65 @@
+/**
+ * Created by amandaghassaei on 5/10/17.
+ */
+
+
+function initViveInterface(globals){
+
+    var $status = $("#VRstatus");
+
+    if ( WEBVR.isAvailable() === false ) {
+        $status.html("WebVR not supported by this browser<br/>see <a href='https://webvr.info/' target='_blank'>webvr.info</a> for more info.");
+        $("#VRoptions").hide();
+        return;
+    }
+
+    var controls, controller1, controller2, effect;
+
+    var mesh = new THREE.Mesh(new THREE.CubeGeometry(1, 1,1 ), new THREE.MeshLambertMaterial({color:0xff0000}));
+
+    function setup(){
+
+        controls = new THREE.VRControls(globals.threeView.camera);
+        controls.standing = true;
+
+        // controllers
+        controller1 = new THREE.ViveController( 0 );
+        controller1.standingMatrix = controls.getStandingMatrix();
+        globals.threeView.scene.add( controller1 );
+
+        controller2 = new THREE.ViveController( 1 );
+        controller2.standingMatrix = controls.getStandingMatrix();
+        globals.threeView.scene.add( controller2 );
+
+        controller1.add(mesh.clone());
+        controller2.add(mesh.clone());
+
+        effect = new THREE.VREffect(globals.threeView.renderer);
+
+    }
+
+    function connect(){
+        WEBVR.getVRDisplay( function ( display ) {
+
+            document.body.appendChild( WEBVR.getButton( display, renderer.domElement ) );
+
+        } );
+    }
+
+    function disconnect(){
+
+    }
+
+    function render(){
+        controller1.update();
+        controller2.update();
+        controls.update();
+        effect.render( globals.threeView.scene, globals.threeView.camera );
+    }
+
+    return {
+        effect: effect,
+        render: render
+    }
+
+}
\ No newline at end of file
diff --git a/js/dynamic/dynamicSolver.js b/js/dynamic/dynamicSolver.js
index d03df827f73f90c1057f10141f6ca41ba78d1ad5..6f8c2e4a43dd5056991b73416b04c7440db370ca 100644
--- a/js/dynamic/dynamicSolver.js
+++ b/js/dynamic/dynamicSolver.js
@@ -61,6 +61,7 @@ function initDynamicSolver(globals){
         globals.gpuMath.step("zeroTexture", [], "u_lastPosition");
         globals.gpuMath.step("zeroTexture", [], "u_velocity");
         globals.gpuMath.step("zeroTexture", [], "u_lastVelocity");
+        //todo reset theta
         render();
     }
 
@@ -172,7 +173,9 @@ function initDynamicSolver(globals){
         //     var pixels = new Uint8Array(height*textureDimCreases*4*vectorLength);
         //     globals.gpuMath.readPixels(0, 0, textureDimCreases * vectorLength, height, pixels);
         //     var parsedPixels = new Float32Array(pixels.buffer);
-        //     console.log(parsedPixels);
+        //     for (var i=0;i<parsedPixels.length;i+=2){
+        //         if (Math.abs(parsedPixels[i+1])>3.0) console.log(parsedPixels[i+1]);
+        //     }
         // } else {
         //     console.log("here");
         // }
diff --git a/js/main.js b/js/main.js
index 09439897673632812b5b36f3cb0bd960b0b876c0..7b9142077e7a17e2a35d67251ef685711b89a975 100644
--- a/js/main.js
+++ b/js/main.js
@@ -17,5 +17,6 @@ $(function() {
     globals.staticSolver = initStaticSolver(globals);
     globals.dynamicSolver = initDynamicSolver(globals);
     globals.pattern = initPattern(globals);
+    globals.vive = initViveInterface(globals);
     $(".demo[data-url='Tessellations/waterbomb.svg']").click();
 });
\ No newline at end of file
diff --git a/js/threeView.js b/js/threeView.js
index 3aa5f49effbfd0b6f736ff829af3eee451093393..5ddfd4834f6f54575b0fd0e91537ff943836329b 100644
--- a/js/threeView.js
+++ b/js/threeView.js
@@ -122,6 +122,10 @@ function initThreeView(globals) {
     }
 
     function _render(){
+        if (globals.vrEnabled){
+            globals.vive.render();
+            return;
+        }
         if (globals.ambientOcclusion) {
             // Render depth into depthRenderTarget
             scene.overrideMaterial = depthMaterial;
@@ -129,13 +133,19 @@ function initThreeView(globals) {
             // Render renderPass and SSAO shaderPass
             scene.overrideMaterial = null;
             effectComposer.render();
-        } else {
-            renderer.render(scene, camera);
+            return;
         }
+        renderer.render(scene, camera);
     }
 
     function _loop(callback){
         callback();
+        if (globals.vrEnabled){
+            globals.vive.effect.requestAnimationFrame(function(){
+                _loop(callback);
+            });
+            return;
+        }
         requestAnimationFrame(function(){
             if (pauseFlag) {
                 pauseFlag = false;
@@ -231,6 +241,7 @@ function initThreeView(globals) {
         enableControls: enableControls,
         scene: scene,
         camera: camera,
+        renderer: renderer,
         running: running,
         setScale:setScale,
         saveSVG: saveSVG