Skip to content

Commit 5b4aebd

Browse files
author
DylanHuang
committed
```
feat(app): improve touch gesture handling in Trackball - Refactor touch down event handling to properly support single and multi-touch gestures We cannot assume that touchDown.id can only be 0 or 1. ```
1 parent 0bc2573 commit 5b4aebd

File tree

1 file changed

+93
-42
lines changed

1 file changed

+93
-42
lines changed

src/vsg/app/Trackball.cpp

Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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

337338
void 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

Comments
 (0)