@@ -302,43 +302,45 @@ void Trackball::apply(TouchDownEvent& touchDown)
302302 if (!eventRelevant (touchDown)) return ;
303303
304304 _previousTouches[touchDown.id ] = &touchDown;
305- switch (touchDown.id )
305+
306+ // First touch - simulate button press for rotation
307+ if (_previousTouches.size () == 1 )
306308 {
307- case 0 : {
308- if (_previousTouches.size () == 1 )
309- {
310- vsg::ref_ptr<vsg::Window> w = touchDown.window ;
311- vsg::ref_ptr<vsg::ButtonPressEvent> evt = vsg::ButtonPressEvent::create (
312- w,
313- touchDown.time ,
314- touchDown.x ,
315- touchDown.y ,
316- touchMappedToButtonMask,
317- touchDown.id );
318- apply (*evt.get ());
319- }
320- break ;
321- }
322- case 1 : {
323- _prevZoomTouchDistance = 0.0 ;
324- if (touchDown.id == 0 && _previousTouches.count (1 ))
325- {
326- const auto & prevTouch1 = _previousTouches[1 ];
327- auto a = std::abs (static_cast <double >(prevTouch1->x ) - touchDown.x );
328- auto b = std::abs (static_cast <double >(prevTouch1->y ) - touchDown.y );
329- if (a > 0 || b > 0 )
330- _prevZoomTouchDistance = sqrt (a * a + b * b);
331- }
332- break ;
309+ vsg::ref_ptr<vsg::Window> w = touchDown.window ;
310+ vsg::ref_ptr<vsg::ButtonPressEvent> evt = vsg::ButtonPressEvent::create (
311+ w,
312+ touchDown.time ,
313+ touchDown.x ,
314+ touchDown.y ,
315+ touchMappedToButtonMask,
316+ touchDown.id );
317+ apply (*evt.get ());
333318 }
319+ // Second touch - initialize zoom distance
320+ else if (_previousTouches.size () == 2 )
321+ {
322+ // Calculate initial distance between the two touches
323+ auto it = _previousTouches.begin ();
324+ const TouchEvent* touch1 = it->second ;
325+ ++it;
326+ const TouchEvent* touch2 = it->second ;
327+
328+ auto a = std::abs (static_cast <double >(touch1->x ) - static_cast <double >(touch2->x ));
329+ auto b = std::abs (static_cast <double >(touch1->y ) - static_cast <double >(touch2->y ));
330+ _prevZoomTouchDistance = sqrt (a * a + b * b);
331+
332+ // Reset zoom state to establish a stable baseline
333+ // This prevents immediate zoom when second finger touches
334+ _zoomPreviousRatio = 0.0 ;
334335 }
335336}
336337
337338void Trackball::apply (TouchUpEvent& touchUp)
338339{
339340 if (!eventRelevant (touchUp)) return ;
340341
341- if (touchUp.id == 0 && _previousTouches.size () == 1 )
342+ // If this is the last touch, simulate button release
343+ if (_previousTouches.size () == 1 )
342344 {
343345 vsg::ref_ptr<vsg::Window> w = touchUp.window ;
344346 vsg::ref_ptr<vsg::ButtonReleaseEvent> evt = vsg::ButtonReleaseEvent::create (
@@ -372,24 +374,73 @@ void Trackball::apply(TouchMoveEvent& touchMove)
372374 break ;
373375 }
374376 case 2 : {
375- if (touchMove.id == 0 && _previousTouches.count (0 ))
377+ // Two touches - Zoom by pinch
378+ // Find the other touch (not the current moving one)
379+ const TouchEvent* otherTouch = nullptr ;
380+
381+ // Iterate through the map to find the touch that isn't the current one
382+ for (const auto & [id, touch] : _previousTouches)
376383 {
377- // Zoom
378- const auto & prevTouch1 = _previousTouches[1 ];
379- auto a = std::abs (static_cast <double >(prevTouch1->x ) - touchMove.x );
380- auto b = std::abs (static_cast <double >(prevTouch1->y ) - touchMove.y );
381- if (a > 0 || b > 0 )
384+ if (id != touchMove.id )
382385 {
383- auto touchZoomDistance = sqrt (a * a + b * b);
384- if (_prevZoomTouchDistance && touchZoomDistance > 0 )
386+ otherTouch = touch;
387+ break ;
388+ }
389+ }
390+
391+ if (otherTouch)
392+ {
393+ // Calculate current distance between two touches
394+ auto a = std::abs (static_cast <double >(otherTouch->x ) - touchMove.x );
395+ auto b = std::abs (static_cast <double >(otherTouch->y ) - touchMove.y );
396+ auto currentDistance = sqrt (a * a + b * b);
397+
398+ // Only process zoom if we have a valid previous distance
399+ if (_prevZoomTouchDistance > 0 && currentDistance > 0 )
400+ {
401+ // Calculate the distance change
402+ auto distanceChange = std::abs (currentDistance - _prevZoomTouchDistance);
403+
404+ // Minimum threshold to prevent jitter (e.g., 5 pixels)
405+ // Adjust this value based on screen DPI and desired sensitivity
406+ constexpr double MIN_ZOOM_THRESHOLD = 5.0 ;
407+
408+ if (distanceChange >= MIN_ZOOM_THRESHOLD)
385409 {
386- auto zoomLevel = touchZoomDistance / _prevZoomTouchDistance;
387- if (zoomLevel < 1 )
388- zoomLevel = -(1 / zoomLevel);
389- zoomLevel *= 0.1 ;
390- zoom (zoomLevel);
410+ // Calculate zoom ratio
411+ auto zoomRatio = currentDistance / _prevZoomTouchDistance;
412+
413+ // Dead zone: ignore tiny changes near 1.0 (e.g., 0.98-1.02)
414+ constexpr double DEAD_ZONE_MIN = 0.98 ;
415+ constexpr double DEAD_ZONE_MAX = 1.02 ;
416+
417+ if (zoomRatio < DEAD_ZONE_MIN || zoomRatio > DEAD_ZONE_MAX)
418+ {
419+ // Clamp extreme zoom values to prevent sudden jumps
420+ // Limit to 0.5x - 2.0x per frame
421+ constexpr double MIN_ZOOM_RATIO = 0.5 ;
422+ constexpr double MAX_ZOOM_RATIO = 2.0 ;
423+ zoomRatio = std::clamp (zoomRatio, MIN_ZOOM_RATIO, MAX_ZOOM_RATIO);
424+
425+ // Convert ratio to zoom level
426+ auto zoomLevel = zoomRatio;
427+ if (zoomLevel < 1.0 )
428+ zoomLevel = -(1.0 / zoomLevel);
429+
430+ // Apply zoom with damping factor (0.1 = 10% of calculated zoom)
431+ // This makes zoom feel smoother and more controllable
432+ zoomLevel *= 0.1 ;
433+ zoom (zoomLevel);
434+ vsg::debug (" zoom event - distanceChange: " , distanceChange, " , zoomRatio: " , zoomRatio, " , zoomLevel: " , zoomLevel);
435+ // Update previous distance for next frame
436+ _prevZoomTouchDistance = currentDistance;
437+ }
391438 }
392- _prevZoomTouchDistance = touchZoomDistance;
439+ }
440+ else
441+ {
442+ // First valid measurement, just store the distance
443+ _prevZoomTouchDistance = currentDistance;
393444 }
394445 }
395446 break ;
0 commit comments