diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e43b0f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a02d6c2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,28 @@
+### Name and Project Name
+Luke Carlson - Threejs project
+### Link to GitHub Pages site
+[right here](http://pennvr.github.io/three-js-jlukec)
+### Techniques used, and why those techniques
+For this assignment I built a version of the [diamond square algorithm](https://en.wikipedia.org/wiki/Diamond-square_algorithm) to generate my terrain from a PlaneGeometry. It seemed like a reasonable way to generate the terrain. You can see the code at js/diamond_square.js
+
+For my particle system, I created a bunch of small red spheres that explode in random directions.
+
+For controls, I used code from the PointerLockControl & webvr cube THREE.js examples. Those examples were super helpful for setting up my environment & world.
+That way I could test both with a headset in VR and on a laptop with the mouse
+### Instructions on building/assembling/etc. Also document how to run the code
+No assembly required, just load the index.html page (which will load my js/diamond_square.js algorithm and the external libs).
+
+Note: it may take a moment to generate the terrain at the start
+### When in VR mode, did you feel any motion sickness? Why and why not?
+No, I think it is because there isn't much movement going on in the scene so there isn't an opportunity to get motion sickness
+### What was the hardest part of the assignment?
+A lot of it was getting used to Threejs and the small quirks, like learning that number of vertices in a plane is (segments+1) * (segments+1)
+
+I'd say the terrain generation algorithm took the longest. I originally went with some sort of smooth randomness but that didn't look as cool.
+### What do you wish you’d done differently?
+Now that I've finished the project I feel like I understand threejs a lot better so if I did the project again I would
+take it in a different direction and try to generate better terrain, trees, or look into flowing water.
+
+There is a bug in the square section of DS that leads to some vertices not getting averaged correctly which is unfortunate and if I had a bit more time I'd fix that.
+### What do you wish we had done differently?
+Not much, the difficulty in the project was mainly getting into threejs. You guys gave us extra time which was really helpful. I think it would be interesting to try to do a similar assignment again at the end of the semester and see how we progress
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..5a541fe
--- /dev/null
+++ b/index.html
@@ -0,0 +1,422 @@
+
+
+
+
+ Luke three.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Click to play
+
(W, A, S, D = Move, SPACE = Jump, MOUSE = Look around)
+
+
+
+
+
+
+
+
diff --git a/js/diamond_square.js b/js/diamond_square.js
new file mode 100644
index 0000000..c83f5bc
--- /dev/null
+++ b/js/diamond_square.js
@@ -0,0 +1,44 @@
+// my terrain generation algorithm
+// The inputs are side_length (the plane must have sides whose length is of the form 2^n -1),
+// magnitude (sets the bounds on the randomness),
+// reduction_scale (how fast the algorithm 'levels out')
+var diamondSquare = function diamondSquare(side_length, magnitude, reduction_scale) {
+ // think this is how you do 2d arrays in javascript
+ arr = new Array(side_length);
+ for (var i = 0; i < side_length; i++) {
+ arr[i] = new Float32Array(side_length);
+ }
+ // initialize the sides
+ arr[0][0] = Math.random() * magnitude * 2 - magnitude;
+ arr[0][side_length-1] = Math.random() * magnitude * 2 - magnitude;
+ arr[side_length-1][0] = Math.random() * magnitude * 2 - magnitude;
+ arr[side_length-1][side_length-1] = Math.random() * magnitude- magnitude;
+
+ function dsHelper(top_i,top_j,sub_length,m,s) {
+ if (sub_length <= 0) {
+ return;
+ }
+ var half_sub_length = Math.floor(sub_length/2);
+
+ // DIAMOND
+ var corners = [
+ arr[top_i][top_j],
+ arr[top_i + sub_length][top_j],
+ arr[top_i][top_j + sub_length],
+ arr[top_i + sub_length][top_j + sub_length]
+ ];
+ var avg = corners.reduce(function(a,b) {return a+b;}) / 4;
+ arr[top_i + half_sub_length][top_j + half_sub_length] = avg + Math.random() * m;
+
+
+
+ dsHelper(top_i,top_j, half_sub_length, m*s,s);
+ dsHelper(top_i,top_j + half_sub_length, half_sub_length,m*s,s);
+ dsHelper(top_i + half_sub_length,top_j, half_sub_length,m*s,s);
+ dsHelper(top_i + half_sub_length,top_j + half_sub_length, half_sub_length,m*s,s);
+ }
+ dsHelper(0,0,side_length-1,magnitude,reduction_scale);
+
+
+ return arr;
+}
diff --git a/js/lib/PointerLockControls.js b/js/lib/PointerLockControls.js
new file mode 100644
index 0000000..b0d7dc8
--- /dev/null
+++ b/js/lib/PointerLockControls.js
@@ -0,0 +1,69 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.PointerLockControls = function ( camera ) {
+
+ var scope = this;
+
+ camera.rotation.set( 0, 0, 0 );
+
+ var pitchObject = new THREE.Object3D();
+ pitchObject.add( camera );
+
+ var yawObject = new THREE.Object3D();
+ yawObject.position.y = 10;
+ yawObject.add( pitchObject );
+
+ var PI_2 = Math.PI / 2;
+
+ var onMouseMove = function ( event ) {
+
+ if ( scope.enabled === false ) return;
+
+ var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
+ var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+
+ yawObject.rotation.y -= movementX * 0.002;
+ pitchObject.rotation.x -= movementY * 0.002;
+
+ pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );
+
+ };
+
+ this.dispose = function() {
+
+ document.removeEventListener( 'mousemove', onMouseMove, false );
+
+ };
+
+ document.addEventListener( 'mousemove', onMouseMove, false );
+
+ this.enabled = false;
+
+ this.getObject = function () {
+
+ return yawObject;
+
+ };
+
+ this.getDirection = function() {
+
+ // assumes the camera itself is not rotated
+
+ var direction = new THREE.Vector3( 0, 0, - 1 );
+ var rotation = new THREE.Euler( 0, 0, 0, "YXZ" );
+
+ return function( v ) {
+
+ rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 );
+
+ v.copy( direction ).applyEuler( rotation );
+
+ return v;
+
+ };
+
+ }();
+
+};
diff --git a/js/lib/VRControls.js b/js/lib/VRControls.js
new file mode 100644
index 0000000..6eecd60
--- /dev/null
+++ b/js/lib/VRControls.js
@@ -0,0 +1,173 @@
+/**
+ * @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.resetPose = function () {
+
+ if ( vrDisplay ) {
+
+ vrDisplay.resetPose();
+
+ }
+
+ };
+
+ this.resetSensor = function () {
+
+ console.warn( 'THREE.VRControls: .resetSensor() is now .resetPose().' );
+ this.resetPose();
+
+ };
+
+ this.zeroSensor = function () {
+
+ console.warn( 'THREE.VRControls: .zeroSensor() is now .resetPose().' );
+ this.resetPose();
+
+ };
+
+ this.dispose = function () {
+
+ vrDisplay = null;
+
+ };
+
+};
diff --git a/js/lib/VREffect.js b/js/lib/VREffect.js
new file mode 100644
index 0000000..5911ea1
--- /dev/null
+++ b/js/lib/VREffect.js
@@ -0,0 +1,477 @@
+/**
+ * @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;
+ this.scale = 1;
+
+ 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 );
+ camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale );
+
+ var scale = this.scale;
+ cameraL.translateOnAxis( eyeTranslationL, scale );
+ cameraR.translateOnAxis( eyeTranslationR, scale );
+
+ 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/js/lib/WebVR.js b/js/lib/WebVR.js
new file mode 100644
index 0000000..0c37d1b
--- /dev/null
+++ b/js/lib/WebVR.js
@@ -0,0 +1,102 @@
+/**
+ * @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;
+
+ },
+
+ 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 webvr.info 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 ( effect ) {
+
+ 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';
+ button.textContent = 'ENTER VR';
+ button.onclick = function() {
+
+ effect.isPresenting ? effect.exitPresent() : effect.requestPresent();
+
+ };
+
+ window.addEventListener( 'vrdisplaypresentchange', function ( event ) {
+
+ button.textContent = effect.isPresenting ? 'EXIT VR' : 'ENTER VR';
+
+ }, false );
+
+ return button;
+
+ }
+
+};