diff --git a/modules/touch/include/touchinteraction.h b/modules/touch/include/touchinteraction.h index d6bd98c36a..b492bbb5d6 100644 --- a/modules/touch/include/touchinteraction.h +++ b/modules/touch/include/touchinteraction.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -67,9 +68,16 @@ public: TouchInteraction(); // for interpretInteraction() - enum Type { ROT = 0, PINCH, PAN, ROLL, PICK, ZOOM_OUT }; + enum Type { + ROTATION = 0, + PINCH, + PAN, + ROLL, + PICK, + ZOOM_OUT + }; - // Stores the velocity in all 6DOF + // Stores the velocity in all 6 DOF struct VelocityStates { glm::dvec2 orbit = glm::dvec2(0.0); double zoom = 0.0; @@ -146,7 +154,7 @@ private: void decelerate(double dt); // Resets all properties that can be changed in the GUI to default - void resetToDefault(); + void resetPropertiesToDefault(); Camera* _camera = nullptr; @@ -156,7 +164,7 @@ private: properties::BoolProperty _touchActive; properties::BoolProperty _disableZoom; properties::BoolProperty _disableRoll; - properties::BoolProperty _reset; + properties::TriggerProperty _reset; properties::IntProperty _maxTapTime; properties::IntProperty _deceleratesPerSecond; properties::FloatProperty _touchScreenSize; @@ -194,7 +202,7 @@ private: int pinchConsecCt = 0; double pinchConsecZoomFactor = 0; - //int stepVelUpdate = 0; + int stepVelUpdate = 0; #endif std::array _pinchInputs; // Class variables diff --git a/modules/touch/src/touchinteraction.cpp b/modules/touch/src/touchinteraction.cpp index 8a12adba52..dcce4ae840 100644 --- a/modules/touch/src/touchinteraction.cpp +++ b/modules/touch/src/touchinteraction.cpp @@ -77,7 +77,10 @@ namespace { constexpr openspace::properties::Property::PropertyInfo UnitTestInfo = { "UnitTest", "Take a unit test saving the LM data into file", - "" // @TODO Missing documentation + "LM - least-squares minimization using Levenberg-Marquardt algorithm." + "Used to find a new camera state from touch points when doing direct " + "manipulation", + openspace::properties::Property::Visibility::Developer }; constexpr openspace::properties::Property::PropertyInfo DisableZoomInfo = { @@ -268,16 +271,16 @@ TouchInteraction::TouchInteraction() , _touchActive(EventsInfo, false) , _disableZoom(DisableZoomInfo, false) , _disableRoll(DisableRollInfo, false) - , _reset(SetDefaultInfo, false) + , _reset(SetDefaultInfo) , _maxTapTime(MaxTapTimeInfo, 300, 10, 1000) , _deceleratesPerSecond(DecelatesPerSecondInfo, 240, 60, 300) , _touchScreenSize(TouchScreenSizeInfo, 55.0f, 5.5f, 150.0f) - , _tapZoomFactor(TapZoomFactorInfo, 0.2f, 0.f, 0.5f) + , _tapZoomFactor(TapZoomFactorInfo, 0.2f, 0.f, 0.5f, 0.01f) , _pinchZoomFactor(PinchZoomFactorInfo, 0.01f, 0.f, 0.2f) , _nodeRadiusThreshold(DirectManipulationInfo, 0.2f, 0.0f, 1.0f) - , _rollAngleThreshold(RollThresholdInfo, 0.025f, 0.f, 0.05f) - , _orbitSpeedThreshold(OrbitSpinningThreshold, 0.005f, 0.f, 0.01f) - , _spinSensitivity(SpinningSensitivityInfo, 0.25f, 0.f, 2.f) + , _rollAngleThreshold(RollThresholdInfo, 0.025f, 0.f, 0.05f, 0.001f) + , _orbitSpeedThreshold(OrbitSpinningThreshold, 0.005f, 0.f, 0.01f, 0.0001f) + , _spinSensitivity(SpinningSensitivityInfo, 0.25f, 0.f, 2.f, 0.01f) , _zoomSensitivityExponential(ZoomSensitivityExpInfo, 1.03f, 1.0f, 1.1f) , _zoomSensitivityProportionalDist(ZoomSensitivityPropInfo, 11.f, 5.f, 50.f) , _zoomSensitivityDistanceThreshold( @@ -294,15 +297,15 @@ TouchInteraction::TouchInteraction() 1000.0, std::numeric_limits::max() ) - , _inputStillThreshold(InputSensitivityInfo, 0.0005f, 0.f, 0.001f) + , _inputStillThreshold(InputSensitivityInfo, 0.0005f, 0.f, 0.001f, 0.0001f) // used to void wrongly interpreted roll interactions - , _centroidStillThreshold(StationaryCentroidInfo, 0.0018f, 0.f, 0.01f) + , _centroidStillThreshold(StationaryCentroidInfo, 0.0018f, 0.f, 0.01f, 0.0001f) , _panEnabled(PanModeInfo, false) , _interpretPan(PanDeltaDistanceInfo, 0.015f, 0.f, 0.1f) , _slerpTime(SlerpTimeInfo, 3.f, 0.1f, 5.f) , _friction( FrictionInfo, - glm::vec4(0.025f, 0.025f, 0.02f, 0.02f), + glm::vec4(0.025f, 0.025f, 0.02f, 0.001f), glm::vec4(0.f), glm::vec4(0.2f) ) @@ -366,6 +369,10 @@ TouchInteraction::TouchInteraction() _time = std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ); + + _reset.onChange([&]() { + resetPropertiesToDefault(); + }); } void TouchInteraction::updateStateFromInput(const std::vector& list, @@ -389,6 +396,7 @@ void TouchInteraction::updateStateFromInput(const std::vector& high_resolution_clock::now().time_since_epoch() ); if ((timestamp - _time).count() < _maxTapTime) { + LINFO("Double tap!"); _doubleTap = true; _tap = false; } @@ -466,7 +474,7 @@ void TouchInteraction::directControl(const std::vector& list) LINFO("DirectControl"); #endif - // finds best transform values for the new camera state and stores them in par + // Find best transform values for the new camera state and store them in par std::vector par(6, 0.0); par[0] = _lastVel.orbit.x; // use _lastVel for orbit par[1] = _lastVel.orbit.y; @@ -474,7 +482,7 @@ void TouchInteraction::directControl(const std::vector& list) int nDof = _solver.nDof(); if (_lmSuccess && !_unitTest) { - // if good values were found set new camera state + // If good values were found set new camera state _vel.orbit = glm::dvec2(par.at(0), par.at(1)); if (nDof > 2) { if (!_disableZoom) { @@ -499,15 +507,18 @@ 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) + // doesn't get consumed sometimes when LMA fails to converge) resetAfterInput(); } } 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", + // @TODO (emmbr 2023-01-31) This hardcoded list should be removed and replaced by something + // else. Either a type of renderable that can always be directly manipulated, or a list + // that can be set in config/assets. Or both? + std::string selectables[31] = { + "Sun", "Mercury", "Venus", "Earth", "Mars", "Ceres", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto", "Moon", "Titan", "Rhea", "Mimas", "Iapetus", "Enceladus", "Dione", "Io", "Ganymede", "Europa", "Callisto", "NewHorizons", "Styx", "Nix", "Kerberos", "Hydra", "Charon", "Tethys", "OsirisRex", "Bennu" @@ -672,7 +683,7 @@ int TouchInteraction::interpretInteraction(const std::vector& distInput = p; } // find the slowest moving finger - used in roll interpretation - double minDiff = 1000; + double minDiff = 1000.0; for (const TouchInputHolder& inputHolder : list) { const auto it = std::find_if( lastProcessed.cbegin(), @@ -707,12 +718,14 @@ int TouchInteraction::interpretInteraction(const std::vector& lastProcessed.end(), [&inputHolder](const TouchInput& input) { return inputHolder.holdsInput(input); - }); - double res = 0.0; + } + ); + double res = 0.0; 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); } @@ -722,6 +735,7 @@ int TouchInteraction::interpretInteraction(const std::vector& else { res = currentAngle - lastAngle; } + if (std::abs(res) < _rollAngleThreshold) { return 1000.0; } @@ -735,6 +749,7 @@ int TouchInteraction::interpretInteraction(const std::vector& _centroid, lastCentroid ) / list.size(); + #ifdef TOUCH_DEBUG_PROPERTIES _debugProperties.normalizedCentroidDistance = normalizedCentroidDistance; _debugProperties.rollOn = rollOn; @@ -748,7 +763,7 @@ int TouchInteraction::interpretInteraction(const std::vector& return PICK; } else if (list.size() == 1) { - return ROT; + return ROTATION; } else { float avgDistance = static_cast(std::abs(dist - lastDist)); @@ -787,13 +802,14 @@ void TouchInteraction::computeVelocities(const std::vector& li const int action = interpretInteraction(list, lastProcessed); const SceneGraphNode* anchor = global::navigationHandler->orbitalNavigator().anchorNode(); + if (!anchor) { return; } #ifdef TOUCH_DEBUG_PROPERTIES const std::map interactionNames = { - { ROT, "Rotation" }, + { ROTATION, "Rotation" }, { PINCH, "Pinch" }, { PAN, "Pan" }, { ROLL, "Roll" }, @@ -819,7 +835,7 @@ void TouchInteraction::computeVelocities(const std::vector& li const float aspectRatio = static_cast(windowSize.x) / static_cast(windowSize.y); switch (action) { - case ROT: { // add rotation velocity + case ROTATION: { // add rotation velocity _vel.orbit += glm::dvec2(inputHolder.speedX() * _sensitivity.orbit.x, inputHolder.speedY() * _sensitivity.orbit.y); @@ -1015,7 +1031,7 @@ void TouchInteraction::step(double dt, bool directTouch) { // rotations // To avoid problem with lookup in up direction const dmat4 lookAtMat = lookAt( - dvec3(0, 0, 0), + dvec3(0.0, 0.0, 0.0), directionToCenter, normalize(camDirection + lookUp) ); @@ -1034,7 +1050,7 @@ void TouchInteraction::step(double dt, bool directTouch) { } { // Panning (local rotation) - const dvec3 eulerAngles(_vel.pan.y * dt, _vel.pan.x * dt, 0); + const dvec3 eulerAngles(_vel.pan.y * dt, _vel.pan.x * dt, 0.0); const dquat rotationDiff = dquat(eulerAngles); localCamRot = localCamRot * rotationDiff; @@ -1046,7 +1062,7 @@ void TouchInteraction::step(double dt, bool directTouch) { } { // Orbit (global rotation) - const dvec3 eulerAngles(_vel.orbit.y * dt, _vel.orbit.x * dt, 0); + const dvec3 eulerAngles(_vel.orbit.y * dt, _vel.orbit.x * dt, 0.0); const dquat rotationDiffCamSpace = dquat(eulerAngles); const dquat rotationDiffWorldSpace = globalCamRot * rotationDiffCamSpace * @@ -1059,8 +1075,9 @@ void TouchInteraction::step(double dt, bool directTouch) { directionToCenter = normalize(-centerToCam); const dvec3 lookUpWhenFacingCenter = globalCamRot * dvec3(_camera->lookUpVectorCameraSpace()); + const dmat4 lookAtMatrix = lookAt( - dvec3(0, 0, 0), + dvec3(0.0, 0.0, 0.0), directionToCenter, lookUpWhenFacingCenter); globalCamRot = normalize(quat_cast(inverse(lookAtMatrix))); @@ -1105,7 +1122,7 @@ void TouchInteraction::step(double dt, bool directTouch) { } const double currentPosDistance = length(centerToCamera); - //Apply the velocity to update camera position + // Apply the velocity to update camera position double zoomVelocity = _vel.zoom; if (!directTouch) { const double distanceFromSurface = @@ -1168,6 +1185,9 @@ void TouchInteraction::step(double dt, bool directTouch) { _camera->setPositionVec3(camPos); _camera->setRotation(globalCamRot * localCamRot); + // Mark that a camera interaction happened + global::navigationHandler->orbitalNavigator().updateOnCameraInteraction(); + #ifdef TOUCH_DEBUG_PROPERTIES //Show velocity status every N frames if (++stepVelUpdate >= 60) { @@ -1183,9 +1203,6 @@ void TouchInteraction::step(double dt, bool directTouch) { _tap = false; _doubleTap = false; _zoomOutTap = false; - if (_reset) { - resetToDefault(); - } } } @@ -1252,11 +1269,10 @@ void TouchInteraction::resetAfterInput() { } // Reset all property values to default -void TouchInteraction::resetToDefault() { +void TouchInteraction::resetPropertiesToDefault() { _unitTest.set(false); _disableZoom.set(false); _disableRoll.set(false); - _reset.set(false); _maxTapTime.set(300); _deceleratesPerSecond.set(240); _touchScreenSize.set(55.0f); diff --git a/modules/touch/touchmodule.cpp b/modules/touch/touchmodule.cpp index 5a2cc816aa..dfa9ada612 100644 --- a/modules/touch/touchmodule.cpp +++ b/modules/touch/touchmodule.cpp @@ -37,133 +37,13 @@ using namespace TUIO; namespace { constexpr openspace::properties::Property::PropertyInfo TouchActiveInfo = { "TouchActive", - "True if we want to use touch input as 3d navigation", + "True if we want to use touch input as 3D navigation", "Use this if we want to turn on or off Touch input navigation. " "Disabling this will reset all current touch inputs to the navigation." }; } 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); - } - - // Set touch property to active (to void mouse input, mainly for mtdev bridges) - _touch.touchActive(!_touchPoints.empty()); - - if (!_touchPoints.empty()) { - global::interactionMonitor->markInteraction(); - } - - // Erase old input id's that no longer exists - _lastTouchInputs.erase( - std::remove_if( - _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) { - _touch.tap(); - _tap = false; - return true; - } - - // Return true if we got new input - 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( - _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); - } - ); - if (!holder->isMoving()) { - newInput = true; - } - }); - return newInput; - } - else { - return false; - } -} - -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; - } - } - } - _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; - } - } -} - TouchModule::TouchModule() : OpenSpaceModule("Touch") , _touchActive(TouchActiveInfo, true) @@ -253,4 +133,124 @@ void TouchModule::internalInitialize(const ghoul::Dictionary& /*dictionary*/){ }); } +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); + } + + // Set touch property to active (to void mouse input, mainly for mtdev bridges) + _touch.touchActive(!_touchPoints.empty()); + + if (!_touchPoints.empty()) { + global::interactionMonitor->markInteraction(); + } + + // Erase old input id's that no longer exists + _lastTouchInputs.erase( + std::remove_if( + _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) { + _touch.tap(); + _tap = false; + return true; + } + + // Return true if we got new input + 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( + _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); + } + ); + if (!holder->isMoving()) { + newInput = true; + } + }); + return newInput; + } + else { + return false; + } +} + +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; + } + } + } + _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; + } + } +} + } // namespace openspace diff --git a/modules/touch/touchmodule.h b/modules/touch/touchmodule.h index 740e60532c..d217cd4972 100644 --- a/modules/touch/touchmodule.h +++ b/modules/touch/touchmodule.h @@ -33,7 +33,7 @@ namespace openspace { - class TuioEar; +class TuioEar; #ifdef WIN32 class Win32TouchHook; diff --git a/modules/webbrowser/src/eventhandler.cpp b/modules/webbrowser/src/eventhandler.cpp index 0c4e4312dc..5d81cb3295 100644 --- a/modules/webbrowser/src/eventhandler.cpp +++ b/modules/webbrowser/src/eventhandler.cpp @@ -230,11 +230,11 @@ void EventHandler::initialize() { BrowserInstance::SingleClick ); #endif - _validTouchStates.emplace_back(input); - } - else { - _validTouchStates.emplace_back(input); } + + _validTouchStates.emplace_back(input); + + global::interactionMonitor->markInteraction(); return true; } ); @@ -269,6 +269,7 @@ void EventHandler::initialize() { _leftButton.down = true; _browserInstance->sendMouseMoveEvent(mouseEvent()); #endif // WIN32 + global::interactionMonitor->markInteraction(); return true; } else if (it != _validTouchStates.cend()) {