WebVRManager.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /**
  2. * @author mrdoob / http://mrdoob.com/
  3. */
  4. import { Group } from '../../objects/Group.js';
  5. import { Matrix4 } from '../../math/Matrix4.js';
  6. import { Vector2 } from '../../math/Vector2.js';
  7. import { Vector3 } from '../../math/Vector3.js';
  8. import { Vector4 } from '../../math/Vector4.js';
  9. import { Quaternion } from '../../math/Quaternion.js';
  10. import { ArrayCamera } from '../../cameras/ArrayCamera.js';
  11. import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
  12. import { WebGLAnimation } from '../webgl/WebGLAnimation.js';
  13. import { setProjectionFromUnion } from './WebVRUtils.js';
  14. function WebVRManager( renderer ) {
  15. var scope = this;
  16. var device = null;
  17. var frameData = null;
  18. var poseTarget = null;
  19. var controllers = [];
  20. var standingMatrix = new Matrix4();
  21. var standingMatrixInverse = new Matrix4();
  22. var framebufferScaleFactor = 1.0;
  23. var frameOfReferenceType = 'stage';
  24. if ( typeof window !== 'undefined' && 'VRFrameData' in window ) {
  25. frameData = new window.VRFrameData();
  26. window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );
  27. }
  28. var matrixWorldInverse = new Matrix4();
  29. var tempQuaternion = new Quaternion();
  30. var tempPosition = new Vector3();
  31. var cameraL = new PerspectiveCamera();
  32. cameraL.bounds = new Vector4( 0.0, 0.0, 0.5, 1.0 );
  33. cameraL.layers.enable( 1 );
  34. var cameraR = new PerspectiveCamera();
  35. cameraR.bounds = new Vector4( 0.5, 0.0, 0.5, 1.0 );
  36. cameraR.layers.enable( 2 );
  37. var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );
  38. cameraVR.layers.enable( 1 );
  39. cameraVR.layers.enable( 2 );
  40. //
  41. function isPresenting() {
  42. return device !== null && device.isPresenting === true;
  43. }
  44. var currentSize = new Vector2(), currentPixelRatio;
  45. function onVRDisplayPresentChange() {
  46. if ( isPresenting() ) {
  47. var eyeParameters = device.getEyeParameters( 'left' );
  48. var renderWidth = eyeParameters.renderWidth * framebufferScaleFactor;
  49. var renderHeight = eyeParameters.renderHeight * framebufferScaleFactor;
  50. currentPixelRatio = renderer.getPixelRatio();
  51. renderer.getSize( currentSize );
  52. renderer.setDrawingBufferSize( renderWidth * 2, renderHeight, 1 );
  53. animation.start();
  54. } else {
  55. if ( scope.enabled ) {
  56. renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio );
  57. }
  58. animation.stop();
  59. }
  60. }
  61. //
  62. var triggers = [];
  63. function findGamepad( id ) {
  64. var gamepads = navigator.getGamepads && navigator.getGamepads();
  65. for ( var i = 0, j = 0, l = gamepads.length; i < l; i ++ ) {
  66. var gamepad = gamepads[ i ];
  67. if ( gamepad && ( gamepad.id === 'Daydream Controller' ||
  68. gamepad.id === 'Gear VR Controller' || gamepad.id === 'Oculus Go Controller' ||
  69. gamepad.id === 'OpenVR Gamepad' || gamepad.id.startsWith( 'Oculus Touch' ) ||
  70. gamepad.id.startsWith( 'Spatial Controller' ) ) ) {
  71. if ( j === id ) return gamepad;
  72. j ++;
  73. }
  74. }
  75. }
  76. function updateControllers() {
  77. for ( var i = 0; i < controllers.length; i ++ ) {
  78. var controller = controllers[ i ];
  79. var gamepad = findGamepad( i );
  80. if ( gamepad !== undefined && gamepad.pose !== undefined ) {
  81. if ( gamepad.pose === null ) return;
  82. // Pose
  83. var pose = gamepad.pose;
  84. if ( pose.hasPosition === false ) controller.position.set( 0.2, - 0.6, - 0.05 );
  85. if ( pose.position !== null ) controller.position.fromArray( pose.position );
  86. if ( pose.orientation !== null ) controller.quaternion.fromArray( pose.orientation );
  87. controller.matrix.compose( controller.position, controller.quaternion, controller.scale );
  88. controller.matrix.premultiply( standingMatrix );
  89. controller.matrix.decompose( controller.position, controller.quaternion, controller.scale );
  90. controller.matrixWorldNeedsUpdate = true;
  91. controller.visible = true;
  92. // Trigger
  93. var buttonId = gamepad.id === 'Daydream Controller' ? 0 : 1;
  94. if ( triggers[ i ] !== gamepad.buttons[ buttonId ].pressed ) {
  95. triggers[ i ] = gamepad.buttons[ buttonId ].pressed;
  96. if ( triggers[ i ] === true ) {
  97. controller.dispatchEvent( { type: 'selectstart' } );
  98. } else {
  99. controller.dispatchEvent( { type: 'selectend' } );
  100. controller.dispatchEvent( { type: 'select' } );
  101. }
  102. }
  103. } else {
  104. controller.visible = false;
  105. }
  106. }
  107. }
  108. //
  109. this.enabled = false;
  110. this.getController = function ( id ) {
  111. var controller = controllers[ id ];
  112. if ( controller === undefined ) {
  113. controller = new Group();
  114. controller.matrixAutoUpdate = false;
  115. controller.visible = false;
  116. controllers[ id ] = controller;
  117. }
  118. return controller;
  119. };
  120. this.getDevice = function () {
  121. return device;
  122. };
  123. this.setDevice = function ( value ) {
  124. if ( value !== undefined ) device = value;
  125. animation.setContext( value );
  126. };
  127. this.setFramebufferScaleFactor = function ( value ) {
  128. framebufferScaleFactor = value;
  129. };
  130. this.setFrameOfReferenceType = function ( value ) {
  131. frameOfReferenceType = value;
  132. };
  133. this.setPoseTarget = function ( object ) {
  134. if ( object !== undefined ) poseTarget = object;
  135. };
  136. this.getCamera = function ( camera ) {
  137. var userHeight = frameOfReferenceType === 'stage' ? 1.6 : 0;
  138. if ( isPresenting() === false ) {
  139. camera.position.set( 0, userHeight, 0 );
  140. return camera;
  141. }
  142. device.depthNear = camera.near;
  143. device.depthFar = camera.far;
  144. device.getFrameData( frameData );
  145. //
  146. if ( frameOfReferenceType === 'stage' ) {
  147. var stageParameters = device.stageParameters;
  148. if ( stageParameters ) {
  149. standingMatrix.fromArray( stageParameters.sittingToStandingTransform );
  150. } else {
  151. standingMatrix.makeTranslation( 0, userHeight, 0 );
  152. }
  153. }
  154. var pose = frameData.pose;
  155. var poseObject = poseTarget !== null ? poseTarget : camera;
  156. // We want to manipulate poseObject by its position and quaternion components since users may rely on them.
  157. poseObject.matrix.copy( standingMatrix );
  158. poseObject.matrix.decompose( poseObject.position, poseObject.quaternion, poseObject.scale );
  159. if ( pose.orientation !== null ) {
  160. tempQuaternion.fromArray( pose.orientation );
  161. poseObject.quaternion.multiply( tempQuaternion );
  162. }
  163. if ( pose.position !== null ) {
  164. tempQuaternion.setFromRotationMatrix( standingMatrix );
  165. tempPosition.fromArray( pose.position );
  166. tempPosition.applyQuaternion( tempQuaternion );
  167. poseObject.position.add( tempPosition );
  168. }
  169. poseObject.updateMatrixWorld();
  170. //
  171. cameraL.near = camera.near;
  172. cameraR.near = camera.near;
  173. cameraL.far = camera.far;
  174. cameraR.far = camera.far;
  175. cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix );
  176. cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix );
  177. // TODO (mrdoob) Double check this code
  178. standingMatrixInverse.getInverse( standingMatrix );
  179. if ( frameOfReferenceType === 'stage' ) {
  180. cameraL.matrixWorldInverse.multiply( standingMatrixInverse );
  181. cameraR.matrixWorldInverse.multiply( standingMatrixInverse );
  182. }
  183. var parent = poseObject.parent;
  184. if ( parent !== null ) {
  185. matrixWorldInverse.getInverse( parent.matrixWorld );
  186. cameraL.matrixWorldInverse.multiply( matrixWorldInverse );
  187. cameraR.matrixWorldInverse.multiply( matrixWorldInverse );
  188. }
  189. // envMap and Mirror needs camera.matrixWorld
  190. cameraL.matrixWorld.getInverse( cameraL.matrixWorldInverse );
  191. cameraR.matrixWorld.getInverse( cameraR.matrixWorldInverse );
  192. cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix );
  193. cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix );
  194. setProjectionFromUnion( cameraVR, cameraL, cameraR );
  195. //
  196. var layers = device.getLayers();
  197. if ( layers.length ) {
  198. var layer = layers[ 0 ];
  199. if ( layer.leftBounds !== null && layer.leftBounds.length === 4 ) {
  200. cameraL.bounds.fromArray( layer.leftBounds );
  201. }
  202. if ( layer.rightBounds !== null && layer.rightBounds.length === 4 ) {
  203. cameraR.bounds.fromArray( layer.rightBounds );
  204. }
  205. }
  206. updateControllers();
  207. return cameraVR;
  208. };
  209. this.getStandingMatrix = function () {
  210. return standingMatrix;
  211. };
  212. this.isPresenting = isPresenting;
  213. // Animation Loop
  214. var animation = new WebGLAnimation();
  215. this.setAnimationLoop = function ( callback ) {
  216. animation.setAnimationLoop( callback );
  217. };
  218. this.submitFrame = function () {
  219. if ( isPresenting() ) device.submitFrame();
  220. };
  221. this.dispose = function () {
  222. if ( typeof window !== 'undefined' ) {
  223. window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange );
  224. }
  225. };
  226. }
  227. export { WebVRManager };