diff --git a/examples/grift_ar/awe-rift-dependencies.js b/examples/grift_ar/awe-rift-dependencies.js new file mode 100644 index 0000000..1169fe1 --- /dev/null +++ b/examples/grift_ar/awe-rift-dependencies.js @@ -0,0 +1,218 @@ +/* + NOTE: We have combined the minimum collection of required js libs awe depends + upon to make it easy for you to get started using the Oculus Rift +*/ + +// BEGIN FILE: OculusBridge.min.js +// See: https://github.com/Instrument/oculus-bridge +/*! OculusBridge 2013-07-30 */ +var OculusBridge=function(a){var b,c=!0,d=a.hasOwnProperty("address")?a.address:"localhost",e=a.hasOwnProperty("port")?a.port:9005,f=a.hasOwnProperty("retryInterval")?a.retryInterval:1,g=a.hasOwnProperty("debug")?a.debug:!1,h={x:0,y:0,z:0,w:0},i={FOV:125.871,hScreenSize:.14976,vScreenSize:.0935,vScreenCenter:.04675,eyeToScreenDistance:.041,lensSeparationDistance:.067,interpupillaryDistance:.0675,hResolution:1280,vResolution:720,distortionK:[1,.22,.24,0],chromaAbParameter:[.996,-.004,1.014,0]},j={onOrientationUpdate:null,onConfigUpdate:null,onConnect:null,onDisconnect:null};for(var k in j)"function"==typeof a[k]&&(j[k]=a[k]);var l=function(a){a.o&&4==a.o.length&&(h.x=Number(a.o[1]),h.y=Number(a.o[2]),h.z=Number(a.o[3]),h.w=Number(a.o[0]),j.onOrientationUpdate&&j.onOrientationUpdate(h))},m=function(a){i.hScreenSize=a.screenSize[0],i.vScreenSize=a.screenSize[1],i.vScreenCenter=a.screenSize[1]/2,i.eyeToScreenDistance=a.eyeToScreen,i.lensSeparationDistance=a.lensDistance,i.interpupillaryDistance=a.interpupillaryDistance,i.hResolution=a.screenResolution[0],i.vResolution=a.screenResolution[1],i.distortionK=[a.distortion[0],a.distortion[1],a.distortion[2],a.distortion[3]],i.FOV=a.fov,j.onConfigUpdate&&j.onConfigUpdate(i)},n=function(){c=!0;var a="ws://"+d+":"+e+"/";b=new WebSocket(a),o("Attempting to connect: "+a),b.onopen=function(){o("Connected!"),j.onConnect&&j.onConnect()},b.onerror=function(){o("Socket error.")},b.onmessage=function(a){var b=JSON.parse(a.data),c=b.m;switch(c){case"config":m(b);break;case"orientation":l(b);break;default:o("Unknown message received from server: "+a.data),q()}},b.onclose=function(){j.onDisconnect&&j.onDisconnect(),c&&(o("Connection failed, retrying in 1 second..."),window.setTimeout(p,1e3*f))}},o=function(a){g&&console.log("OculusBridge: "+a)},p=function(){n()},q=function(){c=!1,b.close()},r=function(){return i},s=function(){return h},t=function(){return 1==b.readyState};return{isConnected:t,disconnect:q,connect:n,getOrientation:s,getConfiguration:r}}; +// END FILE: OculusBridge.min.js + +// BEGIN FILE: OculusRiftEffect.js +// See: https://github.com/mrdoob/three.js/blob/master/examples/js/effects/OculusRiftEffect.js +/** + * @author troffmo5 / http://github.com/troffmo5 + * + * Effect to render the scene in stereo 3d side by side with lens distortion. + * It is written to be used with the Oculus Rift (http://www.oculusvr.com/) but + * it works also with other HMD using the same technology + */ + +THREE.OculusRiftEffect = function ( renderer, options ) { + // worldFactor indicates how many units is 1 meter + var worldFactor = (options && options.worldFactor) ? options.worldFactor: 1.0; + + // Specific HMD parameters + var HMD = (options && options.HMD) ? options.HMD: { + // Parameters from the Oculus Rift DK1 + hResolution: 1280, + vResolution: 720, + hScreenSize: 0.14976, + vScreenSize: 0.0935, + interpupillaryDistance: 0.064, + lensSeparationDistance: 0.0635, + eyeToScreenDistance: 0.041, + distortionK : [1.0, 0.22, 0.24, 0.0], + chromaAbParameter : [0.996, -0.004, 1.014, 0] + }; + + // Perspective camera + var pCamera = new THREE.PerspectiveCamera(); + pCamera.matrixAutoUpdate = false; + pCamera.target = new THREE.Vector3(); + + // Orthographic camera + var oCamera = new THREE.OrthographicCamera( -1, 1, 1, -1, 0.0001, 100000 ); + oCamera.position.z = 1; + + // pre-render hooks + this.preLeftRender = function() {}; + this.preRightRender = function() {}; + + //renderer.autoClear = false; + var emptyColor = new THREE.Color("black"); + + // Render target + var RTParams = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }; + var renderTarget = new THREE.WebGLRenderTarget( 640, 800, RTParams ); + var RTMaterial = new THREE.ShaderMaterial( { + uniforms: { + "texid": { type: "t", value: renderTarget }, + "scale": { type: "v2", value: new THREE.Vector2(1.0,1.0) }, + "scaleIn": { type: "v2", value: new THREE.Vector2(1.0,1.0) }, + "lensCenter": { type: "v2", value: new THREE.Vector2(0.0,0.0) }, + "hmdWarpParam": { type: "v4", value: new THREE.Vector4(1.0,0.0,0.0,0.0) }, + "chromAbParam": { type: "v4", value: new THREE.Vector4(1.0,0.0,0.0,0.0) } + }, + vertexShader: [ + "varying vec2 vUv;", + "void main() {", + " vUv = uv;", + " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + "}" + ].join("\n"), + + fragmentShader: [ + "uniform vec2 scale;", + "uniform vec2 scaleIn;", + "uniform vec2 lensCenter;", + "uniform vec4 hmdWarpParam;", + 'uniform vec4 chromAbParam;', + "uniform sampler2D texid;", + "varying vec2 vUv;", + "void main()", + "{", + " vec2 uv = (vUv*2.0)-1.0;", // range from [0,1] to [-1,1] + " vec2 theta = (uv-lensCenter)*scaleIn;", + " float rSq = theta.x*theta.x + theta.y*theta.y;", + " vec2 rvector = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq);", + ' vec2 rBlue = rvector * (chromAbParam.z + chromAbParam.w * rSq);', + " vec2 tcBlue = (lensCenter + scale * rBlue);", + " tcBlue = (tcBlue+1.0)/2.0;", // range from [-1,1] to [0,1] + " if (any(bvec2(clamp(tcBlue, vec2(0.0,0.0), vec2(1.0,1.0))-tcBlue))) {", + " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", + " return;}", + " vec2 tcGreen = lensCenter + scale * rvector;", + " tcGreen = (tcGreen+1.0)/2.0;", // range from [-1,1] to [0,1] + " vec2 rRed = rvector * (chromAbParam.x + chromAbParam.y * rSq);", + " vec2 tcRed = lensCenter + scale * rRed;", + " tcRed = (tcRed+1.0)/2.0;", // range from [-1,1] to [0,1] + " gl_FragColor = vec4(texture2D(texid, tcRed).r, texture2D(texid, tcGreen).g, texture2D(texid, tcBlue).b, 1);", + "}" + ].join("\n") + } ); + + var mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), RTMaterial ); + + // Final scene + var finalScene = new THREE.Scene(); + finalScene.add( oCamera ); + finalScene.add( mesh ); + + var left = {}, right = {}; + var distScale = 1.0; + this.setHMD = function(v) { + HMD = v; + // Compute aspect ratio and FOV + var aspect = HMD.hResolution / (2*HMD.vResolution); + + // Fov is normally computed with: + // THREE.Math.radToDeg( 2*Math.atan2(HMD.vScreenSize,2*HMD.eyeToScreenDistance) ); + // But with lens distortion it is increased (see Oculus SDK Documentation) + var r = -1.0 - (4 * (HMD.hScreenSize/4 - HMD.lensSeparationDistance/2) / HMD.hScreenSize); + distScale = (HMD.distortionK[0] + HMD.distortionK[1] * Math.pow(r,2) + HMD.distortionK[2] * Math.pow(r,4) + HMD.distortionK[3] * Math.pow(r,6)); + var fov = HMD.fov ? HMD.fov : THREE.Math.radToDeg(2*Math.atan2(HMD.vScreenSize*distScale, 2*HMD.eyeToScreenDistance)); + + // Compute camera projection matrices + var proj = (new THREE.Matrix4()).makePerspective( fov, aspect, 0.3, 10000 ); + var h = 4 * (HMD.hScreenSize/4 - HMD.interpupillaryDistance/2) / HMD.hScreenSize; + left.proj = ((new THREE.Matrix4()).makeTranslation( h, 0.0, 0.0 )).multiply(proj); + right.proj = ((new THREE.Matrix4()).makeTranslation( -h, 0.0, 0.0 )).multiply(proj); + + // Compute camera transformation matrices + left.tranform = (new THREE.Matrix4()).makeTranslation( -worldFactor * HMD.interpupillaryDistance/2, 0.0, 0.0 ); + right.tranform = (new THREE.Matrix4()).makeTranslation( worldFactor * HMD.interpupillaryDistance/2, 0.0, 0.0 ); + + // Compute Viewport + left.viewport = [0, 0, HMD.hResolution/2, HMD.vResolution]; + right.viewport = [HMD.hResolution/2, 0, HMD.hResolution/2, HMD.vResolution]; + + // Distortion shader parameters + var lensShift = 4 * (HMD.hScreenSize/4 - HMD.lensSeparationDistance/2) / HMD.hScreenSize; + left.lensCenter = new THREE.Vector2(lensShift, 0.0); + right.lensCenter = new THREE.Vector2(-lensShift, 0.0); + + RTMaterial.uniforms['hmdWarpParam'].value = new THREE.Vector4(HMD.distortionK[0], HMD.distortionK[1], HMD.distortionK[2], HMD.distortionK[3]); + RTMaterial.uniforms['chromAbParam'].value = new THREE.Vector4(HMD.chromaAbParameter[0], HMD.chromaAbParameter[1], HMD.chromaAbParameter[2], HMD.chromaAbParameter[3]); + RTMaterial.uniforms['scaleIn'].value = new THREE.Vector2(1.0,1.0/aspect); + RTMaterial.uniforms['scale'].value = new THREE.Vector2(1.0/distScale, 1.0*aspect/distScale); + console.log(lensShift); + console.log("ScaleIn", new THREE.Vector2(1.0,1.0/aspect)); + console.log("Scale", new THREE.Vector2(1.0,1.0/aspect)); + + // Create render target + renderTarget = new THREE.WebGLRenderTarget( HMD.hResolution*distScale/2, HMD.vResolution*distScale, RTParams ); + RTMaterial.uniforms[ "texid" ].value = renderTarget; + + } + this.getHMD = function() {return HMD}; + + this.setHMD(HMD); + + this.setSize = function ( width, height ) { + left.viewport = [width/2 - HMD.hResolution/2, height/2 - HMD.vResolution/2, HMD.hResolution/2, HMD.vResolution]; + right.viewport = [width/2, height/2 - HMD.vResolution/2, HMD.hResolution/2, HMD.vResolution]; + + renderer.setSize( width, height ); + }; + + this.render = function ( scene, camera ) { + var cc = renderer.getClearColor().clone(); + var autoClear = renderer.autoClear; + + renderer.autoClear = false; + + // Clear + renderer.setClearColor(emptyColor); + renderer.clear(); + renderer.setClearColor(cc); + + // camera parameters + if (camera.matrixAutoUpdate) camera.updateMatrix(); + + // Render left + this.preLeftRender(); + + pCamera.projectionMatrix.copy(left.proj); + + pCamera.matrix.copy(camera.matrix).multiply(left.tranform); + pCamera.matrixWorldNeedsUpdate = true; + + renderer.setViewport(left.viewport[0], left.viewport[1], left.viewport[2], left.viewport[3]); + + RTMaterial.uniforms['lensCenter'].value = left.lensCenter; + renderer.render( scene, pCamera, renderTarget, true ); + + renderer.render( finalScene, oCamera ); + + // Render right + this.preRightRender(); + + pCamera.projectionMatrix.copy(right.proj); + + pCamera.matrix.copy(camera.matrix).multiply(right.tranform); + pCamera.matrixWorldNeedsUpdate = true; + + renderer.setViewport(right.viewport[0], right.viewport[1], right.viewport[2], right.viewport[3]); + + RTMaterial.uniforms['lensCenter'].value = right.lensCenter; + + renderer.render( scene, pCamera, renderTarget, true ); + renderer.render( finalScene, oCamera ); + + renderer.autoClear = autoClear; + }; + +}; +// END FILE: OculusRiftEffect.js diff --git a/examples/grift_ar/awe.grift_ar.js b/examples/grift_ar/awe.grift_ar.js new file mode 100644 index 0000000..58d45cb --- /dev/null +++ b/examples/grift_ar/awe.grift_ar.js @@ -0,0 +1,199 @@ +(function(awe) { + var oculus_bridge, + super_render, + rift_enabled = false, + bodyAngle = 0, + riftCam; + + function on_resize() { + if(!rift_enabled || !riftCam){ + windowHalf = new THREE.Vector2(window.innerWidth / 2, window.innerHeight / 2); + aspectRatio = window.innerWidth / window.innerHeight; + awe.pov().aspect = aspectRatio; + awe.pov().updateProjectionMatrix(); + awe.renderer().setSize(window.innerWidth, window.innerHeight); + } else { + riftCam.setSize(window.innerWidth, window.innerHeight); + } + awe.scene_needs_rendering = 1; + } + + function connect() { + oculus_bridge.connect(); + } + function disconnect() { + oculus_bridge.disconnect(); + } + function show_connection_state(state) { + var btn = document.querySelector('._rift_toggle') + btn.setAttribute('class', '_rift_toggle '+state) + btn.setAttribute('title', state) + } + + // add the rift plugin + awe.plugins.add([{ + id: 'rift_display', + register: function(plugin_data){ + // add video stream background + awe.setup_stream(); + awe.events.add([ + { + id: 'video_stream', + device_types: { + pc: 1 + }, + register: function(handler) { + window.addEventListener('gum_ready', handler, false); + }, + unregister: function(handler){ + window.removeEventListener('gum_ready', handler, false); + }, + handler: function(e) { + awe.projections.add({ + id: 'camera1', + position: { + x: 0, + y: 0, + z: -400 + }, + geometry: { + shape: 'plane', + width: 1200, + height: 900 + }, + texture: { + color: 0xFFFFFF, + path: 'camerastream' + } + }, { + parent: { + object_type: 'pov', + object_id: 'default' + } + }); + } + } + ]); + + awe.events.add([{ + id: 'rift_toggle', + device_types: { + pc: 1 + }, + register: function(handler) { + document.querySelector('._rift_toggle').addEventListener('click', handler, false); + }, + unregister: function(handler){ + document.querySelector('._rift_toggle').removeEventListener('click', handler, false); + }, + handler: function(e) { + e.preventDefault(); + rift_enabled = !rift_enabled; + on_resize(); + if (rift_enabled) { + connect(); + show_connection_state('connecting'); + } + else { + disconnect(); + show_connection_state('disconnected'); + } + } + }, + { + id: 'resize', + device_types: { + pc: 1 + }, + register: function(handler) { + window.addEventListener('resize', handler, false); + }, + unregister: function(handler){ + window.removeEventListener('resize', handler, false); + }, + handler: function(e) { + on_resize(); + } + }, + { + id: 'setup_bridge', + device_types: { + pc: 1 + }, + register: function(handler) { + oculus_bridge = new OculusBridge({ + onOrientationUpdate: handler, + onConnect: function(){ + show_connection_state('connected'); + }, + onDisconnect: function(){ + show_connection_state('disconnected'); + rift_enabled = false; + on_resize(); + } + }); + riftCam = new THREE.OculusRiftEffect(awe.renderer()); + }, + unregister: function(){ + oculus_bridge.disconnect(); + delete(oculus_bridge); + delete(riftCam); + rift_enabled = false; + on_resize(); + }, + handler: function(quatValues) { + var bodyAxis = new THREE.Vector3(0, 1, 0); + // make a quaternion for the the body angle rotated about the Y axis + var bodyQuat = new THREE.Quaternion(); + bodyQuat.setFromAxisAngle(bodyAxis, bodyAngle); + // make a quaternion for the current orientation of the Rift + var riftQuat = new THREE.Quaternion(quatValues.x, quatValues.y, quatValues.z, quatValues.w); + // multiply the body rotation by the Rift rotation. + bodyQuat.multiply(riftQuat); + // Make a vector pointing along the Z axis and rotate it + // according to the combined look+body angle. + var xzVector = new THREE.Vector3(0, 0, 1); + xzVector.applyQuaternion(bodyQuat); + // Compute the X/Z angle based on the combined look/body angle. + viewAngle = Math.atan2(xzVector.z, xzVector.x) + Math.PI; + // Update the camera so it matches the current view orientation + window.awe.povs.view('default').quaternion.copy(bodyQuat); + } + }, + { + id: 'tick', + device_types: { + pc: 1, + }, + register: function(handler) { + window.addEventListener('tick', handler, false); + }, + unregister: function(handler){ + window.removeEventListener('tick', handler, false); + }, + handler: function(event) { + if (rift_enabled && riftCam) { + riftCam.render(awe.scene(), awe.pov()); + } + else { + super_render(); + } + } + }]); + // overwrite the default renderer + super_render = awe.render; + document.getElementById('toggle').on('click', function(){ + // TODO + }) + }, + unregister: function(plugin_data){ + // restore default awe.render + awe.render = super_render; + awe.events.delete('rift_toggle'); + awe.events.delete('resize'); + awe.events.delete('bridge_update'); + awe.events.delete('tick'); + awe.render = super_render; + } + }]); +})(window.awe); diff --git a/examples/grift_ar/index.html b/examples/grift_ar/index.html new file mode 100644 index 0000000..15fa396 --- /dev/null +++ b/examples/grift_ar/index.html @@ -0,0 +1,214 @@ + + + +AWE GRiftAR (gUM + Rift + AR) demo + + + + + +
+Toggle rift +
NOTE: Requires the open source Oculus Bridge and an Oculus Rift
+ + + +