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