From 4e75b161db2788b503a5f2a04336c510003b8d60 Mon Sep 17 00:00:00 2001 From: Mikael Pettersson Date: Mon, 13 Jan 2020 08:27:13 +0100 Subject: [PATCH] Feature/internal touchhandling (#1038) * Removal of dead code and compiler warnings * Added basic internal touch This commit only adds the description-shell of the touch implementation * Added callbacks and first WIP of internal touch Makes use of the TouchInput/TouchInputs class in the TouchModule. Internally we cache the TouchInputs as an input deque and utilizes it for motion-vectors. This commit has bugs and issues, which will be worked upon. * Happy new year! Bumped year on branch-local files * Improvements to internal touch Almost reached feature-parity with tuio-handled touch events - Added most of the touch-logic to touchinteraction - Added helper functions to new TouchInput/TouchInputs classes * Naming changes to touch interface * Translate TUIO to TouchInput This commit translates TUIO messages to an internal TouchInput structure while still trying to keep feature parity. Removed TUIO-dependencies from many files. Changed behavior on tuioear to lock-swap its content. * Minor cleanup and fixes - Should fix touch roll - Simplified some functions * Build fix * Use internal touch in webgui - Added consume-logic to touch callbacks - Constrained touch-input to either webgui or 3D application as mouse is - This fixes some flaws with previous implementation, such as ghost inputs - Initialize touchmodule through init-functions rather than constructor * Cleanup of comments * Simplified touch classes Added timestamp through constructor meaning no more sprinkled timestamps Renamed TouchInputs to TouchInputHolder for clarity Added helper functions to the Holder to see if it holds an input Remade addInput as tryAddInput which return true on successful insertion + other cleanup * Code style cleanup and tweaks Removed avoidable zero-comparison for code clarity Cleanup of code style * Added comments to DirectInputSolver Clarifying the use of the DirectInputSolver. * Changes for coding style Change SGCT version to make it checkout-able * Clarify magic bitmask * const -> constexpr const for magic bitmasks Co-authored-by: Alexander Bock --- include/openspace/engine/globalscallbacks.h | 12 +- include/openspace/engine/openspaceengine.h | 6 +- include/openspace/util/camera.h | 1 - include/openspace/util/touch.h | 88 ++++ modules/touch/include/directinputsolver.h | 22 +- modules/touch/include/touchinteraction.h | 66 +-- modules/touch/include/touchmarker.h | 11 +- modules/touch/include/tuioear.h | 92 ++-- modules/touch/src/directinputsolver.cpp | 59 +-- modules/touch/src/touchinteraction.cpp | 557 ++++++++------------ modules/touch/src/touchmarker.cpp | 23 +- modules/touch/src/tuioear.cpp | 137 ++--- modules/touch/src/win32_touch.cpp | 240 ++++++--- modules/touch/touchmodule.cpp | 225 ++++---- modules/touch/touchmodule.h | 67 ++- modules/webbrowser/include/eventhandler.h | 8 +- modules/webbrowser/src/eventhandler.cpp | 158 ++++-- src/CMakeLists.txt | 2 + src/engine/globalscallbacks.cpp | 15 + src/engine/openspaceengine.cpp | 28 + src/rendering/framebufferrenderer.cpp | 9 - src/rendering/renderengine.cpp | 2 +- src/util/touch.cpp | 185 +++++++ 23 files changed, 1191 insertions(+), 822 deletions(-) create mode 100644 include/openspace/util/touch.h create mode 100644 src/util/touch.cpp diff --git a/include/openspace/engine/globalscallbacks.h b/include/openspace/engine/globalscallbacks.h index 856cc79f4f..08452a2af1 100644 --- a/include/openspace/engine/globalscallbacks.h +++ b/include/openspace/engine/globalscallbacks.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -53,6 +54,10 @@ std::vector>& gMouseB std::vector>& gMousePosition(); std::vector>& gMouseScrollWheel(); +std::vector>& gTouchDetected(); +std::vector>& gTouchUpdated(); +std::vector>& gTouchExit(); + } // namespace detail namespace callback { @@ -76,7 +81,12 @@ static std::vector>& mousePosition = detail::gMousePosition(); static std::vector>& mouseScrollWheel = detail::gMouseScrollWheel(); - +static std::vector>& touchDetected = + detail::gTouchDetected(); +static std::vector>& touchUpdated = + detail::gTouchUpdated(); +static std::vector>& touchExit = + detail::gTouchExit(); /** * If the framerate becomes slow, Chromium Embedded Framework (used in Web Browser Module) diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index fb0ad85061..e4e006ba41 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -80,7 +81,9 @@ public: void mouseButtonCallback(MouseButton button, MouseAction action, KeyModifier mods); void mousePositionCallback(double x, double y); void mouseScrollWheelCallback(double posX, double posY); - void externalControlCallback(const char* receivedChars, int size, int clientId); + void touchDetectionCallback(TouchInput input); + void touchUpdateCallback(TouchInput input); + void touchExitCallback(TouchInput input); std::vector encode(); void decode(std::vector data); @@ -105,7 +108,6 @@ private: void loadFonts(); void runGlobalCustomizationScripts(); - void configureLogging(); std::unique_ptr _scene; std::unique_ptr _assetManager; diff --git a/include/openspace/util/camera.h b/include/openspace/util/camera.h index 8335329b31..f71564dd82 100644 --- a/include/openspace/util/camera.h +++ b/include/openspace/util/camera.h @@ -99,7 +99,6 @@ public: // Right now this function returns the actual combined matrix which makes some // of the old calls to the function wrong.. const glm::dmat4& combinedViewMatrix() const; - const glm::dmat4& combinedViewMatrixNoScale() const; void invalidateCache(); diff --git a/include/openspace/util/touch.h b/include/openspace/util/touch.h new file mode 100644 index 0000000000..5871094fb6 --- /dev/null +++ b/include/openspace/util/touch.h @@ -0,0 +1,88 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * 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. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___TOUCH___H__ +#define __OPENSPACE_CORE___TOUCH___H__ + +#include + +#include +#include + +namespace openspace { + +struct TouchInput { + TouchInput(size_t touchDeviceId, size_t fingerId, float x, float y, double timestamp); + glm::vec2 screenCoordinates(glm::vec2 resolution) const; + glm::vec2 currentWindowCoordinates() const; + bool isMoving() const; + float distanceToPos(float otherX, float otherY) const; + float angleToPos(float otherX, float otherY) const; + + size_t touchDeviceId; + size_t fingerId; + float x; + float y; + float dx = 0.f; // movement in x direction since last touch input + float dy = 0.f; // movement in y direction since last touch input + double timestamp; // timestamp in seconds from global touch initialization +}; + +class TouchInputHolder { +public: + TouchInputHolder(TouchInput input); + + // tryAddInput: + // Succeeds upon a different input than last. + // Fails upon a too similar input as last. + bool tryAddInput(TouchInput input); + void clearInputs(); + + bool holdsInput(const TouchInput &input) const; + + size_t touchDeviceId() const; + size_t fingerId() const; + + float speedX() const; + float speedY() const; + + bool isMoving() const; + float gestureDistance() const; + double gestureTime() const; + + size_t numInputs() const; + const TouchInput& latestInput() const; + const std::deque& peekInputs() const; + +private: + //A deque of recorded inputs. Adding newer points to the front of the queue + std::deque _inputs; + + size_t _touchDeviceId; + size_t _fingerId; +}; + +} // namespace openspace + +#endif // __OPENSPACE_CORE___TOUCH___H__ diff --git a/modules/touch/include/directinputsolver.h b/modules/touch/include/directinputsolver.h index 50f5cbeceb..bc866ba2dd 100644 --- a/modules/touch/include/directinputsolver.h +++ b/modules/touch/include/directinputsolver.h @@ -25,8 +25,8 @@ #ifndef __OPENSPACE_MODULE_TOUCH___DIRECTINPUT_SOLVER___H__ #define __OPENSPACE_MODULE_TOUCH___DIRECTINPUT_SOLVER___H__ +#include #include -#include #include @@ -35,23 +35,35 @@ namespace openspace { class Camera; class SceneGraphNode; +/** + * The DirectInputSolver is used to minimize the L2 error of touch input + * to 3D camera position. It uses the levmarq algorithm in order to do this. + * */ class DirectInputSolver { public: // Stores the selected node, the cursor ID as well as the surface coordinates the // cursor touched struct SelectedBody { - long id; + size_t id; SceneGraphNode* node; glm::dvec3 coordinates; }; DirectInputSolver(); - bool solve(const std::vector& list, + + /** + * Returns true if the error could be minimized within certain bounds. + * If the error is found to be outside the bounds after a certain amount of + * iterations, this function fails. + * */ + bool solve(const std::vector& list, const std::vector& selectedBodies, std::vector* calculatedValues, const Camera& camera); - int getNDof() const; - const LMstat& getLevMarqStat(); + int nDof() const; + + const LMstat& levMarqStat(); + void setLevMarqVerbosity(bool verbose); private: diff --git a/modules/touch/include/touchinteraction.h b/modules/touch/include/touchinteraction.h index 7158973b70..a0f025d90b 100644 --- a/modules/touch/include/touchinteraction.h +++ b/modules/touch/include/touchinteraction.h @@ -28,8 +28,6 @@ #include #include -#include - #include #include #include @@ -37,7 +35,7 @@ #include #include #include - +#include #include //#define TOUCH_DEBUG_PROPERTIES @@ -66,8 +64,6 @@ private: class TouchInteraction : public properties::PropertyOwner { public: - using Point = std::pair; - TouchInteraction(); // for interpretInteraction() @@ -95,15 +91,13 @@ public: * 8 Evaluate if directControl should be called next frame- true if all contact points * select the same node and said node is larger than _nodeRadiusThreshold */ - void updateStateFromInput(const std::vector& list, - std::vector& lastProcessed); + + void updateStateFromInput(const std::vector& list, + std::vector& lastProcessed); // Calculates the new camera state with velocities and time since last frame void step(double dt); - // Used to save LMA data for one frame if the user chose to - void unitTest(); - // Called each frame we have no new input, used to reset data void resetAfterInput(); @@ -120,37 +114,32 @@ public: void setCamera(Camera* camera); private: - /* Returns true if the clicked position contains WebGui content and the event will - * be parsed to the webbrowser - */ - bool webContent(const std::vector& list); - /* Returns true if we have the GUI window open. If so, emulates the incoming touch * input to a mouse such that we can interact with the GUI */ - bool guiMode(const std::vector& list); + bool isGuiMode(glm::dvec2 screenPosition, size_t numFingers); /* Function that calculates the new camera state such that it minimizes the L2 error * in screenspace * between contact points and surface coordinates projected to clip space using LMA */ - void directControl(const std::vector& list); + void directControl(const std::vector& list); /* Traces each contact point into the scene as a ray * if the ray hits a node, save the id, node and surface coordinates the cursor hit * in the list _selected */ - void findSelectedNode(const std::vector& list); + void findSelectedNode(const std::vector& list); /* Returns an int (ROT = 0, PINCH, PAN, ROLL, PICK) for what interaction to be used, * depending on what input was gotten */ - int interpretInteraction(const std::vector& list, - const std::vector& lastProcessed); + int interpretInteraction(const std::vector& list, + const std::vector& lastProcessed); // Compute new velocity according to the interpreted action - void computeVelocities(const std::vector& list, - const std::vector& lastProcessed); + void computeVelocities(const std::vector& list, + const std::vector& lastProcessed); //Compute velocity based on double-tap for zooming double computeTapZoomDistance(double zoomGain); @@ -158,11 +147,6 @@ private: //Compute coefficient for velocity decay to be applied in decceleration double computeConstTimeDecayCoefficient(double velocity); - //Compute coefficient of decay based on current frametime; if frametime has been - // longer than usual then multiple decay steps may be applied to keep the decay - // relative to user time - double computeDecayCoeffFromFrametime(double coeff, int times); - /* Decelerate the velocities. Function is called in step() but is dereferenced from * frame time to assure same behaviour on all systems */ @@ -224,25 +208,25 @@ private: VelocityStates _lastVel; VelocityStates _sensitivity; - double _projectionScaleFactor; - double _currentRadius; - double _slerpdT; - double _timeSlack; - int _numOfTests; - TUIO::TuioTime _time; - bool _directTouchMode; - bool _wasPrevModeDirectTouch; - bool _tap; - bool _doubleTap; - bool _zoomOutTap; - bool _lmSuccess; - bool _guiON; + double _projectionScaleFactor = 1.000004; + double _currentRadius = 1.0; + double _slerpdT = 10001.0; + double _timeSlack = 0.0; + int _numOfTests = 0; + std::chrono::milliseconds _time; + bool _directTouchMode = false; + bool _wasPrevModeDirectTouch = false; + bool _tap = false; + bool _doubleTap = false; + bool _zoomOutTap = false; + bool _lmSuccess = true; + bool _guiON = false; std::vector _selected; SceneGraphNode* _pickingSelected = nullptr; DirectInputSolver _solver; glm::dquat _toSlerp; - glm::dvec3 _centroid; + glm::vec2 _centroid = glm::vec2(0.f); FrameTimeAverage _frameTimeAvg; diff --git a/modules/touch/include/touchmarker.h b/modules/touch/include/touchmarker.h index ab2488b830..c11ea7f65d 100644 --- a/modules/touch/include/touchmarker.h +++ b/modules/touch/include/touchmarker.h @@ -25,9 +25,6 @@ #ifndef __OPENSPACE_MODULE_TOUCH___TOUCH_MARKER___H__ #define __OPENSPACE_MODULE_TOUCH___TOUCH_MARKER___H__ -#include - -#include #include #include #include @@ -35,10 +32,10 @@ #include #include #include - +#include #include +#include #include - #include #include @@ -54,10 +51,10 @@ public: void initialize(); void deinitialize(); - void render(const std::vector& list); + void render(const std::vector& list); private: - void createVertexList(const std::vector& list); + void createVertexList(const std::vector& list); properties::BoolProperty _visible; properties::FloatProperty _radiusSize; diff --git a/modules/touch/include/tuioear.h b/modules/touch/include/tuioear.h index a05296ba89..25c98edbf8 100644 --- a/modules/touch/include/tuioear.h +++ b/modules/touch/include/tuioear.h @@ -38,8 +38,6 @@ #include #include -#include -#include #if (defined(__GNUC__) && !defined(__clang__)) #pragma GCC diagnostic pop @@ -48,72 +46,52 @@ #pragma clang diagnostic pop #endif // __clang__ +#include #include - +#include #include -#include #include #include -#include +#include +namespace openspace { class TuioEar : public TUIO::TuioListener { - public: - TuioEar(); - ~TuioEar() { - _tuioClient.disconnect(); - } +public: + TuioEar(); + ~TuioEar(); - /** - * Callback functions, listens to the TUIO server + /** + * Callback functions, listens to the TUIO server + */ + void addTuioObject(TUIO::TuioObject *tobj); + void updateTuioObject(TUIO::TuioObject *tobj); + void removeTuioObject(TUIO::TuioObject *tobj); + + void addTuioCursor(TUIO::TuioCursor *tcur); + void updateTuioCursor(TUIO::TuioCursor *tcur); + void removeTuioCursor(TUIO::TuioCursor *tcur); + + void addTuioBlob(TUIO::TuioBlob *tblb); + void updateTuioBlob(TUIO::TuioBlob *tblb); + void removeTuioBlob(TUIO::TuioBlob *tblb); + + void refresh(TUIO::TuioTime frameTime); + + /** + * Lock-swap the containers of this listener */ - void addTuioObject(TUIO::TuioObject *tobj); - void updateTuioObject(TUIO::TuioObject *tobj); - void removeTuioObject(TUIO::TuioObject *tobj); + std::vector takeInput(); + std::vector takeRemovals(); - void addTuioCursor(TUIO::TuioCursor *tcur); - void updateTuioCursor(TUIO::TuioCursor *tcur); - void removeTuioCursor(TUIO::TuioCursor *tcur); +private: + TUIO::TuioClient _tuioClient; - void addTuioBlob(TUIO::TuioBlob *tblb); - void updateTuioBlob(TUIO::TuioBlob *tblb); - void removeTuioBlob(TUIO::TuioBlob *tblb); - - void refresh(TUIO::TuioTime frameTime); - - /** - * Returns a list of all touch history that happened since the last frame - */ - std::vector getInput(); - - /** - * Returns true if a tap occured since the last frame - */ - bool tap(); - - /** - * Returns tap's cursor coordinates and time information - */ - TUIO::TuioCursor getTap(); - - /** - * Clears the input list, function called after getInput() each frame - */ - void clearInput(); - - private: - bool _tap = false; - TUIO::TuioCursor _tapCo = TUIO::TuioCursor(-1, -1, -1.0f, -1.0f); - std::mutex _mx; - - TUIO::TuioClient _tuioClient; - - std::vector _list; - - /** - * A list that tracks all of the cursor ID's that got removed since last frame - */ - std::vector _removeList; + std::vector _inputList; + std::vector _removalList; + std::mutex _mx; }; +} // namespace openspace + #endif // __OPENSPACE_MODULE_TOUCH___TUIO_EAR___H__ diff --git a/modules/touch/src/directinputsolver.cpp b/modules/touch/src/directinputsolver.cpp index 6ceed7dac4..46701af1da 100644 --- a/modules/touch/src/directinputsolver.cpp +++ b/modules/touch/src/directinputsolver.cpp @@ -37,7 +37,7 @@ namespace { openspace::SceneGraphNode* node; LMstat stats; }; -} +} // namespace namespace openspace { @@ -45,7 +45,8 @@ DirectInputSolver::DirectInputSolver() { levmarq_init(&_lmstat); } -// project back a 3D point in model view to clip space [-1,1] coordinates on the view plane +// project back a 3D point in model view to clip space [-1,1] coordinates on the view +// plane glm::dvec2 castToNDC(const glm::dvec3& vec, Camera& camera, SceneGraphNode* node) { glm::dvec3 posInCamSpace = glm::inverse(camera.rotationQuaternion()) * (node->worldRotationMatrix() * vec + @@ -82,20 +83,24 @@ double distToMinimize(double* par, int x, void* fdata, LMstat* lmstat) { dvec3(0, 0, 0), directionToCenter, // To avoid problem with lookup in up direction - normalize(camDirection + lookUp)); + normalize(camDirection + lookUp) + ); dquat globalCamRot = normalize(quat_cast(inverse(lookAtMat))); dquat localCamRot = inverse(globalCamRot) * ptr->camera->rotationQuaternion(); - { // Roll + { + // Roll dquat rollRot = angleAxis(q[3], dvec3(0.0, 0.0, 1.0)); localCamRot = localCamRot * rollRot; } - { // Panning (local rotation) + { + // Panning (local rotation) dvec3 eulerAngles(q[5], q[4], 0); dquat panRot = dquat(eulerAngles); localCamRot = localCamRot * panRot; } - { // Orbit (global rotation) + { + // Orbit (global rotation) dvec3 eulerAngles(q[1], q[0], 0); dquat rotationDiffCamSpace = dquat(eulerAngles); @@ -169,15 +174,17 @@ void gradient(double* g, double* par, int x, void* fdata, LMstat* lmstat) { } // calculate f1 with good h for finite difference - dPar.at(i) += h; + dPar[i] += h; f1 = distToMinimize(dPar.data(), x, fdata, lmstat); - dPar.at(i) = par[i]; + dPar[i] = par[i]; break; } - else if ((f1 - f0) != 0 && lastG != 0) { // h too big + else if ((f1 - f0) != 0 && lastG != 0) { + // h too big h /= scale; } - else if ((f1 - f0) == 0) { // h too small + else if ((f1 - f0) == 0) { + // h too small h *= scale; } lastG = f1 - f0; @@ -201,9 +208,9 @@ void gradient(double* g, double* par, int x, void* fdata, LMstat* lmstat) { } } -bool DirectInputSolver::solve(const std::vector& list, +bool DirectInputSolver::solve(const std::vector& list, const std::vector& selectedBodies, - std::vector* parameters, const Camera& camera) + std::vector* parameters, const Camera& camera) { int nFingers = std::min(static_cast(list.size()), 3); _nDof = std::min(nFingers * 2, 6); @@ -216,29 +223,9 @@ bool DirectInputSolver::solve(const std::vector& list, const SelectedBody& sb = selectedBodies.at(i); selectedPoints.push_back(sb.coordinates); screenPoints.emplace_back( - 2 * (list[i].getX() - 0.5), - -2 * (list[i].getY() - 0.5) + 2.0 * (list[i].latestInput().x - 0.5), + -2.0 * (list[i].latestInput().y - 0.5) ); - - // This might be needed when we're directing the touchtable from another screen? - // std::vector::const_iterator c = std::find_if( - // list.begin(), - // list.end(), - // [&sb](const TuioCursor& c) { return c.getSessionID() == sb.id; } - // ); - // if (c != list.end()) { - // // normalized -1 to 1 coordinates on screen - // screenPoints.emplace_back(2 * (c->getX() - 0.5), -2 * (c->getY() - 0.5)); - // } - // else { - // global::moduleEngine.module()->touchInput = { - // true, - // glm::dvec2(0.0, 0.0), - // 1 - // }; - // resetAfterInput(); - // return; - // } } FunctionData fData = { @@ -265,11 +252,11 @@ bool DirectInputSolver::solve(const std::vector& list, return result; } -int DirectInputSolver::getNDof() const { +int DirectInputSolver::nDof() const { return _nDof; } -const LMstat& DirectInputSolver::getLevMarqStat() { +const LMstat& DirectInputSolver::levMarqStat() { return _lmstat; } diff --git a/modules/touch/src/touchinteraction.cpp b/modules/touch/src/touchinteraction.cpp index eab3fa0185..36d30b6154 100644 --- a/modules/touch/src/touchinteraction.cpp +++ b/modules/touch/src/touchinteraction.cpp @@ -24,39 +24,35 @@ #include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - #ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED #include #include #endif -#ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED -#include -#endif - -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include #ifdef WIN32 #pragma warning (push) @@ -69,10 +65,6 @@ #pragma warning (pop) #endif // WIN32 -#include -#include -#include - namespace { constexpr const char* _loggerCat = "TouchInteraction"; @@ -246,9 +238,19 @@ namespace { "Its purpose is to limit zooming in on a node. If this value is not set it " "defaults to the surface of the current anchor. " }; -} // namespace -using namespace TUIO; + // Compute coefficient of decay based on current frametime; if frametime has been + // longer than usual then multiple decay steps may be applied to keep the decay + // relative to user time + double computeDecayCoeffFromFrametime(double coeff, int times) { + if (coeff > 0.00001) { + return std::pow(coeff, times); + } + else { + return 0.0; + } + } +} // namespace namespace openspace { @@ -310,22 +312,6 @@ TouchInteraction::TouchInteraction() , _constTimeDecay_secs(ConstantTimeDecaySecsInfo, 1.75f, 0.1f, 4.0f) // calculated with two vectors with known diff in length, then // projDiffLength/diffLength. - , _projectionScaleFactor(1.000004) - , _currentRadius(1.0) - , _slerpdT(1000) - , _timeSlack(0.0) - , _numOfTests(0) - , _directTouchMode(false) - , _wasPrevModeDirectTouch(false) - , _tap(false) - , _doubleTap(false) - , _zoomOutTap(false) - , _lmSuccess(true) - , _guiON(false) -#ifdef TOUCH_DEBUG_PROPERTIES - , _debugProperties() -#endif - , _centroid(glm::dvec3(0.0)) { addProperty(_touchActive); addProperty(_unitTest); @@ -370,41 +356,42 @@ TouchInteraction::TouchInteraction() )); } }); - - _time.initSession(); + _time = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch() + ); } -// Called each frame if there is any input -void TouchInteraction::updateStateFromInput(const std::vector& list, - std::vector& lastProcessed) + +void TouchInteraction::updateStateFromInput(const std::vector& list, + std::vector& lastProcessed) { #ifdef TOUCH_DEBUG_PROPERTIES _debugProperties.nFingers = list.size(); #endif - if (_tap) { // check for doubletap - if (_time.getSessionTime().getTotalMilliseconds() < _maxTapTime) { + if (_tap) { + // check for doubletap + using namespace std::chrono; + milliseconds timestamp = duration_cast( + high_resolution_clock::now().time_since_epoch() + ); + if ((timestamp - _time).count() < _maxTapTime) { _doubleTap = true; _tap = false; } - _time.initSession(); + _time = timestamp; } + // Code for lower-right corner double-tap to zoom-out + const glm::vec2 res = global::windowDelegate.currentWindowSize(); + const glm::vec2 pos = list[0].latestInput().screenCoordinates(res); - bool hasWebContent = webContent(list); - - //Code for lower-right corner double-tap to zoom-out - glm::ivec2 res = global::windowDelegate.currentWindowSize(); - glm::dvec2 pos = glm::vec2( - list.at(0).getScreenX(res.x), - list.at(0).getScreenY(res.y) - ); const float bottomCornerSizeForZoomTap_fraction = 0.08f; - int zoomTapThresholdX = static_cast( - res.x * (1.0f - bottomCornerSizeForZoomTap_fraction) + const int zoomTapThresholdX = static_cast( + res.x * (1.f - bottomCornerSizeForZoomTap_fraction) ); - int zoomTapThresholdY = static_cast( - res.y * (1.0f - bottomCornerSizeForZoomTap_fraction) + const int zoomTapThresholdY = static_cast( + res.y * (1.f - bottomCornerSizeForZoomTap_fraction) ); - bool isTapInLowerRightCorner = + const bool isTapInLowerRightCorner = (std::abs(pos.x) > zoomTapThresholdX && std::abs(pos.y) > zoomTapThresholdY); if (_doubleTap && isTapInLowerRightCorner) { @@ -413,22 +400,18 @@ void TouchInteraction::updateStateFromInput(const std::vector& list, _doubleTap = false; } - if (!guiMode(list) && !hasWebContent) { - bool isThisFrameTransitionBetweenTouchModes - = (_wasPrevModeDirectTouch != _directTouchMode); - if (isThisFrameTransitionBetweenTouchModes) { + size_t numFingers = list.size(); + if (!isGuiMode(pos, numFingers)) { + bool isTransitionBetweenModes = (_wasPrevModeDirectTouch != _directTouchMode); + if (isTransitionBetweenModes) { _vel.orbit = glm::dvec2(0.0, 0.0); _vel.zoom = 0.0; _vel.roll = 0.0; _vel.pan = glm::dvec2(0.0, 0.0); resetAfterInput(); - /*if( _directTouchMode ) - LINFO("Touch -> Direct-touch"); - else - LINFO("Direct-touch -> Touch");*/ } - if (_directTouchMode && _selected.size() > 0 && list.size() == _selected.size()) { + if (_directTouchMode && _selected.size() > 0 && numFingers == _selected.size()) { #ifdef TOUCH_DEBUG_PROPERTIES _debugProperties.interactionMode = "Direct"; #endif @@ -448,41 +431,21 @@ void TouchInteraction::updateStateFromInput(const std::vector& list, _wasPrevModeDirectTouch = _directTouchMode; // evaluates if current frame is in directTouchMode (will be used next frame) _directTouchMode = - (_currentRadius > _nodeRadiusThreshold && _selected.size() == list.size()); + (_currentRadius > _nodeRadiusThreshold && _selected.size() == numFingers); } } -bool TouchInteraction::webContent(const std::vector& list) { -#ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED - glm::ivec2 res = global::windowDelegate.currentWindowSize(); - glm::dvec2 pos = glm::vec2( - list.at(0).getScreenX(res.x), - list.at(0).getScreenY(res.y) - ); - - WebBrowserModule& module = *(global::moduleEngine.module()); - return module.eventHandler().hasContentCallback(pos.x, pos.y); -#else - return false; -#endif -} - -// Activates/Deactivates gui input mode (if active it voids all other interactions) -bool TouchInteraction::guiMode(const std::vector& list) { +bool TouchInteraction::isGuiMode(glm::dvec2 screenPosition, size_t numFingers) { if (_ignoreGui) { return false; } - glm::ivec2 res = global::windowDelegate.currentWindowSize(); - glm::dvec2 pos = glm::vec2( - list.at(0).getScreenX(res.x), - list.at(0).getScreenY(res.y) - ); ImGUIModule& module = *(global::moduleEngine.module()); _guiON = module.gui.isEnabled(); - if (_tap && list.size() == 1 && - std::abs(pos.x) < _guiButton.value().x && std::abs(pos.y) < _guiButton.value().y) + if (_tap && numFingers == 1 && + std::abs(screenPosition.x) < _guiButton.value().x && + std::abs(screenPosition.y) < _guiButton.value().y) { // pressed invisible button _guiON = !_guiON; @@ -491,19 +454,19 @@ bool TouchInteraction::guiMode(const std::vector& list) { LINFO(fmt::format( "GUI mode is {}. Inside box by: ({}%, {}%)", _guiON ? "activated" : "deactivated", - static_cast(100 * (pos.x / _guiButton.value().x)), - static_cast(100 * (pos.y / _guiButton.value().y)) + static_cast(100 * (screenPosition.x / _guiButton.value().x)), + static_cast(100 * (screenPosition.y / _guiButton.value().y)) )); } else if (_guiON) { - module.touchInput = { _guiON, pos, 1 }; // emulate touch input as a mouse + // emulate touch input as a mouse + module.touchInput = { _guiON, screenPosition, 1 }; } return _guiON; } -// Sets _vel to update _camera according to direct-manipulation (L2 error) -void TouchInteraction::directControl(const std::vector& list) { +void TouchInteraction::directControl(const std::vector& list) { // Reset old velocities upon new interaction _vel.orbit = glm::dvec2(0.0, 0.0); _vel.zoom = 0.0; @@ -515,10 +478,10 @@ void TouchInteraction::directControl(const std::vector& list) { // finds best transform values for the new camera state and stores them in par std::vector par(6, 0.0); - par.at(0) = _lastVel.orbit.x; // use _lastVel for orbit - par.at(1) = _lastVel.orbit.y; + par[0] = _lastVel.orbit.x; // use _lastVel for orbit + par[1] = _lastVel.orbit.y; _lmSuccess = _solver.solve(list, _selected, &par, *_camera); - int nDof = _solver.getNDof(); + int nDof = _solver.nDof(); if (_lmSuccess && !_unitTest) { // if good values were found set new camera state @@ -543,19 +506,17 @@ void TouchInteraction::directControl(const std::vector& list) { else { // prevents touch to infinitely be active (due to windows bridge case where event // doesnt get consumed sometimes when LMA fails to converge) - global::moduleEngine.module()->touchInput = { - true, - glm::dvec2(0.0, 0.0), - 1 - }; + Touch touch; + touch.active = true; + touch.pos = glm::dvec2(0.0, 0.0); + touch.action = 1; + global::moduleEngine.module()->touchInput = touch; resetAfterInput(); } } -// Traces the touch input into the scene and finds the surface coordinates of touched -// planets (if occuring) -void TouchInteraction::findSelectedNode(const std::vector& list) { - //trim list to only contain visible nodes that make sense +void TouchInteraction::findSelectedNode(const std::vector& list) { + // trim list to only contain visible nodes that make sense std::string selectables[30] = { "Sun", "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "Moon", "Titan", "Rhea", "Mimas", "Iapetus", "Enceladus", @@ -574,30 +535,31 @@ void TouchInteraction::findSelectedNode(const std::vector& list) { glm::dquat camToWorldSpace = _camera->rotationQuaternion(); glm::dvec3 camPos = _camera->positionVec3(); std::vector newSelected; - - //node & distance - std::tuple currentlyPicked = { + + // node & distance + std::pair currentlyPicked = { nullptr, std::numeric_limits::max() }; - - for (const TuioCursor& c : list) { - double xCo = 2 * (c.getX() - 0.5); - double yCo = -2 * (c.getY() - 0.5); // normalized -1 to 1 coordinates on screen + + for (const TouchInputHolder& inputHolder : list) { + // normalized -1 to 1 coordinates on screen + double xCo = 2 * (inputHolder.latestInput().x - 0.5); + double yCo = -2 * (inputHolder.latestInput().y - 0.5); glm::dvec3 cursorInWorldSpace = camToWorldSpace * glm::dvec3(glm::inverse(_camera->projectionMatrix()) * glm::dvec4(xCo, yCo, -1.0, 1.0)); glm::dvec3 raytrace = glm::normalize(cursorInWorldSpace); - long id = c.getSessionID(); + size_t id = inputHolder.fingerId(); for (SceneGraphNode* node : selectableNodes) { double boundingSphereSquared = static_cast(node->boundingSphere()) * static_cast(node->boundingSphere()); glm::dvec3 camToSelectable = node->worldPosition() - camPos; double intersectionDist = 0.0; - bool intersected = glm::intersectRaySphere( + const bool intersected = glm::intersectRaySphere( camPos, raytrace, node->worldPosition(), @@ -616,7 +578,7 @@ void TouchInteraction::findSelectedNode(const std::vector& list) { [id](const DirectInputSolver::SelectedBody& s) { return s.id == id; } ); if (oldNode != newSelected.end()) { - double oldNodeDist = glm::length( + const double oldNodeDist = glm::length( oldNode->node->worldPosition() - camPos ); if (glm::length(camToSelectable) < oldNodeDist) { @@ -637,11 +599,12 @@ void TouchInteraction::findSelectedNode(const std::vector& list) { glm::vec4(node->worldPosition(), 1.0); glm::dvec2 ndc = clip / clip.w; - // If the object is not in the screen, we dont want to consider it at all - if (ndc.x >= -1.0 && ndc.x <= 1.0 && ndc.y >= -1.0 && ndc.y <= 1.0) { + const bool isVisibleX = (ndc.x >= -1.0 && ndc.x <= 1.0); + const bool isVisibleY = (ndc.y >= -1.0 && ndc.y <= 1.0); + if (isVisibleX && isVisibleY) { glm::dvec2 cursor = { xCo, yCo }; - double ndcDist = glm::length(ndc - cursor); + const double ndcDist = glm::length(ndc - cursor); // We either want to select the object if it's bounding sphere as been // touched (checked by the first part of this loop above) or if the touch // point is within a minimum distance of the center @@ -668,12 +631,9 @@ void TouchInteraction::findSelectedNode(const std::vector& list) { "Picking candidate based on proximity" ); #endif //#ifdef TOUCH_DEBUG_NODE_PICK_MESSAGES - double dist = length(camToSelectable); - if (dist < std::get<1>(currentlyPicked)) { - currentlyPicked = { - node, - dist - }; + const double dist = length(camToSelectable); + if (dist < currentlyPicked.second) { + currentlyPicked = std::make_pair(node, dist); } } } @@ -681,7 +641,7 @@ void TouchInteraction::findSelectedNode(const std::vector& list) { } // If an item has been picked, it's in the first position of the vector now - if (SceneGraphNode* node = std::get<0>(currentlyPicked)) { + if (SceneGraphNode* node = currentlyPicked.first) { _pickingSelected = node; #ifdef TOUCH_DEBUG_NODE_PICK_MESSAGES LINFOC("Picking", "Picked node: " + _pickingSelected->identifier()); @@ -691,65 +651,60 @@ void TouchInteraction::findSelectedNode(const std::vector& list) { _selected = std::move(newSelected); } -// Interprets the input gesture to a specific interaction -int TouchInteraction::interpretInteraction(const std::vector& list, - const std::vector& lastProcessed) +int TouchInteraction::interpretInteraction(const std::vector& list, + const std::vector& lastProcessed) { - glm::dvec3 lastCentroid = _centroid; - _centroid.x = std::accumulate( - list.begin(), - list.end(), - 0.0, - [](double x, const TuioCursor& c) { return x + c.getX(); } - ) / list.size(); - _centroid.y = std::accumulate( - list.begin(), - list.end(), - 0.0, - [](double y, const TuioCursor& c) { return y + c.getY(); } - ) / list.size(); + glm::fvec2 lastCentroid = _centroid; + _centroid = { 0.f, 0.f }; + for (const TouchInputHolder& inputHolder : list) { + _centroid += glm::vec2( + inputHolder.latestInput().x, + inputHolder.latestInput().y + ); + } + _centroid /= static_cast(list.size()); // see if the distance between fingers changed - used in pan interpretation double dist = 0; double lastDist = 0; - TuioCursor cursor = list.at(0); - for (const TuioCursor& c : list) { + TouchInput distInput = list[0].latestInput(); + for (const TouchInputHolder& inputHolder : list) { + const TouchInput& latestInput = inputHolder.latestInput(); dist += glm::length( - glm::dvec2(c.getX(), c.getY()) - glm::dvec2(cursor.getX(), cursor.getY()) + glm::dvec2(latestInput.x, latestInput.y) - + glm::dvec2(distInput.x, distInput.y) ); - cursor = c; + distInput = latestInput; } - TuioPoint point = lastProcessed.at(0).second; - for (const Point& p : lastProcessed) { - lastDist += glm::length(glm::dvec2(p.second.getX(), p.second.getY()) - - glm::dvec2(point.getX(), point.getY())); - point = p.second; + distInput = lastProcessed[0]; + for (const TouchInput& p : lastProcessed) { + lastDist += glm::length(glm::dvec2(p.x, p.y) - + glm::dvec2(distInput.x, distInput.y)); + distInput = p; } // find the slowest moving finger - used in roll interpretation double minDiff = 1000; - long id = 0; - for (const TuioCursor& c : list) { - auto it = std::find_if( - lastProcessed.begin(), - lastProcessed.end(), - [&c](const Point& p) { - return p.first == c.getSessionID(); + for (const TouchInputHolder& inputHolder : list) { + const auto it = std::find_if( + lastProcessed.cbegin(), + lastProcessed.cend(), + [&inputHolder](const TouchInput& input) { + return inputHolder.holdsInput(input); }); - if (it == lastProcessed.end()) { + if (it == lastProcessed.cend()) { continue; } + const TouchInput& latestInput = inputHolder.latestInput(); + const TouchInput& prevInput = *it; - TuioPoint itPoint = it->second; - double diff = c.getX() - itPoint.getX() + c.getY() - itPoint.getY(); + double diff = latestInput.x - prevInput.x + latestInput.y - prevInput.y; - if (!c.isMoving()) { - diff = minDiff = 0.0; - id = c.getSessionID(); + if (!inputHolder.isMoving()) { + minDiff = 0.0; } else if (std::abs(diff) < std::abs(minDiff)) { minDiff = diff; - id = c.getSessionID(); } } // find if all fingers angles are high - used in roll interpretation @@ -757,26 +712,22 @@ int TouchInteraction::interpretInteraction(const std::vector& list, list.begin(), list.end(), 0.0, - [&](double diff, const TuioCursor& c) { - TuioPoint point = std::find_if( + [&](double diff, const TouchInputHolder& inputHolder) { + const TouchInput& lastPoint = *std::find_if( lastProcessed.begin(), lastProcessed.end(), - [&c](const Point& p) { return p.first == c.getSessionID(); } - )->second; + [&inputHolder](const TouchInput& input) { + return inputHolder.holdsInput(input); + }); double res = 0.0; - float lastAngle = point.getAngle( - static_cast(_centroid.x), - static_cast(_centroid.y) - ); - float currentAngle = c.getAngle( - static_cast(_centroid.x), - static_cast(_centroid.y) - ); - if (lastAngle > currentAngle + 1.5 * M_PI) { - res = currentAngle + (2 * M_PI - lastAngle); + + float lastAngle = lastPoint.angleToPos(_centroid.x, _centroid.y); + float currentAngle = inputHolder.latestInput().angleToPos(_centroid.x, _centroid.y); + if (lastAngle > currentAngle + 1.5 * glm::pi()) { + res = currentAngle + (2.0 * glm::pi() - lastAngle); } - else if (currentAngle > lastAngle + 1.5 * M_PI) { - res = (2 * M_PI - currentAngle) + lastAngle; + else if (currentAngle > lastAngle + 1.5 * glm::pi()) { + res = (2.0 * glm::pi() - currentAngle) + lastAngle; } else { res = currentAngle - lastAngle; @@ -810,9 +761,7 @@ int TouchInteraction::interpretInteraction(const std::vector& list, return ROT; } else { - float avgDistance = static_cast( - std::abs(dist - lastDist) / list.at(0).getMotionSpeed() - ); + float avgDistance = static_cast(std::abs(dist - lastDist)); // if average distance between 3 fingers are constant we have panning if (_panEnabled && (avgDistance < _interpretPan && list.size() == 3)) { return PAN; @@ -833,11 +782,9 @@ int TouchInteraction::interpretInteraction(const std::vector& list, } } -// Calculate how much interpreted interaction (_vel) should change the camera state -void TouchInteraction::computeVelocities(const std::vector& list, - const std::vector& lastProcessed) +void TouchInteraction::computeVelocities(const std::vector& list, + const std::vector& lastProcessed) { - const TuioCursor& cursor = list.at(0); const int action = interpretInteraction(list, lastProcessed); const SceneGraphNode* anchor = global::navigationHandler.orbitalNavigator().anchorNode(); @@ -868,14 +815,16 @@ void TouchInteraction::computeVelocities(const std::vector& list, } #endif + const TouchInputHolder& inputHolder = list.at(0); switch (action) { case ROT: { // add rotation velocity - _vel.orbit += glm::dvec2(cursor.getXSpeed() * - _sensitivity.orbit.x, cursor.getYSpeed() * + _vel.orbit += glm::dvec2(inputHolder.speedX() * + _sensitivity.orbit.x, inputHolder.speedY() * _sensitivity.orbit.y); - double orbitVelocityAvg = glm::distance(_vel.orbit.x, _vel.orbit.y); - _constTimeDecayCoeff.orbit - = computeConstTimeDecayCoefficient(orbitVelocityAvg); + const double orbitVelocityAvg = glm::distance(_vel.orbit.x, _vel.orbit.y); + _constTimeDecayCoeff.orbit = computeConstTimeDecayCoefficient( + orbitVelocityAvg + ); break; } case PINCH: { @@ -885,22 +834,18 @@ void TouchInteraction::computeVelocities(const std::vector& list, list.begin(), list.end(), 0.0, - [&](double d, const TuioCursor& c) { - return d + c.getDistance( - static_cast(_centroid.x), - static_cast(_centroid.y) - ); + [&](double d, const TouchInputHolder& c) { + const glm::vec2 currPos = { c.latestInput().x, c.latestInput().y }; + return d + glm::distance(currPos, _centroid); } ) / list.size(); double lastDistance = std::accumulate( lastProcessed.begin(), lastProcessed.end(), - 0.0f, - [&](float d, const Point& p) { - return d + p.second.getDistance( - static_cast(_centroid.x), - static_cast(_centroid.y) - ); + 0.f, + [&](float d, const TouchInput& p) { + const glm::vec2 lastPos = { p.x, p.y }; + return d + glm::distance(lastPos, _centroid); } ) / lastProcessed.size(); @@ -908,7 +853,7 @@ void TouchInteraction::computeVelocities(const std::vector& list, glm::dvec3 centerPos = anchor->worldPosition(); glm::dvec3 currDistanceToFocusNode = camPos - centerPos; - double distanceFromFocusSurface = + const double distanceFromFocusSurface = length(currDistanceToFocusNode) - anchor->boundingSphere(); double zoomFactor = (distance - lastDistance); #ifdef TOUCH_DEBUG_PROPERTIES @@ -918,11 +863,11 @@ void TouchInteraction::computeVelocities(const std::vector& list, _constTimeDecayCoeff.zoom = computeConstTimeDecayCoefficient(_vel.zoom); if (distanceFromFocusSurface > 0.1) { - double ratioOfDistanceToNodeVsSurface = + const double ratioOfDistanceToNodeVsSurface = length(currDistanceToFocusNode) / distanceFromFocusSurface; if (ratioOfDistanceToNodeVsSurface > _zoomSensitivityDistanceThreshold) { zoomFactor *= pow( - std::abs(distanceFromFocusSurface), + std::abs(distanceFromFocusSurface), static_cast(_zoomSensitivityExponential) ); } @@ -940,27 +885,26 @@ void TouchInteraction::computeVelocities(const std::vector& list, list.begin(), list.end(), 0.0, - [&](double diff, const TuioCursor& c) { - TuioPoint point = std::find_if( + [&](double diff, const TouchInputHolder& inputHolder) { + TouchInput point = *std::find_if( lastProcessed.begin(), lastProcessed.end(), - [&c](const Point& p) { return p.first == c.getSessionID(); } - )->second; - double res = diff; - double lastAngle = point.getAngle( - static_cast(_centroid.x), - static_cast(_centroid.y) + [&inputHolder](const TouchInput& input) { + return inputHolder.holdsInput(input); + } ); - double currentAngle = c.getAngle( - static_cast(_centroid.x), - static_cast(_centroid.y) + double res = diff; + float lastAngle = point.angleToPos(_centroid.x, _centroid.y); + float currentAngle = inputHolder.latestInput().angleToPos( + _centroid.x, + _centroid.y ); // if's used to set angles 359 + 1 = 0 and 0 - 1 = 359 - if (lastAngle > currentAngle + 1.5 * M_PI) { - res += currentAngle + (2 * M_PI - lastAngle); + if (lastAngle > currentAngle + 1.5 * glm::pi()) { + res += currentAngle + (2 * glm::pi() - lastAngle); } - else if (currentAngle > lastAngle + 1.5 * M_PI) { - res += (2 * M_PI - currentAngle) + lastAngle; + else if (currentAngle > lastAngle + 1.5 * glm::pi()) { + res += (2 * glm::pi() - currentAngle) + lastAngle; } else { res += currentAngle - lastAngle; @@ -968,15 +912,14 @@ void TouchInteraction::computeVelocities(const std::vector& list, return res; } ) / list.size(); - _vel.roll += -rollFactor * _sensitivity.roll; _constTimeDecayCoeff.roll = computeConstTimeDecayCoefficient(_vel.roll); break; } case PAN: { // add local rotation velocity - _vel.pan += glm::dvec2(cursor.getXSpeed() * - _sensitivity.pan.x, cursor.getYSpeed() * _sensitivity.pan.y); + _vel.pan += glm::dvec2(inputHolder.speedX() * + _sensitivity.pan.x, inputHolder.speedY() * _sensitivity.pan.y); double panVelocityAvg = glm::distance(_vel.pan.x, _vel.pan.y); _constTimeDecayCoeff.pan = computeConstTimeDecayCoefficient(panVelocityAvg); break; @@ -1015,8 +958,8 @@ void TouchInteraction::computeVelocities(const std::vector& list, } double TouchInteraction::computeConstTimeDecayCoefficient(double velocity) { - const double postDecayVelocityTarget = 1e-6; - double stepsToDecay = _constTimeDecay_secs / _frameTimeAvg.averageFrameTime(); + constexpr const double postDecayVelocityTarget = 1e-6; + const double stepsToDecay = _constTimeDecay_secs / _frameTimeAvg.averageFrameTime(); if (stepsToDecay > 0.0 && std::abs(velocity) > postDecayVelocityTarget) { return std::pow(postDecayVelocityTarget / std::abs(velocity), 1.0 / stepsToDecay); @@ -1061,17 +1004,17 @@ void TouchInteraction::step(double dt) { if (anchor && _camera) { // Create variables from current state dvec3 camPos = _camera->positionVec3(); - dvec3 centerPos = anchor->worldPosition(); + const dvec3 centerPos = anchor->worldPosition(); dvec3 directionToCenter = normalize(centerPos - camPos); - dvec3 centerToCamera = camPos - centerPos; - dvec3 lookUp = _camera->lookUpVectorWorldSpace(); - dvec3 camDirection = _camera->viewDirectionWorldSpace(); + const dvec3 centerToCamera = camPos - centerPos; + const dvec3 lookUp = _camera->lookUpVectorWorldSpace(); + const dvec3 camDirection = _camera->viewDirectionWorldSpace(); // Make a representation of the rotation quaternion with local and global // rotations // To avoid problem with lookup in up direction - dmat4 lookAtMat = lookAt( + const dmat4 lookAtMat = lookAt( dvec3(0, 0, 0), directionToCenter, normalize(camDirection + lookUp) @@ -1079,60 +1022,61 @@ void TouchInteraction::step(double dt) { dquat globalCamRot = normalize(quat_cast(inverse(lookAtMat))); dquat localCamRot = inverse(globalCamRot) * _camera->rotationQuaternion(); - double boundingSphere = anchor->boundingSphere(); - double distance = std::max(length(centerToCamera) - boundingSphere, 0.0); - _currentRadius = boundingSphere / - std::max(distance * _projectionScaleFactor, 1.0); + const double boundingSphere = anchor->boundingSphere(); + const double distance = std::max(length(centerToCamera) - boundingSphere, 0.0); + _currentRadius = boundingSphere / + std::max(distance * _projectionScaleFactor, 1.0); { // Roll - dquat camRollRot = angleAxis(_vel.roll * dt, dvec3(0.0, 0.0, 1.0)); + const dquat camRollRot = angleAxis(_vel.roll * dt, dvec3(0.0, 0.0, 1.0)); localCamRot = localCamRot * camRollRot; } { // Panning (local rotation) - dvec3 eulerAngles(_vel.pan.y * dt, _vel.pan.x * dt, 0); - dquat rotationDiff = dquat(eulerAngles); + const dvec3 eulerAngles(_vel.pan.y * dt, _vel.pan.x * dt, 0); + const dquat rotationDiff = dquat(eulerAngles); localCamRot = localCamRot * rotationDiff; // if we have chosen a new focus node if (_slerpdT < _slerpTime) { - _slerpdT += 0.1*dt; + _slerpdT += 0.1 * dt; localCamRot = slerp(localCamRot, _toSlerp, _slerpdT / _slerpTime); } } { // Orbit (global rotation) - dvec3 eulerAngles(_vel.orbit.y*dt, _vel.orbit.x*dt, 0); - dquat rotationDiffCamSpace = dquat(eulerAngles); + const dvec3 eulerAngles(_vel.orbit.y*dt, _vel.orbit.x*dt, 0); + const dquat rotationDiffCamSpace = dquat(eulerAngles); - dquat rotationDiffWorldSpace = globalCamRot * rotationDiffCamSpace * - inverse(globalCamRot); - dvec3 rotationDiffVec3 = centerToCamera * rotationDiffWorldSpace - - centerToCamera; + const dquat rotationDiffWorldSpace = globalCamRot * rotationDiffCamSpace * + inverse(globalCamRot); + const dvec3 rotationDiffVec3 = centerToCamera * rotationDiffWorldSpace - + centerToCamera; camPos += rotationDiffVec3; - dvec3 centerToCam = camPos - centerPos; + const dvec3 centerToCam = camPos - centerPos; directionToCenter = normalize(-centerToCam); - dvec3 lookUpWhenFacingCenter = globalCamRot * + const dvec3 lookUpWhenFacingCenter = globalCamRot * dvec3(_camera->lookUpVectorCameraSpace()); - dmat4 lookAtMatrix = lookAt( + const dmat4 lookAtMatrix = lookAt( dvec3(0, 0, 0), directionToCenter, lookUpWhenFacingCenter); globalCamRot = normalize(quat_cast(inverse(lookAtMatrix))); } - { // Zooming + { + // Zooming // This is a rough estimate of the node surface - double zoomInBounds = boundingSphere * _zoomBoundarySphereMultiplier; + const double zoomInBounds = boundingSphere * _zoomBoundarySphereMultiplier; // If nobody has set another zoom in limit, use the default zoom in bounds if (_zoomInLimit.value() < 0) { _zoomInLimit.setValue(zoomInBounds); } else if (_zoomInLimit.value() < zoomInBounds) { - // If zoom in limit is less than the estimated node radius we need to + // If zoom in limit is less than the estimated node radius we need to // make sure we do not get too close to possible height maps SurfacePositionHandle posHandle = anchor->calculateSurfacePositionHandle( camPos @@ -1142,7 +1086,7 @@ void TouchInteraction::step(double dt) { posHandle.referenceSurfaceOutDirection * posHandle.heightToSurface; glm::dvec3 centerToActualSurface = glm::dmat3(anchor->modelTransform()) * centerToActualSurfaceModelSpace; - double nodeRadius = length(centerToActualSurface); + const double nodeRadius = length(centerToActualSurface); // Because of heightmaps we should make sure we do not go through the surface if (_zoomInLimit.value() < nodeRadius) { @@ -1151,28 +1095,28 @@ void TouchInteraction::step(double dt) { "center to surface, setting it to {}", _loggerCat, zoomInBounds)); #endif _zoomInLimit.setValue(zoomInBounds); - } + } } - + // Make sure zoom in limit is not larger than zoom out limit if (_zoomInLimit.value() > _zoomOutLimit.value()) { LWARNING(fmt::format( - "{}: Zoom In Limit should be smaller than Zoom Out Limit", + "{}: Zoom In Limit should be smaller than Zoom Out Limit", _loggerCat, _zoomOutLimit.value() )); } //Apply the velocity to update camera position glm::dvec3 zoomDistanceIncrement = directionToCenter * _vel.zoom * dt; - double newPosDistance = length(centerToCamera + zoomDistanceIncrement); - double currentPosDistance = length(centerToCamera); + const double newPosDistance = length(centerToCamera + zoomDistanceIncrement); + const double currentPosDistance = length(centerToCamera); // Possible with other navigations performed outside touch interaction - bool currentPosViolatingZoomOutLimit = + const bool currentPosViolatingZoomOutLimit = (currentPosDistance >= _zoomOutLimit.value()); - bool willNewPositionViolateZoomOutLimit = + const bool willNewPositionViolateZoomOutLimit = (newPosDistance >= _zoomOutLimit.value()); - bool willNewPositionViolateZoomInLimit = + const bool willNewPositionViolateZoomInLimit = (newPosDistance < _zoomInLimit.value()); if (!willNewPositionViolateZoomInLimit && !willNewPositionViolateZoomOutLimit){ @@ -1185,8 +1129,7 @@ void TouchInteraction::step(double dt) { _loggerCat, _zoomOutLimit.value()); #endif // Only allow zooming in if you are outside the zoom out limit - if (newPosDistance < currentPosDistance) - { + if (newPosDistance < currentPosDistance) { camPos += zoomDistanceIncrement; } } @@ -1224,46 +1167,6 @@ void TouchInteraction::step(double dt) { } } -void TouchInteraction::unitTest() { - if (_unitTest) { - _solver.setLevMarqVerbosity(true); - - // set _selected pos and new pos (on screen) - std::vector lastFrame = { - { TuioCursor(0, 10, 0.45f, 0.4f) }, // session id, cursor id, x, y - { TuioCursor(1, 11, 0.55f, 0.6f) } - }; - std::vector currFrame = { - { TuioCursor(0, 10, 0.2f, 0.6f) }, // (-0.6,-0.2) - { TuioCursor(1, 11, 0.8f, 0.4f) } // (0.6, 0.2) - }; - - // call update - findSelectedNode(lastFrame); - directControl(currFrame); - - // save lmstats.data into a file and clear it - char buffer[32]; - snprintf(buffer, sizeof(char) * 32, "lmdata%i.csv", _numOfTests); - _numOfTests++; - std::ofstream file(buffer); - file << _solver.getLevMarqStat().data; - - // clear everything - _selected.clear(); - _pickingSelected = nullptr; - _vel.orbit = glm::dvec2(0.0, 0.0); - _vel.zoom = 0.0; - _vel.roll = 0.0; - _vel.pan = glm::dvec2(0.0, 0.0); - _lastVel = _vel; - _unitTest = false; - - _solver.setLevMarqVerbosity(false); - // could be the camera copy in func - } -} - // Decelerate velocities, called a set number of times per second to dereference it from // frame time // Example: @@ -1289,15 +1192,6 @@ void TouchInteraction::decelerate(double dt) { _vel.zoom *= computeDecayCoeffFromFrametime(_constTimeDecayCoeff.zoom, times); } -double TouchInteraction::computeDecayCoeffFromFrametime(double coeff, int times) { - if (coeff > 0.00001) { - return std::pow(coeff, times); - } - else { - return 0.0; - } -} - // Called if all fingers are off the screen void TouchInteraction::resetAfterInput() { #ifdef TOUCH_DEBUG_PROPERTIES @@ -1403,12 +1297,13 @@ void FrameTimeAverage::updateWithNewFrame(double sample) { } double FrameTimeAverage::averageFrameTime() const { - double ft; - if (_nSamples == 0) - ft = 1.0 / 60.0; //Just guess at 60fps if no data is available yet - else - ft = std::accumulate(_samples, _samples + _nSamples, 0.0) / (double)(_nSamples); - return ft; + if (_nSamples == 0) { + // Just guess at 60fps if no data is available yet + return 1.0 / 60.0; + } + else { + return std::accumulate(_samples, _samples + _nSamples, 0.0) / (double)(_nSamples); + } } #ifdef TOUCH_DEBUG_PROPERTIES diff --git a/modules/touch/src/touchmarker.cpp b/modules/touch/src/touchmarker.cpp index 07d15aec9a..7757286dab 100644 --- a/modules/touch/src/touchmarker.cpp +++ b/modules/touch/src/touchmarker.cpp @@ -27,9 +27,8 @@ #include #include #include -#include - #include +#include namespace { constexpr const std::array UniformNames = { @@ -63,6 +62,7 @@ namespace { constexpr openspace::properties::Property::PropertyInfo ColorInfo = { "MarkerColor", "Marker color", "" // @TODO Missing documentation }; + } // namespace namespace openspace { @@ -73,13 +73,7 @@ TouchMarker::TouchMarker() , _radiusSize(RadiusInfo, 30.f, 0.f, 100.f) , _transparency(TransparencyInfo, 0.8f, 0.f, 1.f) , _thickness(ThicknessInfo, 2.f, 0.f, 4.f ) - , _color( - ColorInfo, - glm::vec3(204.f / 255.f, 51.f / 255.f, 51.f / 255.f), - glm::vec3(0.f), - glm::vec3(1.f) - ) - , _shader(nullptr) + , _color(ColorInfo, glm::vec3(0.96f, 0.2f, 0.2f), glm::vec3(0.f), glm::vec3(1.f)) { addProperty(_visible); addProperty(_radiusSize); @@ -117,7 +111,7 @@ void TouchMarker::deinitialize() { } } -void TouchMarker::render(const std::vector& list) { +void TouchMarker::render(const std::vector& list) { if (_visible && !list.empty()) { createVertexList(list); _shader->activate(); @@ -131,7 +125,6 @@ void TouchMarker::render(const std::vector& list) { glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_PROGRAM_POINT_SIZE); // Enable gl_PointSize in vertex shader - // glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); // When included this line makes the webgui disappear at touch interaction glBindVertexArray(_quad); glDrawArrays(GL_POINTS, 0, static_cast(_vertexData.size() / 2)); @@ -139,13 +132,13 @@ void TouchMarker::render(const std::vector& list) { } } -void TouchMarker::createVertexList(const std::vector& list) { +void TouchMarker::createVertexList(const std::vector& list) { _vertexData.resize(list.size() * 2); int i = 0; - for (const TUIO::TuioCursor& c : list) { - _vertexData[i] = 2 * (c.getX() - 0.5f); - _vertexData[i + 1] = -2 * (c.getY() - 0.5f); + for (const openspace::TouchInputHolder& inputHolder : list) { + _vertexData[i] = 2 * (inputHolder.latestInput().x - 0.5f); + _vertexData[i + 1] = -2 * (inputHolder.latestInput().y - 0.5f); i += 2; } diff --git a/modules/touch/src/tuioear.cpp b/modules/touch/src/tuioear.cpp index 4a9969f241..885f069969 100644 --- a/modules/touch/src/tuioear.cpp +++ b/modules/touch/src/tuioear.cpp @@ -29,11 +29,12 @@ #include #include #include - #include using namespace TUIO; +namespace openspace { + void TuioEar::addTuioObject(TuioObject*) { } void TuioEar::updateTuioObject(TuioObject*) { } @@ -42,67 +43,41 @@ void TuioEar::removeTuioObject(TuioObject*) { } void TuioEar::addTuioCursor(TuioCursor* tcur) { _mx.lock(); - _tap = false; - // find same id in _list if it exists in _removeList (new input with same ID as a - // previously stored) - long i = tcur->getSessionID(); - std::vector::iterator foundID = std::find_if( - _removeList.begin(), - _removeList.end(), - [&i](long id) { return id == i; }); - - // if found, remove id from _removeList and update, otherwise add new id to list - if (foundID != _removeList.end()) { - std::find_if( - _list.begin(), - _list.end(), - [&i](const TuioCursor& cursor) { - return cursor.getSessionID() == i; - } - )->update(tcur); - _removeList.erase(foundID); - } - else { - _list.emplace_back(*tcur); - } + TouchInput input( + static_cast(tcur->getTuioSourceID()), + static_cast(tcur->getCursorID()), + tcur->getX(), + tcur->getY(), + static_cast(tcur->getTuioTime().getTotalMilliseconds()) / 1000.0 + ); + _inputList.emplace_back(input); _mx.unlock(); } void TuioEar::updateTuioCursor(TuioCursor* tcur) { _mx.lock(); - _tap = false; - long i = tcur->getSessionID(); - std::find_if( - _list.begin(), - _list.end(), - [&i](const TuioCursor& cursor) { - return cursor.getSessionID() == i; - } - )->update(tcur); + TouchInput input( + static_cast(tcur->getTuioSourceID()), + static_cast(tcur->getCursorID()), + tcur->getX(), + tcur->getY(), + static_cast(tcur->getTuioTime().getTotalMilliseconds()) / 1000.0 + ); + _inputList.emplace_back(input); _mx.unlock(); } // save id to be removed and remove it in clearInput void TuioEar::removeTuioCursor(TuioCursor* tcur) { _mx.lock(); - _removeList.push_back(tcur->getSessionID()); - - // Check if the cursor ID could be considered a tap - glm::dvec2 currPos = glm::dvec2(tcur->getX(), tcur->getY()); - double dist = 0; - for (const TuioPoint& p : tcur->getPath()) { - dist += glm::length(glm::dvec2(p.getX(), p.getY()) - currPos); - } - dist /= tcur->getPath().size(); - - double heldTime = - tcur->getPath().back().getTuioTime().getTotalMilliseconds() - - tcur->getPath().front().getTuioTime().getTotalMilliseconds(); - - if (heldTime < 180 && dist < 0.0004 && _list.size() == 1 && _removeList.size() == 1) { - _tapCo = TuioCursor(*tcur); - _tap = true; - } + TouchInput input( + static_cast(tcur->getTuioSourceID()), + static_cast(tcur->getCursorID()), + tcur->getX(), + tcur->getY(), + static_cast(tcur->getTuioTime().getTotalMilliseconds()) / 1000.0 + ); + _removalList.emplace_back(input); _mx.unlock(); } @@ -114,48 +89,22 @@ void TuioEar::removeTuioBlob(TuioBlob*) { } void TuioEar::refresh(TuioTime) { } // about every 15ms -std::vector TuioEar::getInput() { - std::lock_guard lock(_mx); - return _list; -} - -bool TuioEar::tap() { - std::lock_guard lock(_mx); - if (_tap) { - _tap = false; - return !_tap; +std::vector TuioEar::takeInput() { + std::vector outputList; + { + std::lock_guard lock(_mx); + outputList.swap(_inputList); } - else { - return _tap; + return outputList; +} + +std::vector TuioEar::takeRemovals() { + std::vector outputList; + { + std::lock_guard lock(_mx); + outputList.swap(_removalList); } -} - -TuioCursor TuioEar::getTap() { - std::lock_guard lock(_mx); - return _tapCo; -} - -// Removes all cursor ID from list that exists in _removeList -void TuioEar::clearInput() { - _mx.lock(); - _list.erase( - std::remove_if( - _list.begin(), - _list.end(), - [this](const TuioCursor& cursor) { - return std::find_if( - _removeList.begin(), - _removeList.end(), - [&cursor](long id) { - return cursor.getSessionID() == id; - } - ) != _removeList.end(); - } - ), - _list.end() - ); - _removeList.clear(); - _mx.unlock(); + return outputList; } // Standard UDP IP connection to port 3333 @@ -165,3 +114,9 @@ TuioEar::TuioEar() _tuioClient.addTuioListener(this); _tuioClient.connect(); } + +TuioEar::~TuioEar() { + _tuioClient.disconnect(); +} + +} // namespace openspace diff --git a/modules/touch/src/win32_touch.cpp b/modules/touch/src/win32_touch.cpp index 03ded0a114..5c1db8381d 100644 --- a/modules/touch/src/win32_touch.cpp +++ b/modules/touch/src/win32_touch.cpp @@ -26,84 +26,133 @@ #include +#include #include #include #include #include +#include +#include #include #include +// #define ENABLE_TUIOMESSAGES +#define ENABLE_DIRECTMSG + namespace { constexpr const char* _loggerCat = "win32_touch"; - HHOOK gTouchHook{ nullptr }; - bool gStarted{ false }; - TUIO::TuioServer* gTuioServer{ nullptr }; + HHOOK gTouchHook = nullptr; + std::thread* gMouseHookThread; + HHOOK gMouseHook = nullptr; + bool gStarted = false; + std::chrono::microseconds gStartTime = std::chrono::microseconds(0); + std::unordered_map< + UINT32, + std::unique_ptr + > gTouchInputsMap; +#ifdef ENABLE_TUIOMESSAGES + TUIO::TuioServer* gTuioServer = nullptr; std::unordered_map gCursorMap; +#endif } // namespace namespace openspace { +LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam); -// This hook will only work for Win7+ Digitizers. +// This hook will only work for Win8+ Digitizers. // - Once GLFW has native touch support, we can remove this windows-specific code LRESULT CALLBACK HookCallback(int nCode, WPARAM wParam, LPARAM lParam) { - if (nCode < 0) { + if (nCode != HC_ACTION) { return CallNextHookEx(0, nCode, wParam, lParam); } - if (nCode == HC_ACTION) { - LPMSG pStruct = reinterpret_cast(lParam); - const UINT message = pStruct->message; - switch (message) { - case WM_POINTERDOWN: - case WM_POINTERUPDATE: - case WM_POINTERUP: - { - POINTER_INFO pointerInfo = {}; - if (GetPointerInfo(GET_POINTERID_WPARAM(pStruct->wParam), &pointerInfo)) { - RECT rect; - GetClientRect(pStruct->hwnd, reinterpret_cast(&rect)); - POINT p = pointerInfo.ptPixelLocation; - // native touch to screen conversion - ScreenToClient(pStruct->hwnd, reinterpret_cast(&p)); - - float xPos = static_cast(p.x) / - static_cast(rect.right - rect.left); - float yPos = static_cast(p.y) / - static_cast(rect.bottom - rect.top); - if (pointerInfo.pointerFlags & POINTER_FLAG_DOWN) { - // Handle new touchpoint - gTuioServer->initFrame(TUIO::TuioTime::getSessionTime()); - gCursorMap[pointerInfo.pointerId] = gTuioServer->addTuioCursor( - xPos, - yPos - ); - gTuioServer->commitFrame(); - } - else if (pointerInfo.pointerFlags & POINTER_FLAG_UPDATE) { - // Handle update of touchpoint - TUIO::TuioTime frameTime = TUIO::TuioTime::getSessionTime(); - if (gCursorMap[pointerInfo.pointerId]->getTuioTime() == frameTime) - { - break; - } - gTuioServer->initFrame(frameTime); - gTuioServer->updateTuioCursor( - gCursorMap[pointerInfo.pointerId], - xPos, - yPos - ); - gTuioServer->commitFrame(); - } - else if (pointerInfo.pointerFlags & POINTER_FLAG_UP) { - // Handle removed touchpoint - gTuioServer->initFrame(TUIO::TuioTime::getSessionTime()); - gTuioServer->removeTuioCursor(gCursorMap[pointerInfo.pointerId]); - gTuioServer->commitFrame(); - gCursorMap.erase(pointerInfo.pointerId); - } - } + LPMSG pStruct = reinterpret_cast(lParam); + const UINT message = pStruct->message; + switch (message) { + case WM_POINTERDOWN: + case WM_POINTERUPDATE: + case WM_POINTERUP: + { + POINTER_INFO info = {}; + BOOL hasInfo = GetPointerInfo(GET_POINTERID_WPARAM(pStruct->wParam), &info); + if (!hasInfo) { break; } + + using namespace std::chrono; + const microseconds timestamp = duration_cast( + high_resolution_clock::now().time_since_epoch() + ) - gStartTime; + RECT rect; + GetClientRect(pStruct->hwnd, reinterpret_cast(&rect)); + + POINT p = info.ptPixelLocation; + // native touch to screen conversion + ScreenToClient(pStruct->hwnd, reinterpret_cast(&p)); + + float xPos = static_cast(p.x) / + static_cast(rect.right - rect.left); + float yPos = static_cast(p.y) / + static_cast(rect.bottom - rect.top); + + TouchInput touchInput( + reinterpret_cast(info.sourceDevice), + static_cast(info.pointerId), + xPos, + yPos, + static_cast(timestamp.count())/1'000'000.0 + ); + + if (info.pointerFlags & POINTER_FLAG_DOWN) { +#ifdef ENABLE_DIRECTMSG + std::unique_ptr points = + std::make_unique(touchInput); + gTouchInputsMap.emplace(info.pointerId, std::move(points)); + global::openSpaceEngine.touchDetectionCallback(touchInput); +#endif +#ifdef ENABLE_TUIOMESSAGES + // Handle new touchpoint + gTuioServer->initFrame(TUIO::TuioTime::getSessionTime()); + gCursorMap[info.pointerId] = gTuioServer->addTuioCursor( + xPos, + yPos + ); + gTuioServer->commitFrame(); +#endif + } + else if (info.pointerFlags & POINTER_FLAG_UPDATE) { + // Handle update of touchpoint +#ifdef ENABLE_DIRECTMSG + TouchInputHolder* points = gTouchInputsMap[info.pointerId].get(); + + if (points->tryAddInput(touchInput)) { + global::openSpaceEngine.touchUpdateCallback(points->latestInput()); + } +#endif +#ifdef ENABLE_TUIOMESSAGES + TUIO::TuioTime frameTime = TUIO::TuioTime::getSessionTime(); + if (gCursorMap[info.pointerId]->getTuioTime() == frameTime) { + break; + } + gTuioServer->initFrame(frameTime); + gTuioServer->updateTuioCursor(gCursorMap[info.pointerId], xPos, yPos); + gTuioServer->commitFrame(); +#endif + } + else if (info.pointerFlags & POINTER_FLAG_UP) { +#ifdef ENABLE_DIRECTMSG + gTouchInputsMap.erase(info.pointerId); + global::openSpaceEngine.touchExitCallback(touchInput); +#endif +#ifdef ENABLE_TUIOMESSAGES + // Handle removed touchpoint + gTuioServer->initFrame(TUIO::TuioTime::getSessionTime()); + gTuioServer->removeTuioCursor(gCursorMap[info.pointerId]); + gTuioServer->commitFrame(); + gCursorMap.erase(info.pointerId); +#endif + } + break; } } @@ -119,9 +168,18 @@ Win32TouchHook::Win32TouchHook(void* nativeWindow) return; } + // HACK: This hack is required as long as our GLFW version is based on the touch + // branch. There is no convenient way to set a GLFWBool (uint32_t) which sets the + // state of touch-to-mouseinput interpretation. It happens to be 116 bytes into an + // internal glfw struct... + uint32_t* HACKY_PTR = (uint32_t *)GetPropW(hWnd, L"GLFW"); + HACKY_PTR += 116/sizeof(uint32_t); + *HACKY_PTR = 1; + + // Test for touch: int value = GetSystemMetrics(SM_DIGITIZER); - if ((value & NID_READY) == 0) { + if ((value & NID_READY) == 0) { // Don't bother setting up touch hooks? return; } @@ -130,12 +188,12 @@ Win32TouchHook::Win32TouchHook(void* nativeWindow) // Digitizer is multitouch LINFO("Found Multitouch input digitizer!"); } - if (value & NID_INTEGRATED_TOUCH) { + if (value & NID_INTEGRATED_TOUCH) { // Integrated touch } // This should be needed, but we seem to receive messages even without it, - // probably a Win7+ behaviour + // this ought to be part to the older (< win8) windows touch-api. // Also - RegisterTouchWindow enables Windows gestures, which we don't want // since they produce visual feedback for "press-and-tap" etc. // RegisterTouchWindow(hWnd, TWF_FINETOUCH | TWF_WANTPALM); @@ -143,7 +201,7 @@ Win32TouchHook::Win32TouchHook(void* nativeWindow) // TODO: Would be nice to find out if the gesture "press-and-tap" can be disabled // basically we don't really care for windows gestures for now... // this disables press and hold (right-click) gesture - const DWORD dwHwndTabletProperty = TABLET_DISABLE_PRESSANDHOLD; + const UINT_PTR dwHwndTabletProperty = TABLET_DISABLE_PRESSANDHOLD; ATOM atom = ::GlobalAddAtom(MICROSOFT_TABLETPENSERVICE_PROPERTY); ::SetProp( @@ -155,28 +213,88 @@ Win32TouchHook::Win32TouchHook(void* nativeWindow) if (!gStarted) { gStarted = true; + gStartTime = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch() + ); +#ifdef ENABLE_TUIOMESSAGES gTuioServer = new TUIO::TuioServer("localhost", 3333); TUIO::TuioTime::initSession(); +#endif gTouchHook = SetWindowsHookExW( WH_GETMESSAGE, HookCallback, GetModuleHandleW(NULL), GetCurrentThreadId() ); + + // In theory, if our UI is pumped from a different thread, we can + // handle Low-level mouse events in that thread as well. + // this might help reduce mouse lag while running OpenSpace? + // gMouseHookThread = new std::thread([](){ + // gMouseHook = SetWindowsHookExW( + // WH_MOUSE_LL, + // LowLevelMouseProc, + // GetModuleHandleW(NULL), + // 0 //<- Global thread id (low-level mouse is global only) + // ); + // if(!gMouseHook){ + // LINFO("Could not setup mousehook!"); + // } + + // MSG msg; + // while (GetMessage(&msg, NULL, 0, 0)) + // { + // DispatchMessage(&msg); + // } + // }); + if (!gTouchHook) { LINFO(fmt::format("Failed to setup WindowsHook for touch input redirection")); +#ifdef ENABLE_TUIOMESSAGES delete gTuioServer; +#endif gStarted = false; } } } + Win32TouchHook::~Win32TouchHook() { if (gStarted) { UnhookWindowsHookEx(gTouchHook); + UnhookWindowsHookEx(gMouseHook); +#ifdef ENABLE_TUIOMESSAGES delete gTuioServer; +#endif } } +// Low-level mouse hook is "needed" if we want to stop mousecursor from moving +// when we get a touch-input on our window A negative effect is that this +// function is for global threads, meaning our application will cause Windows to +// stall the mouse cursor when this function can't be scheduled. This is not yet +// fail-proof...might be a race-condition on message pumping? +// - Seems to move the cursor when we get two fingers as input.. +// - If we ourselves would pump windows for events, we can handle this in the +// pump-loop +LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) { + constexpr const LONG_PTR SIGNATURE_MASK = 0xFFFFFF00; + constexpr const LONG_PTR MOUSEEVENTF_FROMTOUCH = 0xFF515700; + if (nCode < 0) { + // do not process message + return CallNextHookEx(0, nCode, wParam, lParam); + } + LPMSLLHOOKSTRUCT msg = reinterpret_cast(lParam); + // block injected events (in most cases generated by touches) + bool isFromTouch = (msg->dwExtraInfo || SIGNATURE_MASK) == MOUSEEVENTF_FROMTOUCH; + if (msg->flags & LLMHF_INJECTED || isFromTouch) { + return 1; + } + + // forward event + return CallNextHookEx(0, nCode, wParam, lParam); +} + } // namespace openspace + #endif // WIN32 diff --git a/modules/touch/touchmodule.cpp b/modules/touch/touchmodule.cpp index f968095a44..a436f1107a 100644 --- a/modules/touch/touchmodule.cpp +++ b/modules/touch/touchmodule.cpp @@ -23,95 +23,94 @@ ****************************************************************************************/ #include -#include -#include +#include +#include #include #include #include #include -#include #include +#include #include #include #include #include #include #include -#include - -#ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED -#include -#endif using namespace TUIO; +namespace { + constexpr const double ONE_MS = 0.001; +} // namespace + namespace openspace { bool TouchModule::processNewInput() { // Get new input from listener + std::vector earInputs = _ear->takeInput(); + std::vector earRemovals = _ear->takeRemovals(); + + for(const TouchInput& input : earInputs) { + updateOrAddTouchInput(input); + } + for(const TouchInput& removal : earRemovals) { + removeTouchInput(removal); + } - _listOfContactPoints = _ear.getInput(); - _ear.clearInput(); // Set touch property to active (to void mouse input, mainly for mtdev bridges) - _touch.touchActive(!_listOfContactPoints.empty()); + _touch.touchActive(!_touchPoints.empty()); - if (!_listOfContactPoints.empty()) { + if (!_touchPoints.empty()) { global::interactionMonitor.markInteraction(); } // Erase old input id's that no longer exists - _lastProcessed.erase( + _lastTouchInputs.erase( std::remove_if( - _lastProcessed.begin(), - _lastProcessed.end(), - [this](const Point& point) { - return std::find_if( - _listOfContactPoints.begin(), - _listOfContactPoints.end(), - [&point](const TuioCursor& c) { - return point.first == c.getSessionID(); - } - ) == _listOfContactPoints.end(); }), - _lastProcessed.end()); + _lastTouchInputs.begin(), + _lastTouchInputs.end(), + [this](const TouchInput& input) { + return !std::any_of( + _touchPoints.cbegin(), + _touchPoints.cend(), + [&input](const TouchInputHolder& holder) { + return holder.holdsInput(input); + } + ); + } + ), + _lastTouchInputs.end() + ); - // if tap occured, we have new input - if (_listOfContactPoints.empty() && _lastProcessed.empty() && _ear.tap()) { - TuioCursor c = _ear.getTap(); - _listOfContactPoints.push_back(c); - _lastProcessed.emplace_back(c.getSessionID(), c.getPath().back()); + if (_tap) { _touch.tap(); + _tap = false; return true; } - // Check if we need to parse touchevent to the webgui - processNewWebInput(_listOfContactPoints); - // Return true if we got new input - if (_listOfContactPoints.size() == _lastProcessed.size() && - !_listOfContactPoints.empty()) + if (_touchPoints.size() == _lastTouchInputs.size() && + !_touchPoints.empty()) { bool newInput = true; // go through list and check if the last registrered time is newer than the one in // lastProcessed (last frame) std::for_each( - _lastProcessed.begin(), - _lastProcessed.end(), - [this, &newInput](Point& p) { - std::vector::iterator cursor = std::find_if( - _listOfContactPoints.begin(), - _listOfContactPoints.end(), - [&p](const TuioCursor& c) { return c.getSessionID() == p.first; } + _lastTouchInputs.begin(), + _lastTouchInputs.end(), + [this, &newInput](TouchInput& input) { + std::vector::iterator holder = std::find_if( + _touchPoints.begin(), + _touchPoints.end(), + [&input](const TouchInputHolder& inputHolder) { + return inputHolder.holdsInput(input); + } ); - double now = cursor->getPath().back().getTuioTime().getTotalMilliseconds(); - if (!cursor->isMoving()) { - // if current cursor isn't moving, we want to interpret that as new input - // for interaction purposes + if (!holder->isMoving()) { newInput = true; } - else if (p.second.getTuioTime().getTotalMilliseconds() == now) { - newInput = false; - } }); return newInput; } @@ -120,31 +119,51 @@ bool TouchModule::processNewInput() { } } -void TouchModule::processNewWebInput(const std::vector& listOfContactPoints) { - bool isWebPositionCallbackZero = - (_webPositionCallback.x == 0 && _webPositionCallback.y == 0); - bool isSingleContactPoint = (listOfContactPoints.size() == 1); - if (isSingleContactPoint && isWebPositionCallbackZero) { - glm::ivec2 res = global::windowDelegate.currentWindowSize(); - glm::dvec2 pos = glm::vec2( - listOfContactPoints.at(0).getScreenX(res.x), - listOfContactPoints.at(0).getScreenY(res.y) - ); - -#ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED - WebBrowserModule& module = *(global::moduleEngine.module()); - if (module.eventHandler().hasContentCallback(pos.x, pos.y)) { - _webPositionCallback = glm::vec2(pos.x, pos.y); - module.eventHandler().touchPressCallback(pos.x, pos.y); +void TouchModule::clearInputs() { + for (const TouchInput& input : _deferredRemovals) { + for (TouchInputHolder& inputHolder : _touchPoints) { + if (inputHolder.holdsInput(input)) { + inputHolder = std::move(_touchPoints.back()); + _touchPoints.pop_back(); + break; + } } } - // Send mouse release if not same point input - else if (!isSingleContactPoint && !isWebPositionCallbackZero) { - WebBrowserModule& module = *(global::moduleEngine.module()); - module.eventHandler().touchReleaseCallback(_webPositionCallback.x, - _webPositionCallback.y); - _webPositionCallback = glm::vec2(0, 0); -#endif + _deferredRemovals.clear(); +} + +void TouchModule::addTouchInput(TouchInput input) { + _touchPoints.emplace_back(input); +} + +void TouchModule::updateOrAddTouchInput(TouchInput input) { + for (TouchInputHolder& inputHolder : _touchPoints) { + if (inputHolder.holdsInput(input)){ + inputHolder.tryAddInput(input); + return; + } + } + _touchPoints.emplace_back(input); +} + +void TouchModule::removeTouchInput(TouchInput input) { + _deferredRemovals.emplace_back(input); + //Check for "tap" gesture: + for (TouchInputHolder& inputHolder : _touchPoints) { + if (inputHolder.holdsInput(input)) { + inputHolder.tryAddInput(input); + const double totalTime = inputHolder.gestureTime(); + const float totalDistance = inputHolder.gestureDistance(); + //Magic values taken from tuioear.cpp: + const bool isWithinTapTime = totalTime < 0.18; + const bool wasStationary = totalDistance < 0.0004f; + if (isWithinTapTime && wasStationary && _touchPoints.size() == 1 && + _deferredRemovals.size() == 1) + { + _tap = true; + } + return; + } } } @@ -153,17 +172,25 @@ TouchModule::TouchModule() { addPropertySubOwner(_touch); addPropertySubOwner(_markers); +} + +TouchModule::~TouchModule() { + // intentionally left empty +} + +void TouchModule::internalInitialize(const ghoul::Dictionary& /*dictionary*/){ + _ear.reset(new TuioEar()); global::callback::initializeGL.push_back([&]() { LDEBUGC("TouchModule", "Initializing TouchMarker OpenGL"); _markers.initialize(); #ifdef WIN32 - // We currently only support one window of touch input internally - // so here we grab the first window-handle and use it. - void* nativeWindowHandle = global::windowDelegate.getNativeWindowHandle(0); - if (nativeWindowHandle) { - _win32TouchHook.reset(new Win32TouchHook(nativeWindowHandle)); - } + // We currently only support one window of touch input internally + // so here we grab the first window-handle and use it. + void* nativeWindowHandle = global::windowDelegate.getNativeWindowHandle(0); + if (nativeWindowHandle) { + _win32TouchHook = std::make_unique(nativeWindowHandle); + } #endif }); @@ -172,35 +199,51 @@ TouchModule::TouchModule() _markers.deinitialize(); }); + // These are handled in UI thread, which (as of 20th dec 2019) is in main/rendering + // thread so we don't need a mutex here + global::callback::touchDetected.push_back( + [this](TouchInput i) { + addTouchInput(i); + return true; + } + ); + + global::callback::touchUpdated.push_back( + [this](TouchInput i) { + updateOrAddTouchInput(i); + return true; + } + ); + + global::callback::touchExit.push_back( + std::bind(&TouchModule::removeTouchInput, this, std::placeholders::_1) + ); + + global::callback::preSync.push_back([&]() { _touch.setCamera(global::navigationHandler.camera()); _touch.setFocusNode(global::navigationHandler.orbitalNavigator().anchorNode()); if (processNewInput() && global::windowDelegate.isMaster()) { - _touch.updateStateFromInput(_listOfContactPoints, _lastProcessed); + _touch.updateStateFromInput(_touchPoints, _lastTouchInputs); } - else if (_listOfContactPoints.empty()) { + else if (_touchPoints.empty()) { _touch.resetAfterInput(); } // update lastProcessed - _lastProcessed.clear(); - for (const TuioCursor& c : _listOfContactPoints) { - _lastProcessed.emplace_back(c.getSessionID(), c.getPath().back()); + _lastTouchInputs.clear(); + for (const TouchInputHolder& points : _touchPoints) { + _lastTouchInputs.emplace_back(points.latestInput()); } - // used to save data from solver, only calculated for one frame when user chooses - // in GUI - _touch.unitTest(); // calculate the new camera state for this frame _touch.step(global::windowDelegate.deltaTime()); + clearInputs(); }); - global::callback::render.push_back([&]() { _markers.render(_listOfContactPoints); }); - -} - -TouchModule::~TouchModule() { - //intentionally left empty + global::callback::render.push_back([&]() { + _markers.render(_touchPoints); + }); } } // namespace openspace diff --git a/modules/touch/touchmodule.h b/modules/touch/touchmodule.h index 5300967a36..f32b28d854 100644 --- a/modules/touch/touchmodule.h +++ b/modules/touch/touchmodule.h @@ -25,43 +25,52 @@ #ifndef __OPENSPACE_MODULE_TOUCH___TOUCHMODULE___H__ #define __OPENSPACE_MODULE_TOUCH___TOUCHMODULE___H__ -#include #include #include - +#include +#include +#include namespace openspace { - #ifdef WIN32 - class Win32TouchHook; - #endif //WIN32 - class TouchModule : public OpenSpaceModule { - using Point = std::pair; - public: - TouchModule(); - ~TouchModule(); + class TuioEar; - private: - /** - * Returns true if new touch input occured since the last frame - */ - bool processNewInput(); - /** - * Checks if touchevent should be parsed to the webgui - */ - void processNewWebInput(const std::vector& listOfContactPoints); - - TuioEar _ear; - TouchInteraction _touch; - TouchMarker _markers; - std::vector _listOfContactPoints; - // contains an id and the TuioPoint that was processed last frame - std::vector _lastProcessed; - glm::ivec2 _webPositionCallback = glm::ivec2(0,0); #ifdef WIN32 - std::unique_ptr _win32TouchHook; +class Win32TouchHook; #endif //WIN32 - }; + +class TouchModule : public OpenSpaceModule { +public: + TouchModule(); + ~TouchModule(); + +protected: + void internalInitialize(const ghoul::Dictionary& dictionary) override; + +private: + /// Returns true if new touch input occured since the last frame + bool processNewInput(); + + void clearInputs(); + + void addTouchInput(TouchInput input); + void updateOrAddTouchInput(TouchInput input); + void removeTouchInput(TouchInput input); + + std::unique_ptr _ear; + TouchInteraction _touch; + TouchMarker _markers; + std::vector _touchPoints; + std::vector _deferredRemovals; + std::vector _lastTouchInputs; + + // contains an id and the Point that was processed last frame + glm::ivec2 _webPositionCallback = glm::ivec2(0,0); +#ifdef WIN32 + std::unique_ptr _win32TouchHook; +#endif //WIN32 + bool _tap = false; +}; } // namespace openspace diff --git a/modules/webbrowser/include/eventhandler.h b/modules/webbrowser/include/eventhandler.h index 3ef6bdce06..22cc3bbe7d 100644 --- a/modules/webbrowser/include/eventhandler.h +++ b/modules/webbrowser/include/eventhandler.h @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -60,10 +61,6 @@ public: void setBrowserInstance(BrowserInstance* browserInstance); void resetBrowserInstance(); - void touchPressCallback(const double x, const double y); - void touchReleaseCallback(const double x, const double y); - bool hasContentCallback(const double, const double); - private: bool mouseButtonCallback(MouseButton button, MouseAction action, KeyModifier mods); bool mousePositionCallback(double x, double y); @@ -107,6 +104,9 @@ private: MouseButtonState _leftButton; MouseButtonState _rightButton; + //This vector assumes first element to be the active one: + std::vector _validTouchStates; + /** * determines if a click should be sent as a double click or not * @return diff --git a/modules/webbrowser/src/eventhandler.cpp b/modules/webbrowser/src/eventhandler.cpp index c805e22722..ef68227e2f 100644 --- a/modules/webbrowser/src/eventhandler.cpp +++ b/modules/webbrowser/src/eventhandler.cpp @@ -29,7 +29,6 @@ #include #include #include - #include #include @@ -37,12 +36,12 @@ namespace { constexpr const char* _loggerCat = "WebBrowser:EventHandler"; /** - * Map from GLFW key codes to windows key codes, supported by JS and CEF. - * See http://keycode.info/ for lookup - * - * \param key - * \return the key code, if mapped or the GLFW key code - */ + * Map from GLFW key codes to windows key codes, supported by JS and CEF. + * See http://keycode.info/ for lookup + * + * \param key + * \return the key code, if mapped or the GLFW key code + */ int mapFromGlfwToWindows(openspace::Key key) { switch (key) { case openspace::Key::BackSpace: return 8; @@ -192,34 +191,114 @@ void EventHandler::initialize() { return false; } ); + + global::callback::touchDetected.emplace_back( + [&](TouchInput input) -> bool { + if (!_browserInstance) { + return false; + } + + const glm::vec2 windowPos = input.currentWindowCoordinates(); + const bool hasContent = _browserInstance->hasContent( + static_cast(windowPos.x), + static_cast(windowPos.y) + ); + if (!hasContent) { + return false; + } + + if (_validTouchStates.empty()) { + _mousePosition.x = windowPos.x; + _mousePosition.y = windowPos.y; + _leftButton.down = true; + _browserInstance->sendMouseClickEvent( + mouseEvent(), + MBT_LEFT, + false, + BrowserInstance::SingleClick + ); + _validTouchStates.emplace_back(input); + } + else { + _validTouchStates.emplace_back(input); + } + return true; + } + ); + + global::callback::touchUpdated.emplace_back( + [&](TouchInput input) -> bool { + if (!_browserInstance) { + return false; + } + if (_validTouchStates.empty()) { + return false; + } + + auto it = std::find_if( + _validTouchStates.cbegin(), + _validTouchStates.cend(), + [&](const TouchInput& state){ + return state.fingerId == input.fingerId && + state.touchDeviceId == input.touchDeviceId; + } + ); + + if (it == _validTouchStates.cbegin()) { + glm::vec2 windowPos = input.currentWindowCoordinates(); + _mousePosition.x = windowPos.x; + _mousePosition.y = windowPos.y; + _leftButton.down = true; + _browserInstance->sendMouseMoveEvent(mouseEvent()); + return true; + } + else if (it != _validTouchStates.cend()){ + return true; + } + return false; + } + ); + + global::callback::touchExit.emplace_back( + [&](TouchInput input) { + if (!_browserInstance) { + return; + } + if (_validTouchStates.empty()) { + return; + } + + const auto found = std::find_if( + _validTouchStates.cbegin(), + _validTouchStates.cend(), + [&](const TouchInput& state){ + return state.fingerId == input.fingerId && + state.touchDeviceId == input.touchDeviceId; + } + ); + + if (found == _validTouchStates.cend()) { + return; + } + + _validTouchStates.erase(found); + if (_validTouchStates.empty()) { + glm::vec2 windowPos = input.currentWindowCoordinates(); + _mousePosition.x = windowPos.x; + _mousePosition.y = windowPos.y; + _leftButton.down = false; + _browserInstance->sendMouseClickEvent( + mouseEvent(), + MBT_LEFT, + true, + BrowserInstance::SingleClick + ); + } + } + ); } -void EventHandler::touchPressCallback(const double x, const double y) { - if (_browserInstance) { - _mousePosition.x = static_cast(x); - _mousePosition.y = static_cast(y); - - int clickCount = BrowserInstance::SingleClick; - _browserInstance->sendMouseClickEvent(mouseEvent(), MBT_LEFT, false, clickCount); - } -} - -void EventHandler::touchReleaseCallback(const double x, const double y) { - if (_browserInstance) { - _mousePosition.x = static_cast(x); - _mousePosition.y = static_cast(y); - - int clickCount = BrowserInstance::SingleClick; - _browserInstance->sendMouseClickEvent(mouseEvent(), MBT_LEFT, true, clickCount); - } -} - -bool EventHandler::hasContentCallback(const double x, const double y) { - return _browserInstance->hasContent(static_cast(x), static_cast(y)); -} - -bool EventHandler::mouseButtonCallback(MouseButton button, - MouseAction action, +bool EventHandler::mouseButtonCallback(MouseButton button, MouseAction action, KeyModifier mods) { if (button != MouseButton::Left && button != MouseButton::Right) { @@ -234,10 +313,12 @@ bool EventHandler::mouseButtonCallback(MouseButton button, // click or release? if (action == MouseAction::Release) { state.down = false; - } else { + } + else { if (isDoubleClick(state)) { ++clickCount; - } else { + } + else { state.lastClickTime = std::chrono::high_resolution_clock::now(); } @@ -256,6 +337,7 @@ bool EventHandler::mouseButtonCallback(MouseButton button, bool EventHandler::isDoubleClick(const MouseButtonState& button) const { // check time using namespace std::chrono; + auto now = high_resolution_clock::now(); milliseconds maxTimeDifference(doubleClickTime()); auto requiredTime = button.lastClickTime + maxTimeDifference; @@ -336,11 +418,7 @@ bool EventHandler::specialKeyEvent(Key key, KeyModifier mod, KeyAction) { } cef_key_event_type_t EventHandler::keyEventType(KeyAction action) { - if (action == KeyAction::Release) { - return KEYEVENT_KEYUP; - } else { - return KEYEVENT_KEYDOWN; - } + return action == KeyAction::Release ? KEYEVENT_KEYUP : KEYEVENT_KEYDOWN; } CefMouseEvent EventHandler::mouseEvent(KeyModifier mods) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bba42b68da..836e42e3cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -195,6 +195,7 @@ set(OPENSPACE_SOURCE ${OPENSPACE_BASE_DIR}/src/util/timemanager.cpp ${OPENSPACE_BASE_DIR}/src/util/time_lua.inl ${OPENSPACE_BASE_DIR}/src/util/timerange.cpp + ${OPENSPACE_BASE_DIR}/src/util/touch.cpp ${OPENSPACE_BASE_DIR}/src/util/transformationmanager.cpp ${OPENSPACE_BASE_DIR}/src/util/versionchecker.cpp ) @@ -389,6 +390,7 @@ set(OPENSPACE_HEADER ${OPENSPACE_BASE_DIR}/include/openspace/util/timeline.inl ${OPENSPACE_BASE_DIR}/include/openspace/util/timemanager.h ${OPENSPACE_BASE_DIR}/include/openspace/util/timerange.h + ${OPENSPACE_BASE_DIR}/include/openspace/util/touch.h ${OPENSPACE_BASE_DIR}/include/openspace/util/updatestructures.h ${OPENSPACE_BASE_DIR}/include/openspace/util/versionchecker.h ${OPENSPACE_BASE_DIR}/include/openspace/util/transformationmanager.h diff --git a/src/engine/globalscallbacks.cpp b/src/engine/globalscallbacks.cpp index 93f7fe0d0a..2bba9d4560 100644 --- a/src/engine/globalscallbacks.cpp +++ b/src/engine/globalscallbacks.cpp @@ -96,6 +96,21 @@ std::vector>& gMouseScrollWheel() { return g; } +std::vector>& gTouchDetected() { + static std::vector> g; + return g; +} + +std::vector>& gTouchUpdated() { + static std::vector> g; + return g; +} + +std::vector>& gTouchExit() { + static std::vector> g; + return g; +} + } // namespace openspace::global::detail namespace openspace::global::callback { diff --git a/src/engine/openspaceengine.cpp b/src/engine/openspaceengine.cpp index 5ca2fdfec6..f907f0087f 100644 --- a/src/engine/openspaceengine.cpp +++ b/src/engine/openspaceengine.cpp @@ -1295,6 +1295,34 @@ void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) { global::interactionMonitor.markInteraction(); } +void OpenSpaceEngine::touchDetectionCallback(TouchInput input) { + using F = std::function; + for (const F& func : global::callback::touchDetected) { + bool isConsumed = func(input); + if (isConsumed) { + return; + } + } +} + +void OpenSpaceEngine::touchUpdateCallback(TouchInput input) { + using F = std::function; + for (const F& func : global::callback::touchUpdated) { + bool isConsumed = func(input); + if (isConsumed) { + return; + } + } +} + +void OpenSpaceEngine::touchExitCallback(TouchInput input) { + using F = std::function; + for (const F& func : global::callback::touchExit) { + func(input); + } +} + + std::vector OpenSpaceEngine::encode() { std::vector buffer = global::syncEngine.encodeSyncables(); return buffer; diff --git a/src/rendering/framebufferrenderer.cpp b/src/rendering/framebufferrenderer.cpp index a6b327149b..0a6dd0ab57 100644 --- a/src/rendering/framebufferrenderer.cpp +++ b/src/rendering/framebufferrenderer.cpp @@ -1020,9 +1020,6 @@ void FramebufferRenderer::updateHDRAndFiltering() { absPath("${SHADERS}/framebuffer/hdrAndFiltering.vert"), absPath("${SHADERS}/framebuffer/hdrAndFiltering.frag") ); - using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; - //_hdrFilteringProgram->setIgnoreSubroutineUniformLocationError(IgnoreError::Yes); - //_hdrFilteringProgram->setIgnoreUniformLocationError(IgnoreError::Yes); } void FramebufferRenderer::updateFXAA() { @@ -1031,9 +1028,6 @@ void FramebufferRenderer::updateFXAA() { absPath("${SHADERS}/framebuffer/fxaa.vert"), absPath("${SHADERS}/framebuffer/fxaa.frag") ); - using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; - //_fxaaProgram->setIgnoreSubroutineUniformLocationError(IgnoreError::Yes); - //_fxaaProgram->setIgnoreUniformLocationError(IgnoreError::Yes); } void FramebufferRenderer::updateDownscaledVolume() { @@ -1042,9 +1036,6 @@ void FramebufferRenderer::updateDownscaledVolume() { absPath("${SHADERS}/framebuffer/mergeDownscaledVolume.vert"), absPath("${SHADERS}/framebuffer/mergeDownscaledVolume.frag") ); - using IgnoreError = ghoul::opengl::ProgramObject::IgnoreError; - //_downscaledVolumeProgram->setIgnoreSubroutineUniformLocationError(IgnoreError::Yes); - //_downscaledVolumeProgram->setIgnoreUniformLocationError(IgnoreError::Yes); } void FramebufferRenderer::render(Scene* scene, Camera* camera, float blackoutFactor) { diff --git a/src/rendering/renderengine.cpp b/src/rendering/renderengine.cpp index 918f5638f2..d8dcbd7b65 100644 --- a/src/rendering/renderengine.cpp +++ b/src/rendering/renderengine.cpp @@ -335,7 +335,7 @@ RenderEngine::RenderEngine() _hue.onChange([this]() { if (_renderer) { - const float h = _hue / 360.0; + const float h = _hue / 360.f; _renderer->setHue(h); } }); diff --git a/src/util/touch.cpp b/src/util/touch.cpp new file mode 100644 index 0000000000..ab16c23ed4 --- /dev/null +++ b/src/util/touch.cpp @@ -0,0 +1,185 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * 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 + +namespace openspace { + +TouchInput::TouchInput(size_t touchDeviceId, size_t fingerId, float x, float y, + double timestamp) + : touchDeviceId(touchDeviceId) + , fingerId(fingerId) + , x(x) + , y(y) + , timestamp(timestamp) +{} + +glm::vec2 TouchInput::screenCoordinates(glm::vec2 resolution) const { + return { std::floor(x * resolution.x + 0.5f), std::floor(y * resolution.y + 0.5f) }; +} + +glm::vec2 TouchInput::currentWindowCoordinates() const { + glm::vec2 res = global::windowDelegate.currentWindowSize(); + return { std::floor(x * res.x + 0.5f), std::floor(y * res.y + 0.5f) }; +} + +bool TouchInput::isMoving() const { + return dx != 0.f || dy != 0.f; +} + +float TouchInput::distanceToPos(float otherX, float otherY) const { + const float distX = x - otherX; + const float distY = y - otherY; + return std::sqrt(distX*distX + distY*distY); +} + +float TouchInput::angleToPos(float otherX, float otherY) const { + const float side = x - otherX; + const float height = y - otherY; + const float distance = distanceToPos(otherX, otherY); + + float angle = glm::half_pi() + std::asin(side / distance); + if (height < 0.f) { + angle = 2.f * glm::pi() - angle; + } + + return angle; +} + +TouchInputHolder::TouchInputHolder(TouchInput input) + : _inputs{ input } + , _touchDeviceId(input.touchDeviceId) + , _fingerId(input.fingerId) +{} + +bool TouchInputHolder::tryAddInput(TouchInput input) { + constexpr const double ONE_MS = 0.001; + const TouchInput& lastInput = latestInput(); + input.dx = input.x - lastInput.x; + input.dy = input.y - lastInput.y; + + const bool sameTimeAsLastInput = (input.timestamp - lastInput.timestamp) > ONE_MS; + bool wasInserted = false; + if (isMoving()) { + _inputs.emplace_front(input); + wasInserted = true; + } + else if (sameTimeAsLastInput && input.isMoving()) { + _inputs.emplace_front(input); + wasInserted = true; + } + + constexpr const int MaxInputs = 128; + if (_inputs.size() > MaxInputs) { + _inputs.pop_back(); + } + return wasInserted; +} + +void TouchInputHolder::clearInputs() { + _inputs.clear(); +} + +bool TouchInputHolder::holdsInput(const TouchInput &input) const { + return input.fingerId == _fingerId && input.touchDeviceId == _touchDeviceId; +} + +size_t TouchInputHolder::touchDeviceId() const { + return _touchDeviceId; +} + +size_t TouchInputHolder::fingerId() const { + return _fingerId; +} + +float TouchInputHolder::speedX() const { + if (_inputs.size() <= 1) { + return 0.f; + } + const TouchInput& currentInput = _inputs[0]; + const TouchInput& previousInput = _inputs[1]; + const float dt = static_cast(currentInput.timestamp - previousInput.timestamp); + return currentInput.dx / dt; +} + +float TouchInputHolder::speedY() const { + if(_inputs.size() <= 1) { + return 0.f; + } + const TouchInput& currentInput = _inputs[0]; + const TouchInput& previousInput = _inputs[1]; + const float dt = static_cast(currentInput.timestamp - previousInput.timestamp); + + return currentInput.dy / dt; +} + +bool TouchInputHolder::isMoving() const { + if (_inputs.size() <= 1) { + return false; + } + const TouchInput& currentInput = _inputs[0]; + return currentInput.dx != 0.f || currentInput.dy != 0.f; +} + +float TouchInputHolder::gestureDistance() const { + if (_inputs.size() <= 1) { + return 0.f; + } + float distX = 0.f; + float distY = 0.f; + const float startX = _inputs.front().x; + const float startY = _inputs.front().y; + for (const TouchInput& input : _inputs) { + distX += std::abs(input.x - startX); + distY += std::abs(input.y - startY); + } + return std::sqrt(distX*distX + distY*distY); +} + +double TouchInputHolder::gestureTime() const { + if (_inputs.size() <= 1) { + return 0.0; + } + const double before = _inputs.back().timestamp; + const double after = _inputs.front().timestamp; + return after - before; +} + +size_t TouchInputHolder::numInputs() const { + return _inputs.size(); +} + +const TouchInput& TouchInputHolder::latestInput() const { + return _inputs.front(); +} + +const std::deque& TouchInputHolder::peekInputs() const { + return _inputs; +} + +} // namespace openspace