WebXRManager.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /**
  2. * @author mrdoob / http://mrdoob.com/
  3. */
  4. import { ArrayCamera } from '../../cameras/ArrayCamera.js';
  5. import { EventDispatcher } from '../../core/EventDispatcher.js';
  6. import { Group } from '../../objects/Group.js';
  7. import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
  8. import { Vector3 } from '../../math/Vector3.js';
  9. import { Vector4 } from '../../math/Vector4.js';
  10. import { WebGLAnimation } from '../webgl/WebGLAnimation.js';
  11. function WebXRManager( renderer, gl ) {
  12. var scope = this;
  13. var session = null;
  14. // var framebufferScaleFactor = 1.0;
  15. var referenceSpace = null;
  16. var referenceSpaceType = 'local-floor';
  17. var pose = null;
  18. var controllers = [];
  19. var sortedInputSources = [];
  20. function isPresenting() {
  21. return session !== null && referenceSpace !== null;
  22. }
  23. //
  24. var cameraL = new PerspectiveCamera();
  25. cameraL.layers.enable( 1 );
  26. cameraL.viewport = new Vector4();
  27. var cameraR = new PerspectiveCamera();
  28. cameraR.layers.enable( 2 );
  29. cameraR.viewport = new Vector4();
  30. var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );
  31. cameraVR.layers.enable( 1 );
  32. cameraVR.layers.enable( 2 );
  33. //
  34. this.enabled = false;
  35. this.getController = function ( id ) {
  36. var controller = controllers[ id ];
  37. if ( controller === undefined ) {
  38. controller = new Group();
  39. controller.matrixAutoUpdate = false;
  40. controller.visible = false;
  41. controllers[ id ] = controller;
  42. }
  43. return controller;
  44. };
  45. //
  46. function onSessionEvent( event ) {
  47. for ( var i = 0; i < controllers.length; i ++ ) {
  48. if ( sortedInputSources[ i ] === event.inputSource ) {
  49. controllers[ i ].dispatchEvent( { type: event.type } );
  50. }
  51. }
  52. }
  53. function onSessionEnd() {
  54. renderer.setFramebuffer( null );
  55. renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
  56. animation.stop();
  57. scope.dispatchEvent( { type: 'sessionend' } );
  58. }
  59. function onRequestReferenceSpace( value ) {
  60. referenceSpace = value;
  61. animation.setContext( session );
  62. animation.start();
  63. scope.dispatchEvent( { type: 'sessionstart' } );
  64. }
  65. this.setFramebufferScaleFactor = function ( /* value */ ) {
  66. // framebufferScaleFactor = value;
  67. };
  68. this.setReferenceSpaceType = function ( value ) {
  69. referenceSpaceType = value;
  70. };
  71. this.getSession = function () {
  72. return session;
  73. };
  74. this.setSession = function ( value ) {
  75. session = value;
  76. if ( session !== null ) {
  77. session.addEventListener( 'select', onSessionEvent );
  78. session.addEventListener( 'selectstart', onSessionEvent );
  79. session.addEventListener( 'selectend', onSessionEvent );
  80. session.addEventListener( 'squeeze', onSessionEvent );
  81. session.addEventListener( 'squeezestart', onSessionEvent );
  82. session.addEventListener( 'squeezeend', onSessionEvent );
  83. session.addEventListener( 'end', onSessionEnd );
  84. var attributes = gl.getContextAttributes();
  85. var layerInit = {
  86. antialias: attributes.antialias,
  87. alpha: attributes.alpha,
  88. depth: attributes.depth,
  89. stencil: attributes.stencil
  90. };
  91. // eslint-disable-next-line no-undef
  92. var baseLayer = new XRWebGLLayer( session, gl, layerInit );
  93. session.updateRenderState( { baseLayer: baseLayer } );
  94. session.requestReferenceSpace( referenceSpaceType ).then( onRequestReferenceSpace );
  95. //
  96. session.addEventListener( 'inputsourceschange', updateInputSources );
  97. updateInputSources();
  98. }
  99. };
  100. function updateInputSources() {
  101. for ( var i = 0; i < controllers.length; i ++ ) {
  102. sortedInputSources[ i ] = findInputSource( i );
  103. }
  104. }
  105. function findInputSource( id ) {
  106. var inputSources = session.inputSources;
  107. for ( var i = 0; i < inputSources.length; i ++ ) {
  108. var inputSource = inputSources[ i ];
  109. var handedness = inputSource.handedness;
  110. if ( id === 0 && ( handedness === 'none' || handedness === 'right' ) ) return inputSource;
  111. if ( id === 1 && ( handedness === 'left' ) ) return inputSource;
  112. }
  113. }
  114. //
  115. var cameraLPos = new Vector3();
  116. var cameraRPos = new Vector3();
  117. /**
  118. * @author jsantell / https://www.jsantell.com/
  119. *
  120. * Assumes 2 cameras that are parallel and share an X-axis, and that
  121. * the cameras' projection and world matrices have already been set.
  122. * And that near and far planes are identical for both cameras.
  123. * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
  124. */
  125. function setProjectionFromUnion( camera, cameraL, cameraR ) {
  126. cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
  127. cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
  128. var ipd = cameraLPos.distanceTo( cameraRPos );
  129. var projL = cameraL.projectionMatrix.elements;
  130. var projR = cameraR.projectionMatrix.elements;
  131. // VR systems will have identical far and near planes, and
  132. // most likely identical top and bottom frustum extents.
  133. // Use the left camera for these values.
  134. var near = projL[ 14 ] / ( projL[ 10 ] - 1 );
  135. var far = projL[ 14 ] / ( projL[ 10 ] + 1 );
  136. var topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
  137. var bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
  138. var leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
  139. var rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
  140. var left = near * leftFov;
  141. var right = near * rightFov;
  142. // Calculate the new camera's position offset from the
  143. // left camera. xOffset should be roughly half `ipd`.
  144. var zOffset = ipd / ( - leftFov + rightFov );
  145. var xOffset = zOffset * - leftFov;
  146. // TODO: Better way to apply this offset?
  147. cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
  148. camera.translateX( xOffset );
  149. camera.translateZ( zOffset );
  150. camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
  151. camera.matrixWorldInverse.getInverse( camera.matrixWorld );
  152. // Find the union of the frustum values of the cameras and scale
  153. // the values so that the near plane's position does not change in world space,
  154. // although must now be relative to the new union camera.
  155. var near2 = near + zOffset;
  156. var far2 = far + zOffset;
  157. var left2 = left - xOffset;
  158. var right2 = right + ( ipd - xOffset );
  159. var top2 = topFov * far / far2 * near2;
  160. var bottom2 = bottomFov * far / far2 * near2;
  161. camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
  162. }
  163. function updateCamera( camera, parent ) {
  164. if ( parent === null ) {
  165. camera.matrixWorld.copy( camera.matrix );
  166. } else {
  167. camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
  168. }
  169. camera.matrixWorldInverse.getInverse( camera.matrixWorld );
  170. }
  171. this.getCamera = function ( camera ) {
  172. var parent = camera.parent;
  173. var cameras = cameraVR.cameras;
  174. updateCamera( cameraVR, parent );
  175. for ( var i = 0; i < cameras.length; i ++ ) {
  176. updateCamera( cameras[ i ], parent );
  177. }
  178. // update camera and its children
  179. camera.matrixWorld.copy( cameraVR.matrixWorld );
  180. var children = camera.children;
  181. for ( var i = 0, l = children.length; i < l; i ++ ) {
  182. children[ i ].updateMatrixWorld( true );
  183. }
  184. setProjectionFromUnion( cameraVR, cameraL, cameraR );
  185. return cameraVR;
  186. };
  187. this.isPresenting = isPresenting;
  188. // Animation Loop
  189. var onAnimationFrameCallback = null;
  190. function onAnimationFrame( time, frame ) {
  191. pose = frame.getViewerPose( referenceSpace );
  192. if ( pose !== null ) {
  193. var views = pose.views;
  194. var baseLayer = session.renderState.baseLayer;
  195. renderer.setFramebuffer( baseLayer.framebuffer );
  196. for ( var i = 0; i < views.length; i ++ ) {
  197. var view = views[ i ];
  198. var viewport = baseLayer.getViewport( view );
  199. var viewMatrix = view.transform.inverse.matrix;
  200. var camera = cameraVR.cameras[ i ];
  201. camera.matrix.fromArray( viewMatrix ).getInverse( camera.matrix );
  202. camera.projectionMatrix.fromArray( view.projectionMatrix );
  203. camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
  204. if ( i === 0 ) {
  205. cameraVR.matrix.copy( camera.matrix );
  206. }
  207. }
  208. }
  209. //
  210. for ( var i = 0; i < controllers.length; i ++ ) {
  211. var controller = controllers[ i ];
  212. var inputSource = sortedInputSources[ i ];
  213. if ( inputSource ) {
  214. var inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
  215. if ( inputPose !== null ) {
  216. controller.matrix.fromArray( inputPose.transform.matrix );
  217. controller.matrix.decompose( controller.position, controller.rotation, controller.scale );
  218. controller.visible = true;
  219. continue;
  220. }
  221. }
  222. controller.visible = false;
  223. }
  224. if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
  225. }
  226. var animation = new WebGLAnimation();
  227. animation.setAnimationLoop( onAnimationFrame );
  228. this.setAnimationLoop = function ( callback ) {
  229. onAnimationFrameCallback = callback;
  230. };
  231. this.dispose = function () {};
  232. }
  233. Object.assign( WebXRManager.prototype, EventDispatcher.prototype );
  234. export { WebXRManager };