InteractionManager.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {
  2. Matrix4,
  3. Vector3
  4. } from 'three';
  5. const _pixelToLocal = new Matrix4();
  6. const _mvp = new Matrix4();
  7. const _viewport = new Matrix4();
  8. const _size = new Vector3();
  9. /**
  10. * Manages interaction for 3D objects independently of the scene graph.
  11. *
  12. * For objects with an {@link HTMLTexture}, the manager computes CSS `matrix3d`
  13. * transforms each frame so the underlying HTML elements stay aligned with
  14. * their meshes. Because the elements are children of the canvas, the browser
  15. * dispatches pointer events to them natively.
  16. *
  17. * ```js
  18. * const interactions = new InteractionManager();
  19. * interactions.connect( renderer, camera );
  20. *
  21. * // Objects live anywhere in the scene graph
  22. * scene.add( mesh );
  23. *
  24. * // Register for interaction separately
  25. * interactions.add( mesh );
  26. *
  27. * // In the animation loop
  28. * interactions.update();
  29. * ```
  30. * @three_import import { InteractionManager } from 'three/addons/interaction/InteractionManager.js';
  31. */
  32. class InteractionManager {
  33. constructor() {
  34. /**
  35. * The registered interactive objects.
  36. *
  37. * @type {Array<Object3D>}
  38. */
  39. this.objects = [];
  40. /**
  41. * The canvas element.
  42. *
  43. * @type {?HTMLCanvasElement}
  44. * @default null
  45. */
  46. this.element = null;
  47. /**
  48. * The camera used for computing the element transforms.
  49. *
  50. * @type {?Camera}
  51. * @default null
  52. */
  53. this.camera = null;
  54. this._cachedCssW = - 1;
  55. this._cachedCssH = - 1;
  56. }
  57. /**
  58. * Adds one or more objects to the manager.
  59. *
  60. * @param {...Object3D} objects - The objects to add.
  61. * @return {this}
  62. */
  63. add( ...objects ) {
  64. for ( const object of objects ) {
  65. if ( this.objects.indexOf( object ) === - 1 ) {
  66. this.objects.push( object );
  67. }
  68. }
  69. return this;
  70. }
  71. /**
  72. * Removes one or more objects from the manager.
  73. *
  74. * @param {...Object3D} objects - The objects to remove.
  75. * @return {this}
  76. */
  77. remove( ...objects ) {
  78. for ( const object of objects ) {
  79. const index = this.objects.indexOf( object );
  80. if ( index !== - 1 ) {
  81. this.objects.splice( index, 1 );
  82. }
  83. }
  84. return this;
  85. }
  86. /**
  87. * Stores the renderer and camera needed for computing element transforms.
  88. *
  89. * @param {(WebGPURenderer|WebGLRenderer)} renderer - The renderer.
  90. * @param {Camera} camera - The camera.
  91. */
  92. connect( renderer, camera ) {
  93. this.camera = camera;
  94. this.element = renderer.domElement;
  95. }
  96. /**
  97. * Updates the element transforms for all registered objects.
  98. * Call this once per frame in the animation loop.
  99. */
  100. update() {
  101. const canvas = this.element;
  102. const camera = this.camera;
  103. if ( canvas === null || camera === null ) return;
  104. // Viewport: NDC (-1,1) to canvas CSS pixels, Y flipped.
  105. // Using CSS pixels (clientWidth/clientHeight) so the resulting matrix
  106. // can be applied directly as a CSS transform without DPR conversion.
  107. const cssW = canvas.clientWidth;
  108. const cssH = canvas.clientHeight;
  109. if ( cssW !== this._cachedCssW || cssH !== this._cachedCssH ) {
  110. _viewport.set(
  111. cssW / 2, 0, 0, cssW / 2,
  112. 0, - cssH / 2, 0, cssH / 2,
  113. 0, 0, 1, 0,
  114. 0, 0, 0, 1
  115. );
  116. this._cachedCssW = cssW;
  117. this._cachedCssH = cssH;
  118. }
  119. for ( const object of this.objects ) {
  120. const texture = object.material.map;
  121. if ( ! texture || ! texture.isHTMLTexture ) continue;
  122. const element = texture.image;
  123. if ( ! element ) continue;
  124. // Position at canvas origin so the CSS matrix3d maps correctly.
  125. element.style.position = 'absolute';
  126. element.style.left = '0';
  127. element.style.top = '0';
  128. element.style.transformOrigin = '0 0';
  129. const elemW = element.offsetWidth;
  130. const elemH = element.offsetHeight;
  131. // Get mesh dimensions from geometry bounding box
  132. const geometry = object.geometry;
  133. if ( ! geometry.boundingBox ) geometry.computeBoundingBox();
  134. geometry.boundingBox.getSize( _size );
  135. // Map element pixel coords (0,0)-(elemW,elemH) to mesh local coords.
  136. // Front face: top-left at (-sizeX/2, sizeY/2, maxZ), bottom-right at (sizeX/2, -sizeY/2, maxZ).
  137. _pixelToLocal.set(
  138. _size.x / elemW, 0, 0, - _size.x / 2,
  139. 0, - _size.y / elemH, 0, _size.y / 2,
  140. 0, 0, 1, geometry.boundingBox.max.z,
  141. 0, 0, 0, 1
  142. );
  143. // Model-View-Projection
  144. _mvp.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
  145. _mvp.multiply( object.matrixWorld );
  146. _mvp.multiply( _pixelToLocal );
  147. // Apply viewport
  148. _mvp.premultiply( _viewport );
  149. // The browser performs the perspective divide (by w) when applying the matrix3d.
  150. element.style.transform = 'matrix3d(' + _mvp.elements.join( ',' ) + ')';
  151. }
  152. }
  153. /**
  154. * Disconnects this manager, clearing the renderer and camera references.
  155. */
  156. disconnect() {
  157. this.camera = null;
  158. this.element = null;
  159. this._cachedCssW = - 1;
  160. this._cachedCssH = - 1;
  161. }
  162. }
  163. export { InteractionManager };
粤ICP备19079148号