| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- import {
- Matrix4,
- Vector3
- } from 'three';
- const _pixelToLocal = new Matrix4();
- const _mvp = new Matrix4();
- const _viewport = new Matrix4();
- const _size = new Vector3();
- /**
- * Manages interaction for 3D objects independently of the scene graph.
- *
- * For objects with an {@link HTMLTexture}, the manager computes CSS `matrix3d`
- * transforms each frame so the underlying HTML elements stay aligned with
- * their meshes. Because the elements are children of the canvas, the browser
- * dispatches pointer events to them natively.
- *
- * ```js
- * const interactions = new InteractionManager();
- * interactions.connect( renderer, camera );
- *
- * // Objects live anywhere in the scene graph
- * scene.add( mesh );
- *
- * // Register for interaction separately
- * interactions.add( mesh );
- *
- * // In the animation loop
- * interactions.update();
- * ```
- * @three_import import { InteractionManager } from 'three/addons/interaction/InteractionManager.js';
- */
- class InteractionManager {
- constructor() {
- /**
- * The registered interactive objects.
- *
- * @type {Array<Object3D>}
- */
- this.objects = [];
- /**
- * The canvas element.
- *
- * @type {?HTMLCanvasElement}
- * @default null
- */
- this.element = null;
- /**
- * The camera used for computing the element transforms.
- *
- * @type {?Camera}
- * @default null
- */
- this.camera = null;
- this._cachedCssW = - 1;
- this._cachedCssH = - 1;
- }
- /**
- * Adds one or more objects to the manager.
- *
- * @param {...Object3D} objects - The objects to add.
- * @return {this}
- */
- add( ...objects ) {
- for ( const object of objects ) {
- if ( this.objects.indexOf( object ) === - 1 ) {
- this.objects.push( object );
- }
- }
- return this;
- }
- /**
- * Removes one or more objects from the manager.
- *
- * @param {...Object3D} objects - The objects to remove.
- * @return {this}
- */
- remove( ...objects ) {
- for ( const object of objects ) {
- const index = this.objects.indexOf( object );
- if ( index !== - 1 ) {
- this.objects.splice( index, 1 );
- }
- }
- return this;
- }
- /**
- * Stores the renderer and camera needed for computing element transforms.
- *
- * @param {(WebGPURenderer|WebGLRenderer)} renderer - The renderer.
- * @param {Camera} camera - The camera.
- */
- connect( renderer, camera ) {
- this.camera = camera;
- this.element = renderer.domElement;
- }
- /**
- * Updates the element transforms for all registered objects.
- * Call this once per frame in the animation loop.
- */
- update() {
- const canvas = this.element;
- const camera = this.camera;
- if ( canvas === null || camera === null ) return;
- // Viewport: NDC (-1,1) to canvas CSS pixels, Y flipped.
- // Using CSS pixels (clientWidth/clientHeight) so the resulting matrix
- // can be applied directly as a CSS transform without DPR conversion.
- const cssW = canvas.clientWidth;
- const cssH = canvas.clientHeight;
- if ( cssW !== this._cachedCssW || cssH !== this._cachedCssH ) {
- _viewport.set(
- cssW / 2, 0, 0, cssW / 2,
- 0, - cssH / 2, 0, cssH / 2,
- 0, 0, 1, 0,
- 0, 0, 0, 1
- );
- this._cachedCssW = cssW;
- this._cachedCssH = cssH;
- }
- for ( const object of this.objects ) {
- const texture = object.material.map;
- if ( ! texture || ! texture.isHTMLTexture ) continue;
- const element = texture.image;
- if ( ! element ) continue;
- // Position at canvas origin so the CSS matrix3d maps correctly.
- element.style.position = 'absolute';
- element.style.left = '0';
- element.style.top = '0';
- element.style.transformOrigin = '0 0';
- const elemW = element.offsetWidth;
- const elemH = element.offsetHeight;
- // Get mesh dimensions from geometry bounding box
- const geometry = object.geometry;
- if ( ! geometry.boundingBox ) geometry.computeBoundingBox();
- geometry.boundingBox.getSize( _size );
- // Map element pixel coords (0,0)-(elemW,elemH) to mesh local coords.
- // Front face: top-left at (-sizeX/2, sizeY/2, maxZ), bottom-right at (sizeX/2, -sizeY/2, maxZ).
- _pixelToLocal.set(
- _size.x / elemW, 0, 0, - _size.x / 2,
- 0, - _size.y / elemH, 0, _size.y / 2,
- 0, 0, 1, geometry.boundingBox.max.z,
- 0, 0, 0, 1
- );
- // Model-View-Projection
- _mvp.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
- _mvp.multiply( object.matrixWorld );
- _mvp.multiply( _pixelToLocal );
- // Apply viewport
- _mvp.premultiply( _viewport );
- // The browser performs the perspective divide (by w) when applying the matrix3d.
- element.style.transform = 'matrix3d(' + _mvp.elements.join( ',' ) + ')';
- }
- }
- /**
- * Disconnects this manager, clearing the renderer and camera references.
- */
- disconnect() {
- this.camera = null;
- this.element = null;
- this._cachedCssW = - 1;
- this._cachedCssH = - 1;
- }
- }
- export { InteractionManager };
|