From e36c4d3a3b1be91303f296d14c2e88a5541cc50a Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Mon, 22 Nov 2010 17:30:44 +0000 Subject: [PATCH] From Stephan Huber, "attached you'll find a proposal for handling multi-touch-events with osgGA. My approach is to bundle all touchpoints into one custom data structure which is attached to an GUIEventAdapter. The current approach simulates a moving mouse for the first touch-point, so basic manipulators do work, sort of. I created a MultiTouchTrackballManipulator-class, one touch-point does rotate the view, two touch-points pan and zoom the view as known from the iphone or other similar multi-touch-devices. A double-tap (similar to a double-click) resets the manipulator to its home-position. The multi-touch-trackball-implementation is not the best, see it as a first starting point. (there's a demo-video at http://vimeo.com/15017377 )" --- include/osgGA/EventQueue | 16 +++ include/osgGA/GUIEventAdapter | 65 ++++++++- include/osgGA/MultiTouchTrackballManipulator | 47 +++++++ src/osgGA/CMakeLists.txt | 2 + src/osgGA/EventQueue.cpp | 51 ++++++++ src/osgGA/GUIEventAdapter.cpp | 17 ++- src/osgGA/MultiTouchTrackballManipulator.cpp | 131 +++++++++++++++++++ 7 files changed, 325 insertions(+), 4 deletions(-) create mode 100644 include/osgGA/MultiTouchTrackballManipulator create mode 100644 src/osgGA/MultiTouchTrackballManipulator.cpp diff --git a/include/osgGA/EventQueue b/include/osgGA/EventQueue index efd408cfc..b88427a40 100644 --- a/include/osgGA/EventQueue +++ b/include/osgGA/EventQueue @@ -157,6 +157,22 @@ class OSGGA_EXPORT EventQueue : public osg::Referenced /** Method for adapting keyboard press events. Note, special keys such as Ctrl/Function keys should be adapted to GUIEventAdapter::KeySymbol mappings, with specified time.*/ void keyRelease(int key, double time); + + GUIEventAdapter* touchBegan(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, double time); + GUIEventAdapter* touchBegan(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y) { + return touchBegan(id, phase, x, y, getTime()); + } + + GUIEventAdapter* touchMoved(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, double time); + GUIEventAdapter* touchMoved(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y) { + return touchMoved(id, phase, x, y, getTime()); + } + + GUIEventAdapter* touchEnded(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, unsigned int tap_count, double time); + GUIEventAdapter* touchEnded(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, unsigned int tap_count) { + return touchEnded(id, phase, x, y, tap_count, getTime()); + } + /** Method for adapting close window events.*/ diff --git a/include/osgGA/GUIEventAdapter b/include/osgGA/GUIEventAdapter index 62dfd6c3c..bbb52ebfb 100644 --- a/include/osgGA/GUIEventAdapter +++ b/include/osgGA/GUIEventAdapter @@ -255,6 +255,62 @@ public: ERASER }; + enum TouchPhase + { + TOUCH_UNKNOWN, + TOUCH_BEGAN, + TOUCH_MOVED, + TOUCH_STATIONERY, + TOUCH_ENDED + }; + + class TouchData : public osg::Referenced { + public: + struct TouchPoint { + unsigned int id; + TouchPhase phase; + float x, y; + + unsigned int tapCount; + TouchPoint() : id(0), phase(TOUCH_UNKNOWN), x(0.0f), y(0.0f), tapCount(0) {} + TouchPoint(unsigned int in_id, TouchPhase in_phase, float in_x, float in_y, unsigned int in_tap_count) + : id(in_id), + phase(in_phase), + x(in_x), + y(in_y), + tapCount(in_tap_count) + { + } + }; + + typedef std::vector TouchSet; + + typedef TouchSet::iterator iterator; + typedef TouchSet::const_iterator const_iterator; + + TouchData() : osg::Referenced() {} + + unsigned int getNumTouchPoints() const { return _touches.size(); } + + iterator begin() { return _touches.begin(); } + const_iterator begin() const { return _touches.begin(); } + + iterator end() { return _touches.end(); } + const_iterator end() const { return _touches.end(); } + + const TouchPoint get(unsigned int i) const { return _touches[i]; } + + protected: + + void addTouchPoint(unsigned int id, TouchPhase phase, float x, float y, unsigned int tap_count) { + _touches.push_back(TouchPoint(id, phase, x, y, tap_count)); + } + + TouchSet _touches; + + friend class GUIEventAdapter; + }; + public: GUIEventAdapter(); @@ -443,7 +499,11 @@ public: /// set the orientation from a tablet input device as a matrix. const osg::Matrix getPenOrientation() const; - + void addTouchPoint(unsigned int id, TouchPhase phase, float x, float y, unsigned int tapCount = 0); + + TouchData* getTouchData() const { return _touchData; } + bool isMultiTouchEvent() const { return (_touchData.valid()); } + protected: /** Force users to create on heap, so that multiple referencing is safe.*/ @@ -489,7 +549,8 @@ public: TabletPen(const TabletPen& rhs) : pressure(rhs.pressure), tiltX(rhs.tiltX), tiltY(rhs.tiltY), rotation(rhs.rotation), tabletPointerType(rhs.tabletPointerType) {} }; TabletPen _tabletPen; - + + osg::ref_ptr _touchData; }; } diff --git a/include/osgGA/MultiTouchTrackballManipulator b/include/osgGA/MultiTouchTrackballManipulator new file mode 100644 index 000000000..0e5763aac --- /dev/null +++ b/include/osgGA/MultiTouchTrackballManipulator @@ -0,0 +1,47 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2010 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGGA_MULTITOUCH_TRACKBALL_MANIPULATOR +#define OSGGA_MULTITOUCH_TRACKBALL_MANIPULATOR 1 + +#include + + +namespace osgGA { + + +class OSGGA_EXPORT MultiTouchTrackballManipulator : public TrackballManipulator +{ + typedef TrackballManipulator inherited; + + public: + + MultiTouchTrackballManipulator( int flags = DEFAULT_SETTINGS ); + MultiTouchTrackballManipulator( const MultiTouchTrackballManipulator& tm, + const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY ); + + META_Object( osgGA, MultiTouchTrackballManipulator ); + + bool handle( const GUIEventAdapter& ea, GUIActionAdapter& us ); + + protected: + + void handleMultiTouchDrag(GUIEventAdapter::TouchData* now, GUIEventAdapter::TouchData* last, const double eventTimeDelta); + + osg::ref_ptr _lastTouchData; +}; + + +} + +#endif /* OSGGA_MULTITOUCH_TRACKBALL_MANIPULATOR */ diff --git a/src/osgGA/CMakeLists.txt b/src/osgGA/CMakeLists.txt index 180d23a8b..78c06c07d 100644 --- a/src/osgGA/CMakeLists.txt +++ b/src/osgGA/CMakeLists.txt @@ -20,6 +20,7 @@ SET(LIB_PUBLIC_HEADERS ${HEADER_PATH}/GUIEventHandler ${HEADER_PATH}/KeySwitchMatrixManipulator ${HEADER_PATH}/CameraManipulator + ${HEADER_PATH}/MultiTouchTrackballManipulator ${HEADER_PATH}/NodeTrackerManipulator ${HEADER_PATH}/OrbitManipulator ${HEADER_PATH}/StandardManipulator @@ -46,6 +47,7 @@ ADD_LIBRARY(${LIB_NAME} GUIEventHandler.cpp KeySwitchMatrixManipulator.cpp CameraManipulator.cpp + MultiTouchTrackballManipulator.cpp NodeTrackerManipulator.cpp OrbitManipulator.cpp StandardManipulator.cpp diff --git a/src/osgGA/EventQueue.cpp b/src/osgGA/EventQueue.cpp index 16b195308..c2dfd6710 100644 --- a/src/osgGA/EventQueue.cpp +++ b/src/osgGA/EventQueue.cpp @@ -344,6 +344,57 @@ void EventQueue::keyRelease(int key, double time) addEvent(event); } + +GUIEventAdapter* EventQueue::touchBegan(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, double time) +{ + // emulate left mouse button press + + _accumulateEventState->setButtonMask((1) | _accumulateEventState->getButtonMask()); + _accumulateEventState->setX(x); + _accumulateEventState->setY(y); + + GUIEventAdapter* event = new GUIEventAdapter(*_accumulateEventState); + event->setEventType(GUIEventAdapter::PUSH); + event->setTime(time); + event->addTouchPoint(id, phase, x, y, 0); + + addEvent(event); + + return event; +} + + +GUIEventAdapter* EventQueue::touchMoved(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, double time) +{ + _accumulateEventState->setX(x); + _accumulateEventState->setY(y); + + + GUIEventAdapter* event = new GUIEventAdapter(*_accumulateEventState); + event->setEventType(GUIEventAdapter::DRAG); + event->setTime(time); + event->addTouchPoint(id, phase, x, y, 0); + addEvent(event); + + return event; +} + +GUIEventAdapter* EventQueue::touchEnded(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, unsigned int tap_count, double time) +{ + _accumulateEventState->setButtonMask(~(1) & _accumulateEventState->getButtonMask()); + _accumulateEventState->setX(x); + _accumulateEventState->setY(y); + + GUIEventAdapter* event = new GUIEventAdapter(*_accumulateEventState); + event->setEventType(GUIEventAdapter::RELEASE); + event->setTime(time); + event->addTouchPoint(id, phase, x, y, tap_count); + addEvent(event); + + return event; +} + + void EventQueue::closeWindow(double time) { GUIEventAdapter* event = new GUIEventAdapter(*_accumulateEventState); diff --git a/src/osgGA/GUIEventAdapter.cpp b/src/osgGA/GUIEventAdapter.cpp index 29f46bb40..6697e8d53 100644 --- a/src/osgGA/GUIEventAdapter.cpp +++ b/src/osgGA/GUIEventAdapter.cpp @@ -42,7 +42,8 @@ GUIEventAdapter::GUIEventAdapter(): _modKeyMask(0), _mouseYOrientation(Y_INCREASING_DOWNWARDS), _scrolling(), - _tabletPen() + _tabletPen(), + _touchData(NULL) {} GUIEventAdapter::GUIEventAdapter(const GUIEventAdapter& rhs,const osg::CopyOp& copyop): @@ -67,7 +68,8 @@ GUIEventAdapter::GUIEventAdapter(const GUIEventAdapter& rhs,const osg::CopyOp& c _modKeyMask(rhs._modKeyMask), _mouseYOrientation(rhs._mouseYOrientation), _scrolling(rhs._scrolling), - _tabletPen(rhs._tabletPen) + _tabletPen(rhs._tabletPen), + _touchData(rhs._touchData) {} GUIEventAdapter::~GUIEventAdapter() @@ -107,3 +109,14 @@ const osg::Matrix GUIEventAdapter::getPenOrientation() const return ( zrot * yrot * xrot ); } + +void GUIEventAdapter::addTouchPoint(unsigned int id, TouchPhase phase, float x, float y, unsigned int tapCount) +{ + if (!_touchData.valid()) { + _touchData = new TouchData(); + setX(x); + setY(y); + } + + _touchData->addTouchPoint(id, phase, x, y, tapCount); +} diff --git a/src/osgGA/MultiTouchTrackballManipulator.cpp b/src/osgGA/MultiTouchTrackballManipulator.cpp new file mode 100644 index 000000000..67c745479 --- /dev/null +++ b/src/osgGA/MultiTouchTrackballManipulator.cpp @@ -0,0 +1,131 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2010 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + + +#include +#include + +using namespace osg; +using namespace osgGA; + + + +/// Constructor. +MultiTouchTrackballManipulator::MultiTouchTrackballManipulator( int flags ) + : inherited( flags ) +{ + setVerticalAxisFixed( false ); +} + + +/// Constructor. +MultiTouchTrackballManipulator::MultiTouchTrackballManipulator( const MultiTouchTrackballManipulator& tm, const CopyOp& copyOp ) + : inherited( tm, copyOp ) +{ +} + + +void MultiTouchTrackballManipulator::handleMultiTouchDrag(GUIEventAdapter::TouchData* now, GUIEventAdapter::TouchData* last, const double eventTimeDelta) +{ + const float zoom_threshold = 1.0f; + + osg::Vec2 pt_1_now(now->get(0).x,now->get(0).y); + osg::Vec2 pt_2_now(now->get(1).x,now->get(1).y); + osg::Vec2 pt_1_last(last->get(0).x,last->get(0).y); + osg::Vec2 pt_2_last(last->get(1).x,last->get(1).y); + + + + float gap_now((pt_1_now - pt_2_now).length()); + float gap_last((pt_1_last - pt_2_last).length()); + + // osg::notify(osg::ALWAYS) << gap_now << " " << gap_last << std::endl; + + if (abs(gap_last - gap_now) >= zoom_threshold) + { + // zoom gesture + zoomModel( (gap_last - gap_now) * eventTimeDelta, true ); + } + + // drag gesture + + osg::Vec2 delta = ((pt_1_last - pt_1_now) + (pt_2_last - pt_2_now)) / 2.0f; + + float scale = 0.2f * _distance * eventTimeDelta; + + // osg::notify(osg::ALWAYS) << "drag: " << delta << " scale: " << scale << std::endl; + + panModel( delta.x() * scale, delta.y() * scale * (-1)); // flip y-coord because of different origins. + + +} + + +bool MultiTouchTrackballManipulator::handle( const GUIEventAdapter& ea, GUIActionAdapter& us ) +{ + + bool handled(false); + + switch(ea.getEventType()) { + + case osgGA::GUIEventAdapter::PUSH: + case osgGA::GUIEventAdapter::DRAG: + case osgGA::GUIEventAdapter::RELEASE: + if (ea.isMultiTouchEvent()) + { + double eventTimeDelta = 1/60.0; //_ga_t0->getTime() - _ga_t1->getTime(); + if( eventTimeDelta < 0. ) + { + OSG_WARN << "Manipulator warning: eventTimeDelta = " << eventTimeDelta << std::endl; + eventTimeDelta = 0.; + } + osgGA::GUIEventAdapter::TouchData* data = ea.getTouchData(); + + // three touches or two taps for home position + if ((data->getNumTouchPoints() == 3) || ((data->getNumTouchPoints() == 1) && (data->get(0).tapCount >= 2))) { + flushMouseEventStack(); + _thrown = false; + home(ea,us); + handled = true; + } + + else if (data->getNumTouchPoints() >= 2) + { + if ((_lastTouchData.valid()) && (_lastTouchData->getNumTouchPoints() >= 2)) { + handleMultiTouchDrag(data, _lastTouchData, eventTimeDelta); + } + + handled = true; + } + + _lastTouchData = data; + + // check if all touches ended + unsigned int num_touches_ended(0); + for(osgGA::GUIEventAdapter::TouchData::iterator i = data->begin(); i != data->end(); ++i) { + if ((*i).phase == osgGA::GUIEventAdapter::TOUCH_ENDED) + num_touches_ended++; + } + + if(num_touches_ended == data->getNumTouchPoints()) { + _lastTouchData = NULL; + } + + } + break; + default: + break; + } + + return handled ? handled : TrackballManipulator::handle(ea, us); +} \ No newline at end of file