@@ -213,6 +213,11 @@ <h3>4. Time integration</h3>
213213 this . canvas . addEventListener ( "mousemove" , this . mouseMove . bind ( this ) , false ) ;
214214 this . canvas . addEventListener ( "mouseup" , this . mouseUp . bind ( this ) , false ) ;
215215 this . canvas . addEventListener ( "wheel" , this . wheel . bind ( this ) , false ) ;
216+ // register touch event listeners (mobile / tablet)
217+ // passive:false is required so we can call preventDefault() to suppress scrolling
218+ this . canvas . addEventListener ( "touchstart" , this . touchStart . bind ( this ) , { passive : false } ) ;
219+ this . canvas . addEventListener ( "touchmove" , this . touchMove . bind ( this ) , { passive : false } ) ;
220+ this . canvas . addEventListener ( "touchend" , this . touchEnd . bind ( this ) , { passive : false } ) ;
216221 }
217222
218223 // set simulation parameters from GUI and start mainLoop
@@ -371,6 +376,61 @@ <h3>4. Time integration</h3>
371376 if ( this . zoom < 1 )
372377 this . zoom = 1 ;
373378 }
379+
380+ // Convert the first touch point to a plain {clientX, clientY} object
381+ // so it can be passed directly to the existing mouse handlers.
382+ getTouchClient ( event )
383+ {
384+ const t = event . touches . length > 0 ? event . touches [ 0 ] : event . changedTouches [ 0 ] ;
385+ return { clientX : t . clientX , clientY : t . clientY } ;
386+ }
387+
388+ touchStart ( event )
389+ {
390+ event . preventDefault ( ) ;
391+ if ( event . touches . length === 1 )
392+ this . mouseDown ( { which : 1 , ...this . getTouchClient ( event ) } ) ;
393+ else if ( event . touches . length === 2 )
394+ this . lastPinchDist = this . getPinchDist ( event ) ;
395+ }
396+
397+ touchMove ( event )
398+ {
399+ event . preventDefault ( ) ;
400+ if ( event . touches . length === 1 )
401+ {
402+ this . lastPinchDist = null ;
403+ this . mouseMove ( this . getTouchClient ( event ) ) ;
404+ }
405+ else if ( event . touches . length === 2 )
406+ {
407+ // deselect any dragged particle while pinching
408+ this . selectedParticle = - 1 ;
409+ const dist = this . getPinchDist ( event ) ;
410+ if ( this . lastPinchDist !== null )
411+ {
412+ this . zoom += ( dist - this . lastPinchDist ) * 0.3 ;
413+ if ( this . zoom < 1 ) this . zoom = 1 ;
414+ }
415+ this . lastPinchDist = dist ;
416+ }
417+ }
418+
419+ touchEnd ( event )
420+ {
421+ event . preventDefault ( ) ;
422+ if ( event . touches . length < 2 )
423+ this . lastPinchDist = null ;
424+ if ( event . touches . length === 0 )
425+ this . mouseUp ( event ) ;
426+ }
427+
428+ getPinchDist ( event )
429+ {
430+ const dx = event . touches [ 0 ] . clientX - event . touches [ 1 ] . clientX ;
431+ const dy = event . touches [ 0 ] . clientY - event . touches [ 1 ] . clientY ;
432+ return Math . sqrt ( dx * dx + dy * dy ) ;
433+ }
374434 }
375435
376436 gui = new GUI ( ) ;
0 commit comments