WebVRManager.js 9.4 KB

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