@@ -554,6 +554,11 @@ <h3>References</h3>
554554 this . canvas . addEventListener ( "mousemove" , this . mouseMove . bind ( this ) , false ) ;
555555 this . canvas . addEventListener ( "mouseup" , this . mouseUp . bind ( this ) , false ) ;
556556 this . canvas . addEventListener ( "wheel" , this . wheel . bind ( this ) , false ) ;
557+ // register touch event listeners (mobile / tablet)
558+ // passive:false is required so we can call preventDefault() to suppress scrolling
559+ this . canvas . addEventListener ( "touchstart" , this . touchStart . bind ( this ) , { passive : false } ) ;
560+ this . canvas . addEventListener ( "touchmove" , this . touchMove . bind ( this ) , { passive : false } ) ;
561+ this . canvas . addEventListener ( "touchend" , this . touchEnd . bind ( this ) , { passive : false } ) ;
557562 }
558563
559564 // set simulation parameters from GUI and start mainLoop
@@ -784,6 +789,61 @@ <h3>References</h3>
784789 if ( this . zoom < 1 )
785790 this . zoom = 1 ;
786791 }
792+
793+ // Convert the first touch point to a plain {clientX, clientY} object
794+ // so it can be passed directly to the existing mouse handlers.
795+ getTouchClient ( event )
796+ {
797+ const t = event . touches . length > 0 ? event . touches [ 0 ] : event . changedTouches [ 0 ] ;
798+ return { clientX : t . clientX , clientY : t . clientY } ;
799+ }
800+
801+ touchStart ( event )
802+ {
803+ event . preventDefault ( ) ;
804+ if ( event . touches . length === 1 )
805+ this . mouseDown ( { which : 1 , ...this . getTouchClient ( event ) } ) ;
806+ else if ( event . touches . length === 2 )
807+ this . lastPinchDist = this . getPinchDist ( event ) ;
808+ }
809+
810+ touchMove ( event )
811+ {
812+ event . preventDefault ( ) ;
813+ if ( event . touches . length === 1 )
814+ {
815+ this . lastPinchDist = null ;
816+ this . mouseMove ( this . getTouchClient ( event ) ) ;
817+ }
818+ else if ( event . touches . length === 2 )
819+ {
820+ // deselect any dragged particle while pinching
821+ this . selectedParticle = - 1 ;
822+ const dist = this . getPinchDist ( event ) ;
823+ if ( this . lastPinchDist !== null )
824+ {
825+ this . zoom += ( dist - this . lastPinchDist ) * 0.3 ;
826+ if ( this . zoom < 1 ) this . zoom = 1 ;
827+ }
828+ this . lastPinchDist = dist ;
829+ }
830+ }
831+
832+ touchEnd ( event )
833+ {
834+ event . preventDefault ( ) ;
835+ if ( event . touches . length < 2 )
836+ this . lastPinchDist = null ;
837+ if ( event . touches . length === 0 )
838+ this . mouseUp ( event ) ;
839+ }
840+
841+ getPinchDist ( event )
842+ {
843+ const dx = event . touches [ 0 ] . clientX - event . touches [ 1 ] . clientX ;
844+ const dy = event . touches [ 0 ] . clientY - event . touches [ 1 ] . clientY ;
845+ return Math . sqrt ( dx * dx + dy * dy ) ;
846+ }
787847 }
788848
789849 gui = new GUI ( ) ;
0 commit comments