@@ -357,6 +357,11 @@ <h3>2. Time integration</h3>
357357 this . canvas . addEventListener ( "mousemove" , this . mouseMove . bind ( this ) , false ) ;
358358 this . canvas . addEventListener ( "mouseup" , this . mouseUp . bind ( this ) , false ) ;
359359 this . canvas . addEventListener ( "wheel" , this . wheel . bind ( this ) , false ) ;
360+ // register touch event listeners (mobile / tablet)
361+ // passive:false is required so we can call preventDefault() to suppress scrolling
362+ this . canvas . addEventListener ( "touchstart" , this . touchStart . bind ( this ) , { passive : false } ) ;
363+ this . canvas . addEventListener ( "touchmove" , this . touchMove . bind ( this ) , { passive : false } ) ;
364+ this . canvas . addEventListener ( "touchend" , this . touchEnd . bind ( this ) , { passive : false } ) ;
360365 }
361366
362367 // set simulation parameters from GUI and start mainLoop
@@ -533,6 +538,61 @@ <h3>2. Time integration</h3>
533538 if ( this . zoom < 1 )
534539 this . zoom = 1 ;
535540 }
541+
542+ // Convert the first touch point to a plain {clientX, clientY} object
543+ // so it can be passed directly to the existing mouse handlers.
544+ getTouchClient ( event )
545+ {
546+ const t = event . touches . length > 0 ? event . touches [ 0 ] : event . changedTouches [ 0 ] ;
547+ return { clientX : t . clientX , clientY : t . clientY } ;
548+ }
549+
550+ touchStart ( event )
551+ {
552+ event . preventDefault ( ) ;
553+ if ( event . touches . length === 1 )
554+ this . mouseDown ( { which : 1 , ...this . getTouchClient ( event ) } ) ;
555+ else if ( event . touches . length === 2 )
556+ this . lastPinchDist = this . getPinchDist ( event ) ;
557+ }
558+
559+ touchMove ( event )
560+ {
561+ event . preventDefault ( ) ;
562+ if ( event . touches . length === 1 )
563+ {
564+ this . lastPinchDist = null ;
565+ this . mouseMove ( this . getTouchClient ( event ) ) ;
566+ }
567+ else if ( event . touches . length === 2 )
568+ {
569+ // deselect any dragged particle while pinching
570+ this . selectedParticle = - 1 ;
571+ const dist = this . getPinchDist ( event ) ;
572+ if ( this . lastPinchDist !== null )
573+ {
574+ this . zoom += ( dist - this . lastPinchDist ) * 0.3 ;
575+ if ( this . zoom < 1 ) this . zoom = 1 ;
576+ }
577+ this . lastPinchDist = dist ;
578+ }
579+ }
580+
581+ touchEnd ( event )
582+ {
583+ event . preventDefault ( ) ;
584+ if ( event . touches . length < 2 )
585+ this . lastPinchDist = null ;
586+ if ( event . touches . length === 0 )
587+ this . mouseUp ( event ) ;
588+ }
589+
590+ getPinchDist ( event )
591+ {
592+ const dx = event . touches [ 0 ] . clientX - event . touches [ 1 ] . clientX ;
593+ const dy = event . touches [ 0 ] . clientY - event . touches [ 1 ] . clientY ;
594+ return Math . sqrt ( dx * dx + dy * dy ) ;
595+ }
536596 }
537597
538598 gui = new GUI ( ) ;
0 commit comments