InteractiveGroup.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import {
  2. Group,
  3. Raycaster,
  4. Vector2
  5. } from 'three';
  6. const _pointer = new Vector2();
  7. const _event = { type: '', data: _pointer };
  8. // The XR events that are mapped to "standard" pointer events.
  9. const _events = {
  10. 'move': 'mousemove',
  11. 'select': 'click',
  12. 'selectstart': 'mousedown',
  13. 'selectend': 'mouseup'
  14. };
  15. const _raycaster = new Raycaster();
  16. /**
  17. * This class can be used to group 3D objects in an interactive group.
  18. * The group itself can listen to Pointer, Mouse or XR controller events to
  19. * detect selections of descendant 3D objects. If a 3D object is selected,
  20. * the respective event is going to dispatched to it.
  21. *
  22. * ```js
  23. * const group = new InteractiveGroup();
  24. * group.listenToPointerEvents( renderer, camera );
  25. * group.listenToXRControllerEvents( controller1 );
  26. * group.listenToXRControllerEvents( controller2 );
  27. * scene.add( group );
  28. *
  29. * // now add objects that should be interactive
  30. * group.add( mesh1, mesh2, mesh3 );
  31. * ```
  32. * @augments Group
  33. */
  34. class InteractiveGroup extends Group {
  35. constructor() {
  36. super();
  37. /**
  38. * The internal raycaster.
  39. *
  40. * @type {Raycaster}
  41. */
  42. this.raycaster = new Raycaster();
  43. /**
  44. * The internal raycaster.
  45. *
  46. * @type {?HTMLDOMElement}
  47. * @default null
  48. */
  49. this.element = null;
  50. /**
  51. * The camera used for raycasting.
  52. *
  53. * @type {?Camera}
  54. * @default null
  55. */
  56. this.camera = null;
  57. /**
  58. * An array of XR controllers.
  59. *
  60. * @type {Array<Group>}
  61. */
  62. this.controllers = [];
  63. this._onPointerEvent = this.onPointerEvent.bind( this );
  64. this._onXRControllerEvent = this.onXRControllerEvent.bind( this );
  65. }
  66. onPointerEvent( event ) {
  67. event.stopPropagation();
  68. const rect = this.element.getBoundingClientRect();
  69. _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
  70. _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
  71. this.raycaster.setFromCamera( _pointer, this.camera );
  72. const intersects = this.raycaster.intersectObjects( this.children, false );
  73. if ( intersects.length > 0 ) {
  74. const intersection = intersects[ 0 ];
  75. const object = intersection.object;
  76. const uv = intersection.uv;
  77. _event.type = event.type;
  78. _event.data.set( uv.x, 1 - uv.y );
  79. object.dispatchEvent( _event );
  80. }
  81. }
  82. onXRControllerEvent( event ) {
  83. const controller = event.target;
  84. _raycaster.setFromXRController( controller );
  85. const intersections = _raycaster.intersectObjects( this.children, false );
  86. if ( intersections.length > 0 ) {
  87. const intersection = intersections[ 0 ];
  88. const object = intersection.object;
  89. const uv = intersection.uv;
  90. _event.type = _events[ event.type ];
  91. _event.data.set( uv.x, 1 - uv.y );
  92. object.dispatchEvent( _event );
  93. }
  94. }
  95. /**
  96. * Calling this method makes sure the interactive group listens to Pointer and Mouse events.
  97. * The target is the `domElement` of the given renderer. The camera is required for the internal
  98. * raycasting so 3D objects can be detected based on the events.
  99. *
  100. * @param {(WebGPURenderer|WebGLRenderer)} renderer - The renderer.
  101. * @param {Camera} camera - The camera.
  102. */
  103. listenToPointerEvents( renderer, camera ) {
  104. this.camera = camera;
  105. this.element = renderer.domElement;
  106. this.element.addEventListener( 'pointerdown', this._onPointerEvent );
  107. this.element.addEventListener( 'pointerup', this._onPointerEvent );
  108. this.element.addEventListener( 'pointermove', this._onPointerEvent );
  109. this.element.addEventListener( 'mousedown', this._onPointerEvent );
  110. this.element.addEventListener( 'mouseup', this._onPointerEvent );
  111. this.element.addEventListener( 'mousemove', this._onPointerEvent );
  112. this.element.addEventListener( 'click', this._onPointerEvent );
  113. }
  114. /**
  115. * Disconnects this interactive group from all Pointer and Mouse Events.
  116. */
  117. disconnectionPointerEvents() {
  118. if ( this.element !== null ) {
  119. this.element.removeEventListener( 'pointerdown', this._onPointerEvent );
  120. this.element.removeEventListener( 'pointerup', this._onPointerEvent );
  121. this.element.removeEventListener( 'pointermove', this._onPointerEvent );
  122. this.element.removeEventListener( 'mousedown', this._onPointerEvent );
  123. this.element.removeEventListener( 'mouseup', this._onPointerEvent );
  124. this.element.removeEventListener( 'mousemove', this._onPointerEvent );
  125. this.element.removeEventListener( 'click', this._onPointerEvent );
  126. }
  127. }
  128. /**
  129. * Calling this method makes sure the interactive group listens to events of
  130. * the given XR controller.
  131. *
  132. * @param {Group} controller - The XR controller.
  133. */
  134. listenToXRControllerEvents( controller ) {
  135. this.controllers.push( controller );
  136. controller.addEventListener( 'move', this._onXRControllerEvent );
  137. controller.addEventListener( 'select', this._onXRControllerEvent );
  138. controller.addEventListener( 'selectstart', this._onXRControllerEvent );
  139. controller.addEventListener( 'selectend', this._onXRControllerEvent );
  140. }
  141. /**
  142. * Disconnects this interactive group from all XR controllers.
  143. */
  144. disconnectXrControllerEvents() {
  145. for ( const controller of this.controllers ) {
  146. controller.removeEventListener( 'move', this._onXRControllerEvent );
  147. controller.removeEventListener( 'select', this._onXRControllerEvent );
  148. controller.removeEventListener( 'selectstart', this._onXRControllerEvent );
  149. controller.removeEventListener( 'selectend', this._onXRControllerEvent );
  150. }
  151. }
  152. /**
  153. * Disconnects this interactive group from the DOM and all XR controllers.
  154. */
  155. disconnect() {
  156. this.disconnectionPointerEvents();
  157. this.disconnectXrControllerEvents();
  158. this.camera = null;
  159. this.element = null;
  160. this.controllers = [];
  161. }
  162. }
  163. export { InteractiveGroup };
粤ICP备19079148号