From 85bce8b8ad304b5cf5a99781308d6291677c492a Mon Sep 17 00:00:00 2001 From: Robert Osfield Date: Fri, 3 Feb 2012 14:25:08 +0000 Subject: [PATCH] From Stephan Huber, "attached you'll find a first version of multi-touch-support for OS X (>= 10.6), which will forward all multi-touch events from a trackpad to the corresponding osgGA-event-structures. The support is switched off per default, but you can enable multi-touch support via a new flag for GraphicsWindowCocoa::WindowData or directly via the GraphicsWindowCocoa-class. After switching multi-touch-support on, all mouse-events from the trackpad get ignored, otherwise you'll have multiple events for the same pointer which is very confusing (as the trackpad reports absolute movement, and as a mouse relative movement). I think this is not a problem, as multi-touch-input is a completely different beast as a mouse, so you'll have to code your own event-handlers anyway. While coding this stuff, I asked myself if we should refactor GUIEventAdapter/EventQueue and assign a specific event-type for touch-input instead of using PUSH/DRAG/RELEASE. This will make it clearer how to use the code, but will break the mouse-emulation for the first touch-point and with that all existing manipulators. What do you think? I am happy to code the proposed changes. Additionally I created a small (and ugly) example osgmultitouch which makes use of the osgGA::MultiTouchTrackballManipulator, shows all touch-points on a HUD and demonstrates how to get the touchpoints from an osgGA::GUIEventAdapter. There's even a small example video here: http://vimeo.com/31611842" --- examples/CMakeLists.txt | 1 + examples/osgmultitouch/CMakeLists.txt | 4 + examples/osgmultitouch/osgmultitouch.cpp | 302 ++++++++++++++++++ include/osgGA/EventQueue | 3 + .../osgViewer/api/Cocoa/GraphicsWindowCocoa | 20 +- src/osgGA/EventQueue.cpp | 32 +- 6 files changed, 346 insertions(+), 16 deletions(-) create mode 100644 examples/osgmultitouch/CMakeLists.txt create mode 100644 examples/osgmultitouch/osgmultitouch.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 383950471..95443ff34 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -70,6 +70,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osgmultiplerendertargets) ADD_SUBDIRECTORY(osgmultitexture) ADD_SUBDIRECTORY(osgmultitexturecontrol) + ADD_SUBDIRECTORY(osgmultitouch) ADD_SUBDIRECTORY(osgmultiviewpaging) ADD_SUBDIRECTORY(osgoccluder) ADD_SUBDIRECTORY(osgocclusionquery) diff --git a/examples/osgmultitouch/CMakeLists.txt b/examples/osgmultitouch/CMakeLists.txt new file mode 100644 index 000000000..84e80a14f --- /dev/null +++ b/examples/osgmultitouch/CMakeLists.txt @@ -0,0 +1,4 @@ +SET(TARGET_SRC osgmultitouch.cpp ) + +#### end var setup ### +SETUP_EXAMPLE(osgmultitouch) diff --git a/examples/osgmultitouch/osgmultitouch.cpp b/examples/osgmultitouch/osgmultitouch.cpp new file mode 100644 index 000000000..b7ad2b54d --- /dev/null +++ b/examples/osgmultitouch/osgmultitouch.cpp @@ -0,0 +1,302 @@ +/* OpenSceneGraph example, osghud. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#ifdef __APPLE__ +#include +#endif + + +osg::Camera* createHUD(unsigned int w, unsigned int h) +{ + // create a camera to set up the projection and model view matrices, and the subgraph to draw in the HUD + osg::Camera* camera = new osg::Camera; + + // set the projection matrix + camera->setProjectionMatrix(osg::Matrix::ortho2D(0,w,0,h)); + + // set the view matrix + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setViewMatrix(osg::Matrix::identity()); + + // only clear the depth buffer + camera->setClearMask(GL_DEPTH_BUFFER_BIT); + + // draw subgraph after main camera view. + camera->setRenderOrder(osg::Camera::POST_RENDER); + + // we don't want the camera to grab event focus from the viewers main camera(s). + camera->setAllowEventFocus(false); + + + + // add to this camera a subgraph to render + { + + osg::Geode* geode = new osg::Geode(); + + std::string timesFont("fonts/arial.ttf"); + + // turn lighting off for the text and disable depth test to ensure it's always ontop. + osg::StateSet* stateset = geode->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING,osg::StateAttribute::OFF); + + osg::Vec3 position(50.0f,h-50,0.0f); + + { + osgText::Text* text = new osgText::Text; + geode->addDrawable( text ); + + text->setFont(timesFont); + text->setPosition(position); + text->setText("A simple multi-touch-example\n1 touch = rotate, \n2 touches = drag + scale, \n3 touches = home"); + } + + camera->addChild(geode); + } + + return camera; +} + + +class TestMultiTouchEventHandler : public osgGA::GUIEventHandler { +public: + TestMultiTouchEventHandler(osg::Group* parent_group) + : osgGA::GUIEventHandler(), + _cleanupOnNextFrame(false) + { + createTouchRepresentations(parent_group, 10); + } + +private: + void createTouchRepresentations(osg::Group* parent_group, unsigned int num_objects) + { + // create some geometry which is shown for every touch-point + for(unsigned int i = 0; i != num_objects; ++i) + { + std::ostringstream ss; + + osg::Geode* geode = new osg::Geode(); + + osg::ShapeDrawable* drawable = new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 100)); + drawable->setColor(osg::Vec4(0.5, 0.5, 0.5,1)); + geode->addDrawable(drawable); + + ss << "Touch " << i; + + osgText::Text* text = new osgText::Text; + geode->addDrawable( text ); + drawable->setDataVariance(osg::Object::DYNAMIC); + _drawables.push_back(drawable); + + + text->setFont("fonts/arial.ttf"); + text->setPosition(osg::Vec3(110,0,0)); + text->setText(ss.str()); + _texts.push_back(text); + text->setDataVariance(osg::Object::DYNAMIC); + + + + osg::MatrixTransform* mat = new osg::MatrixTransform(); + mat->addChild(geode); + mat->setNodeMask(0x0); + + _mats.push_back(mat); + + parent_group->addChild(mat); + } + + parent_group->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + } + + virtual bool handle (const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &aa, osg::Object *, osg::NodeVisitor *) + { + switch(ea.getEventType()) + { + case osgGA::GUIEventAdapter::FRAME: + if (_cleanupOnNextFrame) { + cleanup(0); + _cleanupOnNextFrame = false; + } + break; + + case osgGA::GUIEventAdapter::PUSH: + case osgGA::GUIEventAdapter::DRAG: + case osgGA::GUIEventAdapter::RELEASE: + { + // is this a multi-touch event? + if (!ea.isMultiTouchEvent()) + return false; + + unsigned int j(0); + + // iterate over all touch-points and update the geometry + unsigned num_touch_ended(0); + + for(osgGA::GUIEventAdapter::TouchData::iterator i = ea.getTouchData()->begin(); i != ea.getTouchData()->end(); ++i, ++j) + { + const osgGA::GUIEventAdapter::TouchData::TouchPoint& tp = (*i); + _mats[j]->setMatrix(osg::Matrix::translate(tp.x, ea.getWindowHeight() - tp.y, 0)); + _mats[j]->setNodeMask(0xffff); + + std::ostringstream ss; + ss << "Touch " << tp.id; + _texts[j]->setText(ss.str()); + + switch (tp.phase) + { + case osgGA::GUIEventAdapter::TOUCH_BEGAN: + _drawables[j]->setColor(osg::Vec4(0,1,0,1)); + break; + + case osgGA::GUIEventAdapter::TOUCH_MOVED: + _drawables[j]->setColor(osg::Vec4(1,1,1,1)); + break; + + case osgGA::GUIEventAdapter::TOUCH_ENDED: + _drawables[j]->setColor(osg::Vec4(1,0,0,1)); + ++num_touch_ended; + break; + + case osgGA::GUIEventAdapter::TOUCH_STATIONERY: + _drawables[j]->setColor(osg::Vec4(0.8,0.8,0.8,1)); + break; + + default: + break; + + } + } + + // hide unused geometry + cleanup(j); + + //check if all touches ended + if ((ea.getTouchData()->getNumTouchPoints() > 0) && (ea.getTouchData()->getNumTouchPoints() == num_touch_ended)) + { + _cleanupOnNextFrame = true; + } + + // reposition mouse-pointer + aa.requestWarpPointer((ea.getWindowX() + ea.getWindowWidth()) / 2.0, (ea.getWindowY() + ea.getWindowHeight()) / 2.0); + } + break; + + default: + break; + } + + return false; + } + + void cleanup(unsigned int j) + { + for(unsigned k = j; k < _mats.size(); ++k) { + _mats[k]->setNodeMask(0x0); + } + } + + std::vector _drawables; + std::vector _mats; + std::vector _texts; + bool _cleanupOnNextFrame; + +}; + + +int main( int argc, char **argv ) +{ + // use an ArgumentParser object to manage the program arguments. + osg::ArgumentParser arguments(&argc,argv); + + + // read the scene from the list of file specified commandline args. + osg::ref_ptr scene = osgDB::readNodeFiles(arguments); + + // if not loaded assume no arguments passed in, try use default model instead. + if (!scene) scene = osgDB::readNodeFile("dumptruck.osgt"); + + if (!scene) + { + osg::Geode* geode = new osg::Geode(); + osg::ShapeDrawable* drawable = new osg::ShapeDrawable(new osg::Box(osg::Vec3(0,0,0), 100)); + drawable->setColor(osg::Vec4(0.5, 0.5, 0.5,1)); + geode->addDrawable(drawable); + scene = geode; + } + + + // construct the viewer. + osgViewer::Viewer viewer; + + + osg::ref_ptr group = new osg::Group; + + // add the HUD subgraph. + if (scene.valid()) group->addChild(scene.get()); + + viewer.setCameraManipulator(new osgGA::MultiTouchTrackballManipulator()); + viewer.realize(); + + osg::GraphicsContext* gc = viewer.getCamera()->getGraphicsContext(); + + #ifdef __APPLE__ + // as multitouch is disabled by default, enable it now + osgViewer::GraphicsWindowCocoa* win = dynamic_cast(gc); + if (win) win->setMultiTouchEnabled(true); + #endif + + std::cout << "creating hud with " << gc->getTraits()->width << "x" << gc->getTraits()->height << std::endl; + osg::Camera* hud_camera = createHUD(gc->getTraits()->width, gc->getTraits()->height); + + + viewer.addEventHandler(new TestMultiTouchEventHandler(hud_camera)); + + + group->addChild(hud_camera); + + // set the scene to render + viewer.setSceneData(group.get()); + + return viewer.run(); + + +} diff --git a/include/osgGA/EventQueue b/include/osgGA/EventQueue index ef253e329..5108a5a25 100644 --- a/include/osgGA/EventQueue +++ b/include/osgGA/EventQueue @@ -218,6 +218,8 @@ class OSGGA_EXPORT EventQueue : public osg::Referenced /** Method for adapting user defined events with specified event time */ void userEvent(osg::Referenced* userEventData, double time); + void setFirstTouchEmulatesMouse(bool b) { _firstTouchEmulatesMouse = b; } + bool getFirstTouchEmulatesMouse() const { return _firstTouchEmulatesMouse; } protected: @@ -233,6 +235,7 @@ class OSGGA_EXPORT EventQueue : public osg::Referenced osg::Timer_t _startTick; mutable OpenThreads::Mutex _eventQueueMutex; Events _eventQueue; + bool _firstTouchEmulatesMouse; }; diff --git a/include/osgViewer/api/Cocoa/GraphicsWindowCocoa b/include/osgViewer/api/Cocoa/GraphicsWindowCocoa index ae847342f..800e569d7 100644 --- a/include/osgViewer/api/Cocoa/GraphicsWindowCocoa +++ b/include/osgViewer/api/Cocoa/GraphicsWindowCocoa @@ -60,7 +60,12 @@ class GraphicsWindowCocoa : public osgViewer::GraphicsWindow, public osgViewer:: _checkForEvents(true), _ownsWindow(true), _currentCursor(RightArrowCursor), - _window(NULL) + _window(NULL), + _view(NULL), + _context(NULL), + _pixelformat(NULL), + _updateContext(false), + _multiTouchEnabled(false) { _traits = traits; @@ -134,11 +139,12 @@ class GraphicsWindowCocoa : public osgViewer::GraphicsWindow, public osgViewer:: class WindowData : public osg::Referenced { public: - enum Options { CreateOnlyView = 1, CheckForEvents = 2, PoseAsStandaloneApp = 4}; + enum Options { CreateOnlyView = 1, CheckForEvents = 2, PoseAsStandaloneApp = 4, EnableMultiTouch = 8}; WindowData(unsigned int options) - : _createOnlyView(options & CreateOnlyView), + : _createOnlyView(options & CreateOnlyView), _checkForEvents(options & CheckForEvents), _poseAsStandaloneApp(options & PoseAsStandaloneApp), + _multiTouchEnabled(options & EnableMultiTouch), _view(NULL) { } @@ -147,12 +153,13 @@ class GraphicsWindowCocoa : public osgViewer::GraphicsWindow, public osgViewer:: bool createOnlyView() const { return _createOnlyView; } bool checkForEvents() const { return _checkForEvents; } bool poseAsStandaloneApp() const { return _poseAsStandaloneApp; } + bool isMultiTouchEnabled() const { return _multiTouchEnabled; } protected: inline void setCreatedNSView(NSView* view) { _view = view; } private: - bool _createOnlyView, _checkForEvents, _poseAsStandaloneApp; + bool _createOnlyView, _checkForEvents, _poseAsStandaloneApp, _multiTouchEnabled; NSView* _view; friend class GraphicsWindowCocoa; @@ -168,6 +175,9 @@ class GraphicsWindowCocoa : public osgViewer::GraphicsWindow, public osgViewer:: /** adapts a resize / move of the window, coords in global screen space */ void adaptResize(int x, int y, int w, int h); + bool isMultiTouchEnabled(); + void setMultiTouchEnabled(bool b); + protected: void init(); @@ -195,7 +205,7 @@ class GraphicsWindowCocoa : public osgViewer::GraphicsWindow, public osgViewer:: GraphicsWindowCocoaGLView* _view; NSOpenGLContext* _context; NSOpenGLPixelFormat* _pixelformat; - bool _updateContext; + bool _updateContext, _multiTouchEnabled; }; } diff --git a/src/osgGA/EventQueue.cpp b/src/osgGA/EventQueue.cpp index f48d1ff41..e8551a6da 100644 --- a/src/osgGA/EventQueue.cpp +++ b/src/osgGA/EventQueue.cpp @@ -24,6 +24,8 @@ EventQueue::EventQueue(GUIEventAdapter::MouseYOrientation mouseYOrientation) _accumulateEventState = new GUIEventAdapter(); _accumulateEventState->setMouseYOrientation(mouseYOrientation); + + _firstTouchEmulatesMouse = true; } EventQueue::~EventQueue() @@ -403,11 +405,14 @@ void EventQueue::keyRelease(int key, double time, int unmodifiedKey) 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); + if(_firstTouchEmulatesMouse) + { + // 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); @@ -422,9 +427,11 @@ GUIEventAdapter* EventQueue::touchBegan(unsigned int id, GUIEventAdapter::Touch GUIEventAdapter* EventQueue::touchMoved(unsigned int id, GUIEventAdapter::TouchPhase phase, float x, float y, double time) { - _accumulateEventState->setX(x); - _accumulateEventState->setY(y); - + if(_firstTouchEmulatesMouse) + { + _accumulateEventState->setX(x); + _accumulateEventState->setY(y); + } GUIEventAdapter* event = new GUIEventAdapter(*_accumulateEventState); event->setEventType(GUIEventAdapter::DRAG); @@ -437,9 +444,12 @@ GUIEventAdapter* EventQueue::touchMoved(unsigned int id, GUIEventAdapter::Touch 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); + if (_firstTouchEmulatesMouse) + { + _accumulateEventState->setButtonMask(~(1) & _accumulateEventState->getButtonMask()); + _accumulateEventState->setX(x); + _accumulateEventState->setY(y); + } GUIEventAdapter* event = new GUIEventAdapter(*_accumulateEventState); event->setEventType(GUIEventAdapter::RELEASE);