@@ -467,6 +467,11 @@ <h3>References</h3>
467467 this . canvas . addEventListener ( "mousemove" , this . mouseMove . bind ( this ) , false ) ;
468468 this . canvas . addEventListener ( "mouseup" , this . mouseUp . bind ( this ) , false ) ;
469469 this . canvas . addEventListener ( "wheel" , this . wheel . bind ( this ) , false ) ;
470+ // register touch event listeners (mobile / tablet)
471+ // passive:false is required so we can call preventDefault() to suppress scrolling
472+ this . canvas . addEventListener ( "touchstart" , this . touchStart . bind ( this ) , { passive : false } ) ;
473+ this . canvas . addEventListener ( "touchmove" , this . touchMove . bind ( this ) , { passive : false } ) ;
474+ this . canvas . addEventListener ( "touchend" , this . touchEnd . bind ( this ) , { passive : false } ) ;
470475 }
471476
472477 // set simulation parameters from GUI and start mainLoop
@@ -642,6 +647,61 @@ <h3>References</h3>
642647 if ( this . zoom < 1 )
643648 this . zoom = 1 ;
644649 }
650+
651+ // Convert the first touch point to a plain {clientX, clientY} object
652+ // so it can be passed directly to the existing mouse handlers.
653+ getTouchClient ( event )
654+ {
655+ const t = event . touches . length > 0 ? event . touches [ 0 ] : event . changedTouches [ 0 ] ;
656+ return { clientX : t . clientX , clientY : t . clientY } ;
657+ }
658+
659+ touchStart ( event )
660+ {
661+ event . preventDefault ( ) ;
662+ if ( event . touches . length === 1 )
663+ this . mouseDown ( { which : 1 , ...this . getTouchClient ( event ) } ) ;
664+ else if ( event . touches . length === 2 )
665+ this . lastPinchDist = this . getPinchDist ( event ) ;
666+ }
667+
668+ touchMove ( event )
669+ {
670+ event . preventDefault ( ) ;
671+ if ( event . touches . length === 1 )
672+ {
673+ this . lastPinchDist = null ;
674+ this . mouseMove ( this . getTouchClient ( event ) ) ;
675+ }
676+ else if ( event . touches . length === 2 )
677+ {
678+ // deselect any dragged particle while pinching
679+ this . selectedParticle = - 1 ;
680+ const dist = this . getPinchDist ( event ) ;
681+ if ( this . lastPinchDist !== null )
682+ {
683+ this . zoom += ( dist - this . lastPinchDist ) * 0.3 ;
684+ if ( this . zoom < 1 ) this . zoom = 1 ;
685+ }
686+ this . lastPinchDist = dist ;
687+ }
688+ }
689+
690+ touchEnd ( event )
691+ {
692+ event . preventDefault ( ) ;
693+ if ( event . touches . length < 2 )
694+ this . lastPinchDist = null ;
695+ if ( event . touches . length === 0 )
696+ this . mouseUp ( event ) ;
697+ }
698+
699+ getPinchDist ( event )
700+ {
701+ const dx = event . touches [ 0 ] . clientX - event . touches [ 1 ] . clientX ;
702+ const dy = event . touches [ 0 ] . clientY - event . touches [ 1 ] . clientY ;
703+ return Math . sqrt ( dx * dx + dy * dy ) ;
704+ }
645705 }
646706
647707 gui = new GUI ( ) ;
0 commit comments