@@ -96,6 +96,11 @@ class OrbitControls extends EventDispatcher {
9696
9797 this . object = object
9898 this . domElement = domElement
99+ // disables touch scroll
100+ // touch-action needs to be defined for pointer events to work on mobile
101+ // https://stackoverflow.com/a/48254578
102+ this . domElement && ( this . domElement . style . touchAction = 'none' )
103+
99104 // for reset
100105 this . target0 = this . target . clone ( )
101106 this . position0 = this . object . position . clone ( )
@@ -301,19 +306,15 @@ class OrbitControls extends EventDispatcher {
301306 scope . domElement = domElement
302307 scope . domElement . addEventListener ( 'contextmenu' , onContextMenu )
303308 scope . domElement . addEventListener ( 'pointerdown' , onPointerDown )
309+ scope . domElement . addEventListener ( 'pointercancel' , onPointerCancel )
304310 scope . domElement . addEventListener ( 'wheel' , onMouseWheel )
305- scope . domElement . addEventListener ( 'touchstart' , onTouchStart )
306- scope . domElement . addEventListener ( 'touchend' , onTouchEnd )
307- scope . domElement . addEventListener ( 'touchmove' , onTouchMove )
308311 }
309312
310313 this . dispose = ( ) : void => {
311314 scope . domElement ?. removeEventListener ( 'contextmenu' , onContextMenu )
312315 scope . domElement ?. removeEventListener ( 'pointerdown' , onPointerDown )
316+ scope . domElement ?. removeEventListener ( 'pointercancel' , onPointerCancel )
313317 scope . domElement ?. removeEventListener ( 'wheel' , onMouseWheel )
314- scope . domElement ?. removeEventListener ( 'touchstart' , onTouchStart )
315- scope . domElement ?. removeEventListener ( 'touchend' , onTouchEnd )
316- scope . domElement ?. removeEventListener ( 'touchmove' , onTouchMove )
317318 scope . domElement ?. ownerDocument . removeEventListener ( 'pointermove' , onPointerMove )
318319 scope . domElement ?. ownerDocument . removeEventListener ( 'pointerup' , onPointerUp )
319320 if ( scope . _domElementKeyEvents !== null ) {
@@ -367,6 +368,9 @@ class OrbitControls extends EventDispatcher {
367368 const dollyEnd = new Vector2 ( )
368369 const dollyDelta = new Vector2 ( )
369370
371+ const pointers : PointerEvent [ ] = [ ]
372+ const pointerPositions : { [ key : string ] : Vector2 } = { }
373+
370374 function getAutoRotationAngle ( ) : number {
371375 return ( ( 2 * Math . PI ) / 60 / 60 ) * scope . autoRotateSpeed
372376 }
@@ -572,53 +576,53 @@ class OrbitControls extends EventDispatcher {
572576 }
573577 }
574578
575- function handleTouchStartRotate ( event : TouchEvent ) {
576- if ( event . touches . length == 1 ) {
577- rotateStart . set ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY )
579+ function handleTouchStartRotate ( ) {
580+ if ( pointers . length == 1 ) {
581+ rotateStart . set ( pointers [ 0 ] . pageX , pointers [ 0 ] . pageY )
578582 } else {
579- const x = 0.5 * ( event . touches [ 0 ] . pageX + event . touches [ 1 ] . pageX )
580- const y = 0.5 * ( event . touches [ 0 ] . pageY + event . touches [ 1 ] . pageY )
583+ const x = 0.5 * ( pointers [ 0 ] . pageX + pointers [ 1 ] . pageX )
584+ const y = 0.5 * ( pointers [ 0 ] . pageY + pointers [ 1 ] . pageY )
581585
582586 rotateStart . set ( x , y )
583587 }
584588 }
585589
586- function handleTouchStartPan ( event : TouchEvent ) {
587- if ( event . touches . length == 1 ) {
588- panStart . set ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY )
590+ function handleTouchStartPan ( ) {
591+ if ( pointers . length == 1 ) {
592+ panStart . set ( pointers [ 0 ] . pageX , pointers [ 0 ] . pageY )
589593 } else {
590- const x = 0.5 * ( event . touches [ 0 ] . pageX + event . touches [ 1 ] . pageX )
591- const y = 0.5 * ( event . touches [ 0 ] . pageY + event . touches [ 1 ] . pageY )
594+ const x = 0.5 * ( pointers [ 0 ] . pageX + pointers [ 1 ] . pageX )
595+ const y = 0.5 * ( pointers [ 0 ] . pageY + pointers [ 1 ] . pageY )
592596
593597 panStart . set ( x , y )
594598 }
595599 }
596600
597- function handleTouchStartDolly ( event : TouchEvent ) {
598- const dx = event . touches [ 0 ] . pageX - event . touches [ 1 ] . pageX
599- const dy = event . touches [ 0 ] . pageY - event . touches [ 1 ] . pageY
601+ function handleTouchStartDolly ( ) {
602+ const dx = pointers [ 0 ] . pageX - pointers [ 1 ] . pageX
603+ const dy = pointers [ 0 ] . pageY - pointers [ 1 ] . pageY
600604 const distance = Math . sqrt ( dx * dx + dy * dy )
601605
602606 dollyStart . set ( 0 , distance )
603607 }
604608
605- function handleTouchStartDollyPan ( event : TouchEvent ) {
606- if ( scope . enableZoom ) handleTouchStartDolly ( event )
607- if ( scope . enablePan ) handleTouchStartPan ( event )
609+ function handleTouchStartDollyPan ( ) {
610+ if ( scope . enableZoom ) handleTouchStartDolly ( )
611+ if ( scope . enablePan ) handleTouchStartPan ( )
608612 }
609613
610- function handleTouchStartDollyRotate ( event : TouchEvent ) {
611- if ( scope . enableZoom ) handleTouchStartDolly ( event )
612- if ( scope . enableRotate ) handleTouchStartRotate ( event )
614+ function handleTouchStartDollyRotate ( ) {
615+ if ( scope . enableZoom ) handleTouchStartDolly ( )
616+ if ( scope . enableRotate ) handleTouchStartRotate ( )
613617 }
614618
615- function handleTouchMoveRotate ( event : TouchEvent ) {
616- if ( event . touches . length == 1 ) {
617- rotateEnd . set ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY )
619+ function handleTouchMoveRotate ( event : PointerEvent ) {
620+ if ( pointers . length == 1 ) {
621+ rotateEnd . set ( event . pageX , event . pageY )
618622 } else {
619- const x = 0.5 * ( event . touches [ 0 ] . pageX + event . touches [ 1 ] . pageX )
620- const y = 0.5 * ( event . touches [ 0 ] . pageY + event . touches [ 1 ] . pageY )
621-
623+ const position = getSecondPointerPosition ( event )
624+ const x = 0.5 * ( event . pageX + position . x )
625+ const y = 0.5 * ( event . pageY + position . y )
622626 rotateEnd . set ( x , y )
623627 }
624628
@@ -633,13 +637,13 @@ class OrbitControls extends EventDispatcher {
633637 rotateStart . copy ( rotateEnd )
634638 }
635639
636- function handleTouchMovePan ( event : TouchEvent ) {
637- if ( event . touches . length == 1 ) {
638- panEnd . set ( event . touches [ 0 ] . pageX , event . touches [ 0 ] . pageY )
640+ function handleTouchMovePan ( event : PointerEvent ) {
641+ if ( pointers . length == 1 ) {
642+ panEnd . set ( event . pageX , event . pageY )
639643 } else {
640- const x = 0.5 * ( event . touches [ 0 ] . pageX + event . touches [ 1 ] . pageX )
641- const y = 0.5 * ( event . touches [ 0 ] . pageY + event . touches [ 1 ] . pageY )
642-
644+ const position = getSecondPointerPosition ( event )
645+ const x = 0.5 * ( event . pageX + position . x )
646+ const y = 0.5 * ( event . pageY + position . y )
643647 panEnd . set ( x , y )
644648 }
645649
@@ -648,9 +652,10 @@ class OrbitControls extends EventDispatcher {
648652 panStart . copy ( panEnd )
649653 }
650654
651- function handleTouchMoveDolly ( event : TouchEvent ) {
652- const dx = event . touches [ 0 ] . pageX - event . touches [ 1 ] . pageX
653- const dy = event . touches [ 0 ] . pageY - event . touches [ 1 ] . pageY
655+ function handleTouchMoveDolly ( event : PointerEvent ) {
656+ const position = getSecondPointerPosition ( event )
657+ const dx = event . pageX - position . x
658+ const dy = event . pageY - position . y
654659 const distance = Math . sqrt ( dx * dx + dy * dy )
655660
656661 dollyEnd . set ( 0 , distance )
@@ -659,12 +664,12 @@ class OrbitControls extends EventDispatcher {
659664 dollyStart . copy ( dollyEnd )
660665 }
661666
662- function handleTouchMoveDollyPan ( event : TouchEvent ) {
667+ function handleTouchMoveDollyPan ( event : PointerEvent ) {
663668 if ( scope . enableZoom ) handleTouchMoveDolly ( event )
664669 if ( scope . enablePan ) handleTouchMovePan ( event )
665670 }
666671
667- function handleTouchMoveDollyRotate ( event : TouchEvent ) {
672+ function handleTouchMoveDollyRotate ( event : PointerEvent ) {
668673 if ( scope . enableZoom ) handleTouchMoveDolly ( event )
669674 if ( scope . enableRotate ) handleTouchMoveRotate ( event )
670675 }
@@ -680,40 +685,51 @@ class OrbitControls extends EventDispatcher {
680685 function onPointerDown ( event : PointerEvent ) {
681686 if ( scope . enabled === false ) return
682687
683- switch ( event . pointerType ) {
684- case 'mouse' :
685- case 'pen' :
686- onMouseDown ( event )
687- break
688+ if ( pointers . length === 0 ) {
689+ scope . domElement ?. ownerDocument . addEventListener ( 'pointermove' , onPointerMove )
690+ scope . domElement ?. ownerDocument . addEventListener ( 'pointerup' , onPointerUp )
691+ }
692+
693+ addPointer ( event )
688694
689- // TODO touch
695+ if ( event . pointerType === 'touch' ) {
696+ onTouchStart ( event )
697+ } else {
698+ onMouseDown ( event )
690699 }
691700 }
692701
693702 function onPointerMove ( event : PointerEvent ) {
694703 if ( scope . enabled === false ) return
695704
696- switch ( event . pointerType ) {
697- case 'mouse' :
698- case 'pen' :
699- onMouseMove ( event )
700- break
701-
702- // TODO touch
705+ if ( event . pointerType === 'touch' ) {
706+ onTouchMove ( event )
707+ } else {
708+ onMouseMove ( event )
703709 }
704710 }
705711
706712 function onPointerUp ( event : PointerEvent ) {
707- switch ( event . pointerType ) {
708- case 'mouse' :
709- case 'pen' :
710- onMouseUp ( )
711- break
713+ if ( scope . enabled === false ) return
714+
715+ if ( event . pointerType === 'touch' ) {
716+ onTouchEnd ( )
717+ } else {
718+ onMouseUp ( )
719+ }
712720
713- // TODO touch
721+ removePointer ( event )
722+
723+ if ( pointers . length === 0 ) {
724+ scope . domElement ?. ownerDocument . removeEventListener ( 'pointermove' , onPointerMove )
725+ scope . domElement ?. ownerDocument . removeEventListener ( 'pointerup' , onPointerUp )
714726 }
715727 }
716728
729+ function onPointerCancel ( event : PointerEvent ) {
730+ removePointer ( event )
731+ }
732+
717733 function onMouseDown ( event : MouseEvent ) {
718734 let mouseAction
719735
@@ -770,8 +786,6 @@ class OrbitControls extends EventDispatcher {
770786 }
771787
772788 if ( state !== STATE . NONE ) {
773- scope . domElement ?. ownerDocument . addEventListener ( 'pointermove' , onPointerMove )
774- scope . domElement ?. ownerDocument . addEventListener ( 'pointerup' , onPointerUp )
775789 scope . dispatchEvent ( startEvent )
776790 }
777791 }
@@ -798,10 +812,6 @@ class OrbitControls extends EventDispatcher {
798812 }
799813
800814 function onMouseUp ( ) {
801- scope . domElement ?. ownerDocument . removeEventListener ( 'pointermove' , onPointerMove )
802- scope . domElement ?. ownerDocument . removeEventListener ( 'pointerup' , onPointerUp )
803-
804- if ( scope . enabled === false ) return
805815 handleMouseUp ( )
806816 scope . dispatchEvent ( endEvent )
807817 state = STATE . NONE
@@ -826,23 +836,21 @@ class OrbitControls extends EventDispatcher {
826836 handleKeyDown ( event )
827837 }
828838
829- function onTouchStart ( event : TouchEvent ) {
830- if ( scope . enabled === false ) return
831-
832- event . preventDefault ( ) // prevent scrolling
839+ function onTouchStart ( event : PointerEvent ) {
840+ trackPointer ( event )
833841
834- switch ( event . touches . length ) {
842+ switch ( pointers . length ) {
835843 case 1 :
836844 switch ( scope . touches . ONE ) {
837845 case TOUCH . ROTATE :
838846 if ( scope . enableRotate === false ) return
839- handleTouchStartRotate ( event )
847+ handleTouchStartRotate ( )
840848 state = STATE . TOUCH_ROTATE
841849 break
842850
843851 case TOUCH . PAN :
844852 if ( scope . enablePan === false ) return
845- handleTouchStartPan ( event )
853+ handleTouchStartPan ( )
846854 state = STATE . TOUCH_PAN
847855 break
848856
@@ -856,13 +864,13 @@ class OrbitControls extends EventDispatcher {
856864 switch ( scope . touches . TWO ) {
857865 case TOUCH . DOLLY_PAN :
858866 if ( scope . enableZoom === false && scope . enablePan === false ) return
859- handleTouchStartDollyPan ( event )
867+ handleTouchStartDollyPan ( )
860868 state = STATE . TOUCH_DOLLY_PAN
861869 break
862870
863871 case TOUCH . DOLLY_ROTATE :
864872 if ( scope . enableZoom === false && scope . enableRotate === false ) return
865- handleTouchStartDollyRotate ( event )
873+ handleTouchStartDollyRotate ( )
866874 state = STATE . TOUCH_DOLLY_ROTATE
867875 break
868876
@@ -881,10 +889,8 @@ class OrbitControls extends EventDispatcher {
881889 }
882890 }
883891
884- function onTouchMove ( event : TouchEvent ) {
885- if ( scope . enabled === false ) return
886-
887- event . preventDefault ( ) // prevent scrolling
892+ function onTouchMove ( event : PointerEvent ) {
893+ trackPointer ( event )
888894
889895 switch ( state ) {
890896 case STATE . TOUCH_ROTATE :
@@ -917,8 +923,6 @@ class OrbitControls extends EventDispatcher {
917923 }
918924
919925 function onTouchEnd ( ) {
920- if ( scope . enabled === false ) return
921-
922926 handleTouchEnd ( )
923927 scope . dispatchEvent ( endEvent )
924928 state = STATE . NONE
@@ -929,6 +933,37 @@ class OrbitControls extends EventDispatcher {
929933 event . preventDefault ( )
930934 }
931935
936+ function addPointer ( event : PointerEvent ) {
937+ pointers . push ( event )
938+ }
939+
940+ function removePointer ( event : PointerEvent ) {
941+ delete pointerPositions [ event . pointerId ]
942+
943+ for ( let i = 0 ; i < pointers . length ; i ++ ) {
944+ if ( pointers [ i ] . pointerId == event . pointerId ) {
945+ pointers . splice ( i , 1 )
946+ return
947+ }
948+ }
949+ }
950+
951+ function trackPointer ( event : PointerEvent ) {
952+ let position = pointerPositions [ event . pointerId ]
953+
954+ if ( position === undefined ) {
955+ position = new Vector2 ( )
956+ pointerPositions [ event . pointerId ] = position
957+ }
958+
959+ position . set ( event . pageX , event . pageY )
960+ }
961+
962+ function getSecondPointerPosition ( event : PointerEvent ) {
963+ const pointer = event . pointerId === pointers [ 0 ] . pointerId ? pointers [ 1 ] : pointers [ 0 ]
964+ return pointerPositions [ pointer . pointerId ]
965+ }
966+
932967 // connect events
933968 if ( domElement !== undefined ) this . connect ( domElement )
934969 // force an update at start
0 commit comments