diff --git a/content-common.js b/content-common.js new file mode 100644 index 0000000..5fca6b5 --- /dev/null +++ b/content-common.js @@ -0,0 +1,24 @@ +var verbose = true; + +function log() { + + if( !verbose ) return; + + console.log.apply( + console, [ + `%c WebVREmu `, + 'background: #007AA3; color: #ffffff; text-shadow: 0 -1px #000; padding: 4px 0 4px 0; line-height: 0', + ...arguments + ] + ); + +} + +function injectScript( source ) { + + var script = document.createElement('script'); + script.textContent = source; + (document.head||document.documentElement).appendChild(script); + script.parentNode.removeChild(script); + +} \ No newline at end of file diff --git a/content-script.js b/content-script.js index 50b3d95..ba2a5c0 100644 --- a/content-script.js +++ b/content-script.js @@ -1,19 +1,3 @@ -var verbose = true; - -function log() { - - if( !verbose ) return; - - console.log.apply( - console, [ - `%c WebVREmu `, - 'background: #007AA3; color: #ffffff; text-shadow: 0 -1px #000; padding: 4px 0 4px 0; line-height: 0', - ...arguments - ] - ); - -} - log( 'Polyfill', window.location.toString() ); var port = chrome.runtime.connect( { name: 'contentScript' } ); @@ -62,9 +46,4 @@ window.addEventListener( 'webvr-resetpose', function() { } ); -var source = '(' + injectedScript + ')();'; - -var script = document.createElement('script'); -script.textContent = source; -(document.head||document.documentElement).appendChild(script); -script.parentNode.removeChild(script); +injectScript( '(' + injectUtils + ')();' + "\r\n" + '(' + injectedScript + ')();' ); \ No newline at end of file diff --git a/manifest.json b/manifest.json index 6e53209..5419846 100755 --- a/manifest.json +++ b/manifest.json @@ -15,7 +15,7 @@ ], "content_scripts": [{ "matches": [""], - "js": [ "polyfill.js", "content-script.js" ], + "js": [ "content-common.js", "webvr-1.1-utils.js", "polyfill.js", "content-script.js" ], "run_at": "document_start", "all_frames": true } ], diff --git a/polyfill.js b/polyfill.js index 840a294..25e923e 100644 --- a/polyfill.js +++ b/polyfill.js @@ -31,7 +31,7 @@ function injectedScript() { // WebVR 1.0 - function VRDisplayCapabilities () { + function EmuVRDisplayCapabilities () { this.canPresent = true; this.hasExternalDisplay = true; @@ -41,7 +41,7 @@ function injectedScript() { } - function VRStageParameters() { + function EmuVRStageParameters() { this.sittingToStandingTransform = new Float32Array( [ 1, 0, 0, 0, @@ -55,7 +55,7 @@ function injectedScript() { } - function VRPose() { + function EmuVRPose() { this.timestamp = startDate + ( performance.now() - startPerfNow ); this.position = new Float32Array( [ 0, 0, 0 ] ); @@ -67,7 +67,7 @@ function injectedScript() { } - function VRFieldOfView() { + function EmuVRFieldOfView() { this.upDegrees = 0; this.downDegrees = 0; @@ -76,10 +76,10 @@ function injectedScript() { } - function VREyeParameters() { + function EmuVREyeParameters() { this.offset = new Float32Array( [ 0, 0, 0 ] ); - this.fieldOfView = new VRFieldOfView(); + this.fieldOfView = new EmuVRFieldOfView(); this.renderWidth = 0; this.renderHeight = 0; @@ -95,7 +95,7 @@ function injectedScript() { } - function VRDisplay( model ) { + function EmuVRDisplay( model ) { this.depthFar = 1000; this.depthNear = .1; @@ -104,17 +104,17 @@ function injectedScript() { this.isConnected = true; this.isPresenting = false; - this.stageParameters = new VRStageParameters(); + this.stageParameters = new EmuVRStageParameters(); - this.capabilities = new VRDisplayCapabilities(); + this.capabilities = new EmuVRDisplayCapabilities(); this.capabilities.canPresent = model.features.canPresent; this.capabilities.hasExternalDisplay = model.features.hasExternalDisplay; this.capabilities.hasOrientation = model.features.hasOrientation; this.capabilities.hasPosition = model.features.hasPosition; - this.pose = new VRPose(); + this.pose = new EmuVRPose(); - this.leftEyeParameters = new VREyeParameters(); + this.leftEyeParameters = new EmuVREyeParameters(); this.leftEyeParameters.fieldOfView.upDegrees = model.leftEye.up; this.leftEyeParameters.fieldOfView.downDegrees = model.leftEye.down; this.leftEyeParameters.fieldOfView.leftDegrees = model.leftEye.left; @@ -123,7 +123,7 @@ function injectedScript() { this.leftEyeParameters.renderHeight = model.resolution.height; this.leftEyeParameters.offset[ 0 ] = model.leftEye.offset; - this.rightEyeParameters = new VREyeParameters(); + this.rightEyeParameters = new EmuVREyeParameters(); this.rightEyeParameters.fieldOfView.upDegrees = model.rightEye.up; this.rightEyeParameters.fieldOfView.downDegrees = model.rightEye.down; this.rightEyeParameters.fieldOfView.leftDegrees = model.rightEye.left; @@ -163,26 +163,26 @@ function injectedScript() { } - VRDisplay.prototype.requestAnimationFrame = function( c ) { + EmuVRDisplay.prototype.requestAnimationFrame = function( c ) { return requestAnimationFrame( c ); } - VRDisplay.prototype.cancelAnimationFrame = function(handle) { + EmuVRDisplay.prototype.cancelAnimationFrame = function(handle) { cancelAnimationFrame(handle); } - VRDisplay.prototype.getEyeParameters = function( id ) { + EmuVRDisplay.prototype.getEyeParameters = function( id ) { if( id === 'left' ) return this.leftEyeParameters; return this.rightEyeParameters; } - VRDisplay.prototype.getPose = function() { + EmuVRDisplay.prototype.getPose = function() { this.pose.timestamp = startDate + ( performance.now() - startPerfNow ); @@ -190,7 +190,7 @@ function injectedScript() { } - VRDisplay.prototype.requestPresent = function() { + EmuVRDisplay.prototype.requestPresent = function() { return new Promise( function( resolve, reject ) { @@ -205,7 +205,7 @@ function injectedScript() { } - VRDisplay.prototype.exitPresent = function() { + EmuVRDisplay.prototype.exitPresent = function() { return new Promise( function( resolve, reject ) { @@ -220,36 +220,34 @@ function injectedScript() { } - VRDisplay.prototype.submitFrame = function( pose ) { + EmuVRDisplay.prototype.submitFrame = function( pose ) { } - VRDisplay.prototype.resetPose = function() { + EmuVRDisplay.prototype.resetPose = function() { var event = new Event( 'webvr-resetpose' ); window.dispatchEvent( event ); } - window.VRDisplay = VRDisplay; - - ( function() { - - var vrD = new VRDisplay( ViveData ) - - navigator.getVRDisplays = function() { + EmuVRDisplay.prototype.getFrameData = function( frameData ) { - return new Promise( function( resolve, reject ) { - - resolve( [ vrD ] ); + return frameDataFromPose( frameData, this.getPose(), this ); - } ); + } - } - - } )(); + function EmuVRFrameData() { + this.leftProjectionMatrix = new Float32Array(16); + this.leftViewMatrix = new Float32Array(16); + this.rightProjectionMatrix = new Float32Array(16); + this.rightViewMatrix = new Float32Array(16); + this.pose = null; + }; + window.VRFrameData = EmuVRFrameData; + // LEGACY - +/* function HMDVRDevice( model ) { this.deviceName = model.name; @@ -409,6 +407,52 @@ function injectedScript() { } )(); + var event = new Event( 'webvr-ready' ); + window.dispatchEvent( event );*/ + + if( window.VRDisplayCapabilities === undefined ) { + + window.VRDisplayCapabilities = EmuVRDisplayCapabilities; + + } + + if( window.VRDisplay === undefined ) { + + window.VRDisplay = EmuVRDisplay; + + } + + var vrD = new EmuVRDisplay( ViveData ) + + var _getVRDisplays = navigator.getVRDisplays; + + if( navigator.getVRDisplays === undefined ) { + + navigator.getVRDisplays = function() { + + return new Promise( function( resolve, reject ) { + + resolve( [ vrD ] ); + + } ); + + } + + } else { + + navigator.getVRDisplays = function() { + + return new Promise( ( resolve, reject ) => { + + console.log( 'getVRDisplays' ); + _getVRDisplays.apply( this, arguments ).then( res => { res.unshift( vrD ); resolve( res ); } ).catch( e => reject( e ) ); + + } ); + + } + + } + var event = new Event( 'webvr-ready' ); window.dispatchEvent( event ); diff --git a/webvr-1.1-utils.js b/webvr-1.1-utils.js new file mode 100644 index 0000000..09c3e8c --- /dev/null +++ b/webvr-1.1-utils.js @@ -0,0 +1,182 @@ +function injectUtils() { + + window.frameDataFromPose = (function() { + + var piOver180 = Math.PI / 180.0; + var rad45 = Math.PI * 0.25; + + // Borrowed from glMatrix. + function mat4_perspectiveFromFieldOfView(out, fov, near, far) { + var upTan = Math.tan(fov ? (fov.upDegrees * piOver180) : rad45), + downTan = Math.tan(fov ? (fov.downDegrees * piOver180) : rad45), + leftTan = Math.tan(fov ? (fov.leftDegrees * piOver180) : rad45), + rightTan = Math.tan(fov ? (fov.rightDegrees * piOver180) : rad45), + xScale = 2.0 / (leftTan + rightTan), + yScale = 2.0 / (upTan + downTan); + + out[0] = xScale; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + out[4] = 0.0; + out[5] = yScale; + out[6] = 0.0; + out[7] = 0.0; + out[8] = -((leftTan - rightTan) * xScale * 0.5); + out[9] = ((upTan - downTan) * yScale * 0.5); + out[10] = far / (near - far); + out[11] = -1.0; + out[12] = 0.0; + out[13] = 0.0; + out[14] = (far * near) / (near - far); + out[15] = 0.0; + return out; + } + + function mat4_fromRotationTranslation(out, q, v) { + // Quaternion math + var x = q[0], y = q[1], z = q[2], w = q[3], + x2 = x + x, + y2 = y + y, + z2 = z + z, + + xx = x * x2, + xy = x * y2, + xz = x * z2, + yy = y * y2, + yz = y * z2, + zz = z * z2, + wx = w * x2, + wy = w * y2, + wz = w * z2; + + out[0] = 1 - (yy + zz); + out[1] = xy + wz; + out[2] = xz - wy; + out[3] = 0; + out[4] = xy - wz; + out[5] = 1 - (xx + zz); + out[6] = yz + wx; + out[7] = 0; + out[8] = xz + wy; + out[9] = yz - wx; + out[10] = 1 - (xx + yy); + out[11] = 0; + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + + return out; + }; + + function mat4_translate(out, a, v) { + var x = v[0], y = v[1], z = v[2], + a00, a01, a02, a03, + a10, a11, a12, a13, + a20, a21, a22, a23; + + if (a === out) { + out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; + out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; + out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; + out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; + } else { + a00 = a[0]; a01 = a[1]; a02 = a[2]; a03 = a[3]; + a10 = a[4]; a11 = a[5]; a12 = a[6]; a13 = a[7]; + a20 = a[8]; a21 = a[9]; a22 = a[10]; a23 = a[11]; + + out[0] = a00; out[1] = a01; out[2] = a02; out[3] = a03; + out[4] = a10; out[5] = a11; out[6] = a12; out[7] = a13; + out[8] = a20; out[9] = a21; out[10] = a22; out[11] = a23; + + out[12] = a00 * x + a10 * y + a20 * z + a[12]; + out[13] = a01 * x + a11 * y + a21 * z + a[13]; + out[14] = a02 * x + a12 * y + a22 * z + a[14]; + out[15] = a03 * x + a13 * y + a23 * z + a[15]; + } + + return out; + }; + + mat4_invert = function(out, a) { + var a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], + a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], + a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], + a30 = a[12], a31 = a[13], a32 = a[14], a33 = a[15], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + // Calculate the determinant + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + det = 1.0 / det; + + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + + return out; + }; + + var defaultOrientation = new Float32Array([0, 0, 0, 1]); + var defaultPosition = new Float32Array([0, 0, 0]); + + function updateEyeMatrices(projection, view, pose, parameters, vrDisplay) { + mat4_perspectiveFromFieldOfView(projection, parameters ? parameters.fieldOfView : null, vrDisplay.depthNear, vrDisplay.depthFar); + + var orientation = pose.orientation || defaultOrientation; + var position = pose.position || defaultPosition; + + mat4_fromRotationTranslation(view, orientation, position); + if (parameters) + mat4_translate(view, view, parameters.offset); + mat4_invert(view, view); + } + + return function(frameData, pose, vrDisplay) { + if (!frameData || !pose) + return false; + + frameData.pose = pose; + frameData.timestamp = pose.timestamp; + + updateEyeMatrices( + frameData.leftProjectionMatrix, frameData.leftViewMatrix, + pose, vrDisplay.getEyeParameters("left"), vrDisplay); + updateEyeMatrices( + frameData.rightProjectionMatrix, frameData.rightViewMatrix, + pose, vrDisplay.getEyeParameters("right"), vrDisplay); + + return true; + }; +})(); + +};