@@ -464,6 +464,11 @@ <h3>4. Time integration</h3>
464464 this . canvas . addEventListener ( "mousemove" , this . mouseMove . bind ( this ) , false ) ;
465465 this . canvas . addEventListener ( "mouseup" , this . mouseUp . bind ( this ) , false ) ;
466466 this . canvas . addEventListener ( "wheel" , this . wheel . bind ( this ) , false ) ;
467+ // register touch event listeners (mobile / tablet)
468+ // passive:false is required so we can call preventDefault() to suppress scrolling
469+ this . canvas . addEventListener ( "touchstart" , this . touchStart . bind ( this ) , { passive : false } ) ;
470+ this . canvas . addEventListener ( "touchmove" , this . touchMove . bind ( this ) , { passive : false } ) ;
471+ this . canvas . addEventListener ( "touchend" , this . touchEnd . bind ( this ) , { passive : false } ) ;
467472 }
468473
469474 // set simulation parameters from GUI and start mainLoop
@@ -650,6 +655,61 @@ <h3>4. Time integration</h3>
650655 if ( this . zoom < 1 )
651656 this . zoom = 1 ;
652657 }
658+
659+ // Convert the first touch point to a plain {clientX, clientY} object
660+ // so it can be passed directly to the existing mouse handlers.
661+ getTouchClient ( event )
662+ {
663+ const t = event . touches . length > 0 ? event . touches [ 0 ] : event . changedTouches [ 0 ] ;
664+ return { clientX : t . clientX , clientY : t . clientY } ;
665+ }
666+
667+ touchStart ( event )
668+ {
669+ event . preventDefault ( ) ;
670+ if ( event . touches . length === 1 )
671+ this . mouseDown ( { which : 1 , ...this . getTouchClient ( event ) } ) ;
672+ else if ( event . touches . length === 2 )
673+ this . lastPinchDist = this . getPinchDist ( event ) ;
674+ }
675+
676+ touchMove ( event )
677+ {
678+ event . preventDefault ( ) ;
679+ if ( event . touches . length === 1 )
680+ {
681+ this . lastPinchDist = null ;
682+ this . mouseMove ( this . getTouchClient ( event ) ) ;
683+ }
684+ else if ( event . touches . length === 2 )
685+ {
686+ // deselect any dragged particle while pinching
687+ this . selectedParticle = - 1 ;
688+ const dist = this . getPinchDist ( event ) ;
689+ if ( this . lastPinchDist !== null )
690+ {
691+ this . zoom += ( dist - this . lastPinchDist ) * 0.3 ;
692+ if ( this . zoom < 1 ) this . zoom = 1 ;
693+ }
694+ this . lastPinchDist = dist ;
695+ }
696+ }
697+
698+ touchEnd ( event )
699+ {
700+ event . preventDefault ( ) ;
701+ if ( event . touches . length < 2 )
702+ this . lastPinchDist = null ;
703+ if ( event . touches . length === 0 )
704+ this . mouseUp ( event ) ;
705+ }
706+
707+ getPinchDist ( event )
708+ {
709+ const dx = event . touches [ 0 ] . clientX - event . touches [ 1 ] . clientX ;
710+ const dy = event . touches [ 0 ] . clientY - event . touches [ 1 ] . clientY ;
711+ return Math . sqrt ( dx * dx + dy * dy ) ;
712+ }
653713 }
654714
655715 gui = new GUI ( ) ;
0 commit comments