diff --git a/examples/js/controls/TrackballControls.js b/examples/js/controls/TrackballControls.js index 9330c35d52215f..3cd37f2f76630c 100644 --- a/examples/js/controls/TrackballControls.js +++ b/examples/js/controls/TrackballControls.js @@ -27,7 +27,9 @@ TOUCH_ZOOM_PAN: 4 }; this.object = object; - this.domElement = domElement; // API + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + // API this.enabled = true; this.screen = { @@ -76,7 +78,9 @@ _zoomStart = new THREE.Vector2(), _zoomEnd = new THREE.Vector2(), _panStart = new THREE.Vector2(), - _panEnd = new THREE.Vector2(); // for reset + _panEnd = new THREE.Vector2(), + _pointers = [], + _pointerPositions = {}; // for reset this.target0 = this.target.clone(); @@ -383,13 +387,23 @@ if ( scope.enabled === false ) return; - switch ( event.pointerType ) { + if ( _pointers.length === 0 ) { - case 'mouse': - case 'pen': - onMouseDown( event ); - break; - // TODO touch + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + } // + + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); } @@ -399,13 +413,13 @@ if ( scope.enabled === false ) return; - switch ( event.pointerType ) { + if ( event.pointerType === 'touch' ) { - case 'mouse': - case 'pen': - onMouseMove( event ); - break; - // TODO touch + onTouchMove( event ); + + } else { + + onMouseMove( event ); } @@ -413,20 +427,36 @@ function onPointerUp( event ) { + removePointer( event ); + + if ( _pointers.length === 0 ) { + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + } // + + if ( scope.enabled === false ) return; - switch ( event.pointerType ) { + if ( event.pointerType === 'touch' ) { - case 'mouse': - case 'pen': - onMouseUp(); - break; - // TODO touch + onTouchEnd(); + + } else { + + onMouseUp( event ); } } + function onPointerCancel( event ) { + + removePointer( event ); + + } + function keydown( event ) { if ( scope.enabled === false ) return; @@ -515,7 +545,6 @@ function onMouseMove( event ) { - if ( scope.enabled === false ) return; const state = _keyState !== STATE.NONE ? _keyState : _state; if ( state === STATE.ROTATE && ! scope.noRotate ) { @@ -538,7 +567,6 @@ function onMouseUp() { - if ( scope.enabled === false ) return; _state = STATE.NONE; scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); @@ -546,7 +574,7 @@ } - function mousewheel( event ) { + function onMouseWheel( event ) { if ( scope.enabled === false ) return; if ( scope.noZoom === true ) return; @@ -576,17 +604,16 @@ } - function touchstart( event ) { + function onTouchStart( event ) { - if ( scope.enabled === false ) return; - event.preventDefault(); + trackPointer( event ); - switch ( event.touches.length ) { + switch ( _pointers.length ) { case 1: _state = STATE.TOUCH_ROTATE; - _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) ); _movePrev.copy( _moveCurr ); @@ -595,11 +622,11 @@ default: // 2 or more _state = STATE.TOUCH_ZOOM_PAN; - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const dx = _pointers[ 0 ].pageX + _pointers[ 1 ].pageX; + const dy = _pointers[ 0 ].pageY + _pointers[ 1 ].pageY; _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); - const x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; - const y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; + const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2; + const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2; _panStart.copy( getMouseOnScreen( x, y ) ); @@ -613,27 +640,27 @@ } - function touchmove( event ) { + function onTouchMove( event ) { - if ( scope.enabled === false ) return; - event.preventDefault(); + trackPointer( event ); - switch ( event.touches.length ) { + switch ( _pointers.length ) { case 1: _movePrev.copy( _moveCurr ); - _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); break; default: // 2 or more - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const position = getSecondPointerPosition( event ); + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); - const x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; - const y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; + const x = ( event.pageX + position.x ) / 2; + const y = ( event.pageY + position.y ) / 2; _panEnd.copy( getMouseOnScreen( x, y ) ); @@ -643,11 +670,9 @@ } - function touchend( event ) { - - if ( scope.enabled === false ) return; + function onTouchEnd( event ) { - switch ( event.touches.length ) { + switch ( _pointers.length ) { case 0: _state = STATE.NONE; @@ -656,7 +681,7 @@ case 1: _state = STATE.TOUCH_ROTATE; - _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); _movePrev.copy( _moveCurr ); @@ -675,16 +700,58 @@ } + function addPointer( event ) { + + _pointers.push( event ); + + } + + function removePointer( event ) { + + delete _pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < _pointers.length; i ++ ) { + + if ( _pointers[ i ].pointerId == event.pointerId ) { + + _pointers.splice( i, 1 ); + + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = _pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new THREE.Vector2(); + _pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = event.pointerId === _pointers[ 0 ].pointerId ? _pointers[ 1 ] : _pointers[ 0 ]; + return _pointerPositions[ pointer.pointerId ]; + + } + this.dispose = function () { scope.domElement.removeEventListener( 'contextmenu', contextmenu ); scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'wheel', mousewheel ); - scope.domElement.removeEventListener( 'touchstart', touchstart ); - scope.domElement.removeEventListener( 'touchend', touchend ); - scope.domElement.removeEventListener( 'touchmove', touchmove ); - scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); window.removeEventListener( 'keydown', keydown ); window.removeEventListener( 'keyup', keyup ); @@ -692,18 +759,10 @@ this.domElement.addEventListener( 'contextmenu', contextmenu ); this.domElement.addEventListener( 'pointerdown', onPointerDown ); - this.domElement.addEventListener( 'wheel', mousewheel, { - passive: false - } ); - this.domElement.addEventListener( 'touchstart', touchstart, { - passive: false - } ); - this.domElement.addEventListener( 'touchend', touchend ); - this.domElement.addEventListener( 'touchmove', touchmove, { + this.domElement.addEventListener( 'pointercancel', onPointerCancel ); + this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); - this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); window.addEventListener( 'keydown', keydown ); window.addEventListener( 'keyup', keyup ); this.handleResize(); // force an update at start diff --git a/examples/jsm/controls/TrackballControls.js b/examples/jsm/controls/TrackballControls.js index 7a94675c36a0f1..ca1979cacfd761 100644 --- a/examples/jsm/controls/TrackballControls.js +++ b/examples/jsm/controls/TrackballControls.js @@ -24,6 +24,7 @@ class TrackballControls extends EventDispatcher { this.object = object; this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll // API @@ -77,7 +78,10 @@ class TrackballControls extends EventDispatcher { _zoomEnd = new Vector2(), _panStart = new Vector2(), - _panEnd = new Vector2(); + _panEnd = new Vector2(), + + _pointers = [], + _pointerPositions = {}; // for reset @@ -406,14 +410,24 @@ class TrackballControls extends EventDispatcher { if ( scope.enabled === false ) return; - switch ( event.pointerType ) { + if ( _pointers.length === 0 ) { - case 'mouse': - case 'pen': - onMouseDown( event ); - break; + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { - // TODO touch + onMouseDown( event ); } @@ -423,14 +437,13 @@ class TrackballControls extends EventDispatcher { if ( scope.enabled === false ) return; - switch ( event.pointerType ) { + if ( event.pointerType === 'touch' ) { - case 'mouse': - case 'pen': - onMouseMove( event ); - break; + onTouchMove( event ); + + } else { - // TODO touch + onMouseMove( event ); } @@ -438,21 +451,37 @@ class TrackballControls extends EventDispatcher { function onPointerUp( event ) { + removePointer( event ); + + if ( _pointers.length === 0 ) { + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + } + + // + if ( scope.enabled === false ) return; - switch ( event.pointerType ) { + if ( event.pointerType === 'touch' ) { - case 'mouse': - case 'pen': - onMouseUp(); - break; + onTouchEnd(); + + } else { - // TODO touch + onMouseUp( event ); } } + function onPointerCancel( event ) { + + removePointer( event ); + + } + function keydown( event ) { if ( scope.enabled === false ) return; @@ -542,8 +571,6 @@ class TrackballControls extends EventDispatcher { function onMouseMove( event ) { - if ( scope.enabled === false ) return; - const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; if ( state === STATE.ROTATE && ! scope.noRotate ) { @@ -565,8 +592,6 @@ class TrackballControls extends EventDispatcher { function onMouseUp() { - if ( scope.enabled === false ) return; - _state = STATE.NONE; scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); @@ -576,7 +601,7 @@ class TrackballControls extends EventDispatcher { } - function mousewheel( event ) { + function onMouseWheel( event ) { if ( scope.enabled === false ) return; @@ -608,28 +633,26 @@ class TrackballControls extends EventDispatcher { } - function touchstart( event ) { + function onTouchStart( event ) { - if ( scope.enabled === false ) return; - - event.preventDefault(); + trackPointer( event ); - switch ( event.touches.length ) { + switch ( _pointers.length ) { case 1: _state = STATE.TOUCH_ROTATE; - _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) ); _movePrev.copy( _moveCurr ); break; default: // 2 or more _state = STATE.TOUCH_ZOOM_PAN; - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + const dx = _pointers[ 0 ].pageX + _pointers[ 1 ].pageX; + const dy = _pointers[ 0 ].pageY + _pointers[ 1 ].pageY; _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); - const x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; - const y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; + const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2; + const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2; _panStart.copy( getMouseOnScreen( x, y ) ); _panEnd.copy( _panStart ); break; @@ -640,26 +663,27 @@ class TrackballControls extends EventDispatcher { } - function touchmove( event ) { + function onTouchMove( event ) { - if ( scope.enabled === false ) return; - - event.preventDefault(); + trackPointer( event ); - switch ( event.touches.length ) { + switch ( _pointers.length ) { case 1: _movePrev.copy( _moveCurr ); - _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); break; default: // 2 or more - const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; - const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); - const x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; - const y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; + const x = ( event.pageX + position.x ) / 2; + const y = ( event.pageY + position.y ) / 2; _panEnd.copy( getMouseOnScreen( x, y ) ); break; @@ -667,11 +691,9 @@ class TrackballControls extends EventDispatcher { } - function touchend( event ) { - - if ( scope.enabled === false ) return; + function onTouchEnd( event ) { - switch ( event.touches.length ) { + switch ( _pointers.length ) { case 0: _state = STATE.NONE; @@ -679,7 +701,7 @@ class TrackballControls extends EventDispatcher { case 1: _state = STATE.TOUCH_ROTATE; - _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); _movePrev.copy( _moveCurr ); break; @@ -697,19 +719,59 @@ class TrackballControls extends EventDispatcher { } + function addPointer( event ) { + + _pointers.push( event ); + + } + + function removePointer( event ) { + + delete _pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < _pointers.length; i ++ ) { + + if ( _pointers[ i ].pointerId == event.pointerId ) { + + _pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = _pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + _pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === _pointers[ 0 ].pointerId ) ? _pointers[ 1 ] : _pointers[ 0 ]; + + return _pointerPositions[ pointer.pointerId ]; + + } + this.dispose = function () { scope.domElement.removeEventListener( 'contextmenu', contextmenu ); scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'wheel', mousewheel ); - - scope.domElement.removeEventListener( 'touchstart', touchstart ); - scope.domElement.removeEventListener( 'touchend', touchend ); - scope.domElement.removeEventListener( 'touchmove', touchmove ); - - scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); window.removeEventListener( 'keydown', keydown ); window.removeEventListener( 'keyup', keyup ); @@ -719,14 +781,9 @@ class TrackballControls extends EventDispatcher { this.domElement.addEventListener( 'contextmenu', contextmenu ); this.domElement.addEventListener( 'pointerdown', onPointerDown ); - this.domElement.addEventListener( 'wheel', mousewheel, { passive: false } ); - - this.domElement.addEventListener( 'touchstart', touchstart, { passive: false } ); - this.domElement.addEventListener( 'touchend', touchend ); - this.domElement.addEventListener( 'touchmove', touchmove, { passive: false } ); + this.domElement.addEventListener( 'pointercancel', onPointerCancel ); + this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); - this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); - this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); window.addEventListener( 'keydown', keydown ); window.addEventListener( 'keyup', keyup );