diff --git a/modules/touch/ext/levmarq.cpp b/modules/touch/ext/levmarq.cpp index ce924fb92e..2fecba156b 100644 --- a/modules/touch/ext/levmarq.cpp +++ b/modules/touch/ext/levmarq.cpp @@ -23,10 +23,14 @@ OTHER DEALINGS IN THE SOFTWARE. #include #include +#include #include +#include -#define TOL 1e-30 // smallest value allowed in cholesky_decomp() - +namespace { + std::chrono::milliseconds TimeLimit(200); + double TOL = 1e-30; // smallest value allowed in cholesky_decomp() +} // set parameters required by levmarq() to default values void levmarq_init(LMstat *lmstat) { @@ -120,8 +124,19 @@ bool levmarq(int npar, double *par, int ny, double *dysq, lmstat->pos.clear(); err = error_func(par, ny, dysq, func, fdata, lmstat); + std::chrono::system_clock::time_point start = + std::chrono::system_clock::now(); + // main iteration for (it = 0; it < nit; it++) { + std::chrono::system_clock::time_point now = + std::chrono::system_clock::now(); + + if (now - start > TimeLimit) { + LDEBUGC("Touch Levmarq", "Bail out due to time limit!"); + return false; + } + // calculate the approximation to the Hessian and the "derivative" d for (i = 0; i < npar; i++) { d[i] = 0; diff --git a/modules/touch/include/touchinteraction.h b/modules/touch/include/touchinteraction.h index 708c48b5ed..1d55b81793 100644 --- a/modules/touch/include/touchinteraction.h +++ b/modules/touch/include/touchinteraction.h @@ -37,6 +37,8 @@ #include #include +//#define TOUCH_DEBUG_PROPERTIES + namespace openspace { class Camera; @@ -170,13 +172,34 @@ private: properties::FloatProperty _rollAngleThreshold; properties::FloatProperty _orbitSpeedThreshold; properties::FloatProperty _spinSensitivity; + properties::FloatProperty _zoomSensitivity; + properties::FloatProperty _zoomSensitivityDistanceThreshold; + properties::FloatProperty _zoomBoundarySphereMultiplier; properties::FloatProperty _inputStillThreshold; properties::FloatProperty _centroidStillThreshold; + properties::BoolProperty _panEnabled; properties::FloatProperty _interpretPan; properties::FloatProperty _slerpTime; properties::IVec2Property _guiButton; properties::Vec4Property _friction; properties::FloatProperty _pickingRadiusMinimum; + properties::BoolProperty _ignoreGui; + +#ifdef TOUCH_DEBUG_PROPERTIES + struct DebugProperties : PropertyOwner { + DebugProperties(); + properties::StringProperty interactionMode; + properties::IntProperty nFingers; + properties::StringProperty interpretedInteraction; + properties::FloatProperty normalizedCentroidDistance; + properties::FloatProperty minDiff; + properties::FloatProperty rollOn; + } _debugProperties; + + int pinchConsecCt = 0; + double pinchConsecZoomFactor = 0; + //int stepVelUpdate = 0; +#endif // Class variables VelocityStates _vel; diff --git a/modules/touch/include/touchmarker.h b/modules/touch/include/touchmarker.h index 0da92619a0..b3aa625576 100644 --- a/modules/touch/include/touchmarker.h +++ b/modules/touch/include/touchmarker.h @@ -67,6 +67,7 @@ private: std::unique_ptr _shader; UniformCache(radius, transparency, thickness, color) _uniformCache; + std::vector _vertexData; GLuint _quad = 0; GLuint _vertexPositionBuffer = 0; int _numFingers = 0; diff --git a/modules/touch/include/tuioear.h b/modules/touch/include/tuioear.h index 8eb738fc92..9121744fe2 100644 --- a/modules/touch/include/tuioear.h +++ b/modules/touch/include/tuioear.h @@ -104,7 +104,7 @@ class TuioEar : public TUIO::TuioListener { void clearInput(); private: - bool _tap; + bool _tap = false; TUIO::TuioCursor _tapCo = TUIO::TuioCursor(-1, -1, -1.0f, -1.0f); std::mutex _mx; diff --git a/modules/touch/src/touchinteraction.cpp b/modules/touch/src/touchinteraction.cpp index cffb2dab72..e694c39505 100644 --- a/modules/touch/src/touchinteraction.cpp +++ b/modules/touch/src/touchinteraction.cpp @@ -61,7 +61,6 @@ #pragma warning (pop) #endif // WIN32 - #include #include @@ -141,6 +140,24 @@ namespace { "" // @TODO Missing documentation }; + static const openspace::properties::Property::PropertyInfo ZoomSensitivityInfo = { + "ZoomSensitivity", + "Sensitivity of exponential zooming in relation to distance from focus node", + "" // @TODO Missing documentation + }; + + static const openspace::properties::Property::PropertyInfo ZoomSensitivityDistanceThresholdInfo = { + "ZoomSensitivityDistanceThreshold", + "Threshold of distance to target node for whether or not to use exponential zooming", + "" // @TODO Missing documentation + }; + + static const openspace::properties::Property::PropertyInfo ZoomBoundarySphereMultiplierInfo = { + "ZoomBoundarySphereMultiplier", + "Multiplies a node's boundary sphere by this in order to limit zoom & prevent surface collision", + "" // @TODO Missing documentation + }; + static const openspace::properties::Property::PropertyInfo InputSensitivityInfo = { "InputSensitivity", "Threshold for interpreting input as still", @@ -153,6 +170,12 @@ namespace { "" // @TODO Missing documentation }; + static const openspace::properties::Property::PropertyInfo PanModeInfo = { + "PanMode", + "Allow panning gesture", + "" // @TODO Missing documentation + }; + static const openspace::properties::Property::PropertyInfo PanDeltaDistanceInfo = { "PanDeltaDistance", "Delta distance between fingers allowed for interpreting pan interaction", @@ -202,9 +225,13 @@ TouchInteraction::TouchInteraction() , _rollAngleThreshold(RollThresholdInfo, 0.025f, 0.f, 0.05f) , _orbitSpeedThreshold(OrbitSpinningThreshold, 0.005f, 0.f, 0.01f) , _spinSensitivity(SpinningSensitivityInfo, 1.f, 0.f, 2.f) + , _zoomSensitivity(ZoomSensitivityInfo, 1.025f, 1.0f, 1.1f) + , _zoomSensitivityDistanceThreshold(ZoomSensitivityDistanceThresholdInfo, 0.05, 0.01, 0.25) + , _zoomBoundarySphereMultiplier(ZoomBoundarySphereMultiplierInfo, 1.001, 1.0, 1.01) , _inputStillThreshold(InputSensitivityInfo, 0.0005f, 0.f, 0.001f) // used to void wrongly interpreted roll interactions , _centroidStillThreshold(StationaryCentroidInfo, 0.0018f, 0.f, 0.01f) + , _panEnabled(PanModeInfo, false) , _interpretPan(PanDeltaDistanceInfo, 0.015f, 0.f, 0.1f) , _slerpTime(SlerpTimeInfo, 3.f, 0.f, 5.f) , _guiButton( @@ -215,11 +242,20 @@ TouchInteraction::TouchInteraction() ) , _friction( FrictionInfo, - glm::vec4(0.01f, 0.025f, 0.02f, 0.02f), + glm::vec4(0.025f, 0.025f, 0.02f, 0.02f), glm::vec4(0.f), glm::vec4(0.2f) ) - , _pickingRadiusMinimum(PickingRadiusInfo, 0.1f, 0.f, 1.f) + , _pickingRadiusMinimum( + { "Picking Radius", "Minimum radius for picking in NDC coordinates", "" }, + 0.1f, + 0.f, + 1.f + ) + , _ignoreGui( + { "Ignore GUI", "Disable GUI touch interaction", "" }, // @TODO Missing documentation + false + ) , _vel{ glm::dvec2(0.0), 0.0, 0.0, glm::dvec2(0.0) } , _sensitivity{ glm::dvec2(0.08, 0.045), 4.0, 2.75, glm::dvec2(0.08, 0.045) } // calculated with two vectors with known diff in length, then @@ -234,6 +270,9 @@ TouchInteraction::TouchInteraction() , _doubleTap(false) , _lmSuccess(true) , _guiON(false) +#ifdef TOUCH_DEBUG_PROPERTIES + , _debugProperties() +#endif , _centroid(glm::dvec3(0.0)) { addProperty(_touchActive); @@ -247,13 +286,22 @@ TouchInteraction::TouchInteraction() addProperty(_rollAngleThreshold); addProperty(_orbitSpeedThreshold); addProperty(_spinSensitivity); + addProperty(_zoomSensitivity); + addProperty(_zoomSensitivityDistanceThreshold); + addProperty(_zoomBoundarySphereMultiplier); addProperty(_inputStillThreshold); addProperty(_centroidStillThreshold); + addProperty(_panEnabled); addProperty(_interpretPan); addProperty(_slerpTime); addProperty(_guiButton); addProperty(_friction); addProperty(_pickingRadiusMinimum); + addProperty(_ignoreGui); + +#ifdef TOUCH_DEBUG_PROPERTIES + addPropertySubOwner(_debugProperties); +#endif _origin.onChange([this]() { SceneGraphNode* node = sceneGraphNode(_origin.value()); @@ -276,6 +324,9 @@ TouchInteraction::TouchInteraction() 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) { _doubleTap = true; @@ -286,12 +337,18 @@ void TouchInteraction::updateStateFromInput(const std::vector& list, if (!guiMode(list)) { if (_directTouchMode && _selected.size() > 0 && list.size() == _selected.size()) { +#ifdef TOUCH_DEBUG_PROPERTIES + _debugProperties.interactionMode = "Direct"; +#endif directControl(list); } if (_lmSuccess) { findSelectedNode(list); } if (!_directTouchMode) { +#ifdef TOUCH_DEBUG_PROPERTIES + _debugProperties.interactionMode = "Velocities"; +#endif computeVelocities(list, lastProcessed); } @@ -303,6 +360,9 @@ void TouchInteraction::updateStateFromInput(const std::vector& list, // Activates/Deactivates gui input mode (if active it voids all other interactions) bool TouchInteraction::guiMode(const std::vector& list) { + if (_ignoreGui) { + return false; + } WindowWrapper& wrapper = OsEng.windowWrapper(); glm::ivec2 res = wrapper.currentWindowSize(); glm::dvec2 pos = glm::vec2( @@ -340,7 +400,9 @@ void TouchInteraction::directControl(const std::vector& list) { _vel.zoom = 0.0; _vel.roll = 0.0; _vel.pan = glm::dvec2(0.0, 0.0); - +#ifdef TOUCH_DEBUG_PROPERTIES + LINFO("DirectControl"); +#endif // Returns the screen point s(xi,par) dependent the transform M(par) and object // point xi auto distToMinimize = [](double* par, int x, void* fdata, LMstat* lmstat) { @@ -519,11 +581,15 @@ void TouchInteraction::directControl(const std::vector& list) { list.end(), [&sb](const TuioCursor& c) { return c.getSessionID() == sb.id; } ); - // normalized -1 to 1 coordinates on screen - screenPoints.push_back( - glm::dvec2(2 * (c->getX() - 0.5), -2 * (c->getY() - 0.5)) - ); + if (c != list.end()) { + screenPoints.push_back(glm::dvec2(2 * (c->getX() - 0.5), -2 * (c->getY() - 0.5))); // normalized -1 to 1 coordinates on screen + } else { + OsEng.moduleEngine().module()->touchInput = { 1, glm::dvec2(0.0, 0.0), 1 }; + resetAfterInput(); + return; + } } + FunctionData fData = { selectedPoints, screenPoints, @@ -541,7 +607,7 @@ void TouchInteraction::directControl(const std::vector& list) { _lmSuccess = levmarq( nDOF, par.data(), - nFingers, + screenPoints.size(), nullptr, distToMinimize, gradient, @@ -555,7 +621,7 @@ void TouchInteraction::directControl(const std::vector& list) { if (nDOF > 2) { _vel.zoom = par.at(2); _vel.roll = par.at(3); - if (nDOF > 4) { + if (_panEnabled && nDOF > 4) { _vel.roll = 0.0; _vel.pan = glm::dvec2(par.at(4), par.at(5)); } @@ -764,12 +830,20 @@ int TouchInteraction::interpretInteraction(const std::vector& list, double minDiff = 1000; long id = 0; for (const TuioCursor& c : list) { - TuioPoint itPoint = std::find_if( + auto it = std::find_if( lastProcessed.begin(), lastProcessed.end(), - [&c](const Point& p) { return p.first == c.getSessionID(); } - )->second; + [&c](const Point& p) { + return p.first == c.getSessionID(); + }); + + if (it == lastProcessed.end()) { + continue; + } + + TuioPoint itPoint = it->second; double diff = c.getX() - itPoint.getX() + c.getY() - itPoint.getY(); + if (!c.isMoving()) { diff = minDiff = 0.0; id = c.getSessionID(); @@ -817,6 +891,13 @@ int TouchInteraction::interpretInteraction(const std::vector& list, } ); + double normalizedCentroidDistance = glm::distance(_centroid, lastCentroid) / list.size(); +#ifdef TOUCH_DEBUG_PROPERTIES + _debugProperties.normalizedCentroidDistance = normalizedCentroidDistance; + _debugProperties.rollOn = rollOn; + _debugProperties.minDiff = minDiff; +#endif + if (_doubleTap) { return PICK; } @@ -828,17 +909,17 @@ int TouchInteraction::interpretInteraction(const std::vector& list, std::abs(dist - lastDist) / list.at(0).getMotionSpeed() ); // if average distance between 3 fingers are constant we have panning - if (avgDistance < _interpretPan && list.size() == 3) { + if (_panEnabled && (std::abs(dist - lastDist) / list.at(0).getMotionSpeed() < _interpretPan && list.size() == 3)) { return PAN; } + // we have roll if one finger is still, or the total roll angles around the // centroid is over _rollAngleThreshold (_centroidStillThreshold is used to void // misinterpretations) else if (std::abs(minDiff) < _inputStillThreshold || - (std::abs(rollOn) < 100.0 && - glm::distance(_centroid, lastCentroid) / list.size() - < _centroidStillThreshold)) - { + (std::abs(rollOn) < 100.0 && + normalizedCentroidDistance < _centroidStillThreshold)) { + return ROLL; } else { @@ -852,7 +933,25 @@ void TouchInteraction::computeVelocities(const std::vector& list, const std::vector& lastProcessed) { TuioCursor cursor = list.at(0); - int action = interpretInteraction(list, lastProcessed); + const int action = interpretInteraction(list, lastProcessed); + +#ifdef TOUCH_DEBUG_PROPERTIES + const std::map interactionNames = { + {ROT, "Rotation"}, + {PINCH, "Pinch"}, + {PAN, "Pan"}, + {ROLL, "Roll"}, + {PICK, "Pick"} + }; + _debugProperties.interpretedInteraction = interactionNames.at(action); + + if (pinchConsecCt > 0 && action != PINCH) { + if( pinchConsecCt > 3 ) + LINFO("PINCH_gesture_ended_with " << pinchConsecZoomFactor << " drag_distance_and " << pinchConsecCt << " counts."); + pinchConsecCt = 0; + pinchConsecZoomFactor = 0.0; + } +#endif switch (action) { case ROT: { // add rotation velocity @@ -887,9 +986,19 @@ void TouchInteraction::computeVelocities(const std::vector& list, } ) / lastProcessed.size(); - double zoomFactor = (distance - lastDistance) * - (glm::distance(_camera->positionVec3(), _camera->focusPositionVec3()) - - _focusNode->boundingSphere()); + glm::dvec3 camPos = _camera->positionVec3(); + glm::dvec3 centerPos = _focusNode->worldPosition(); + glm::dvec3 currDistanceToFocusNode = camPos - centerPos; + + double distanceFromFocusSurface = length(currDistanceToFocusNode) - _focusNode->boundingSphere(); + double zoomFactor = (distance - lastDistance); +#ifdef TOUCH_DEBUG_PROPERTIES + pinchConsecCt++; + pinchConsecZoomFactor += zoomFactor; +#endif + if ((length(currDistanceToFocusNode) / distanceFromFocusSurface) > _zoomSensitivityDistanceThreshold) { + zoomFactor *= pow(distanceFromFocusSurface, (float)_zoomSensitivity); + } _vel.zoom += zoomFactor * _sensitivity.zoom * std::max(_touchScreenSize.value() * 0.1, 1.0); break; @@ -967,6 +1076,7 @@ void TouchInteraction::computeVelocities(const std::vector& list, _vel.zoom = _sensitivity.zoom * std::max(_touchScreenSize.value() * 0.1, 1.0) * _tapZoomFactor * dist; + } break; } @@ -1048,16 +1158,20 @@ void TouchInteraction::step(double dt) { } { // Zooming centerToBoundingSphere = -directionToCenter * boundingSphere; - dvec3 centerToCam = camPos - centerPos; - double distToSurface = length(centerToCam - centerToBoundingSphere); - - if (length(_vel.zoom * dt) < distToSurface && - length(centerToCam + directionToCenter*_vel.zoom * dt) > - length(centerToBoundingSphere)) + centerToCamera = camPos - centerPos; + double planetBoundaryRadius = length(centerToBoundingSphere); + planetBoundaryRadius *= _zoomBoundarySphereMultiplier; + double distToSurface = length(centerToCamera - planetBoundaryRadius); + if (length(_vel.zoom*dt) < distToSurface && + length(centerToCamera + directionToCenter*_vel.zoom*dt) + > planetBoundaryRadius) { camPos += directionToCenter * _vel.zoom * dt; } else { +#ifdef TOUCH_DEBUG_PROPERTIES + LINFO("Zero the zoom velocity close to surface."); +#endif _vel.zoom = 0.0; } } @@ -1067,6 +1181,14 @@ void TouchInteraction::step(double dt) { _camera->setPositionVec3(camPos); _camera->setRotation(globalCamRot * localCamRot); +#ifdef TOUCH_DEBUG_PROPERTIES + //Show velocity status every N frames + /*if (++stepVelUpdate >= 60) { + stepVelUpdate = 0; + LINFO("DistToFocusNode " << length(centerToCamera) << " stepZoomVelUpdate " << _vel.zoom); + }*/ +#endif + _tap = false; _doubleTap = false; if (_reset) { @@ -1127,7 +1249,7 @@ void TouchInteraction::decelerate(double dt) { // time slack over from last frame int times = static_cast((dt + _timeSlack) / frequency); // Save the new time slack for the next frame - _timeSlack = fmod((dt + _timeSlack), frequency); + _timeSlack = fmod((dt + _timeSlack), frequency) * frequency; // Decelerate zoom velocity quicker if we're close enough to use direct-manipulation if (!_directTouchMode && _currentRadius > _nodeRadiusThreshold && @@ -1143,6 +1265,10 @@ void TouchInteraction::decelerate(double dt) { // Called if all fingers are off the screen void TouchInteraction::resetAfterInput() { +#ifdef TOUCH_DEBUG_PROPERTIES + _debugProperties.nFingers = 0; + _debugProperties.interactionMode = "None"; +#endif if (_directTouchMode && _selected.size() > 0 && _lmSuccess) { double spinDelta = _spinSensitivity / OsEng.windowWrapper().averageDeltaTime(); if (glm::length(_lastVel.orbit) > _orbitSpeedThreshold) { @@ -1190,12 +1316,13 @@ void TouchInteraction::resetToDefault() { _rollAngleThreshold.set(0.025f); _orbitSpeedThreshold.set(0.005f); _spinSensitivity.set(1.0f); + _zoomSensitivity.set(1.025f); _inputStillThreshold.set(0.0005f); _centroidStillThreshold.set(0.0018f); _interpretPan.set(0.015f); _slerpTime.set(3.0f); _guiButton.set(glm::ivec2(32, 64)); - _friction.set(glm::vec4(0.01, 0.025, 0.02, 0.02)); + _friction.set(glm::vec4(0.025, 0.025, 0.02, 0.02)); } void TouchInteraction::tap() { @@ -1221,4 +1348,41 @@ void TouchInteraction::setFocusNode(SceneGraphNode* focusNode) { _focusNode = focusNode; } +#ifdef TOUCH_DEBUG_PROPERTIES +TouchInteraction::DebugProperties::DebugProperties() + : properties::PropertyOwner({ "TouchDebugProperties" }) + , interactionMode( + { "interactionMode", "Current interaction mode", "" }, + "Unknown" + ) + , nFingers( + {"nFingers", "Number of fingers", ""}, + 0, 0, 20 + ) + , interpretedInteraction( + { "interpretedInteraction", "Interpreted interaction", "" }, + "Unknown" + ) + , normalizedCentroidDistance( + { "normalizedCentroidDistance", "Normalized Centroid Distance", "" }, + 0.f, 0.f, 0.01f + ) + , minDiff( + { "minDiff", "Movement of slowest moving finger", "" }, + 0.f, 0.f, 100.f + ) + , rollOn( + { "rollOn", "Roll On", "" }, + 0.f, 0.f, 100.f + ) +{ + addProperty(interactionMode); + addProperty(nFingers); + addProperty(interpretedInteraction); + addProperty(normalizedCentroidDistance); + addProperty(minDiff); + addProperty(rollOn); +} +#endif + } // openspace namespace diff --git a/modules/touch/src/touchmarker.cpp b/modules/touch/src/touchmarker.cpp index 59bebe059d..4f350c0892 100644 --- a/modules/touch/src/touchmarker.cpp +++ b/modules/touch/src/touchmarker.cpp @@ -79,7 +79,6 @@ TouchMarker::TouchMarker() glm::vec3(1.f) ) , _shader(nullptr) - , _numFingers(0) { addProperty(_visible); addProperty(_radiusSize); @@ -135,25 +134,25 @@ void TouchMarker::render(const std::vector& list) { glEnable(GL_PROGRAM_POINT_SIZE); // Enable gl_PointSize in vertex shader glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); glBindVertexArray(_quad); - glDrawArrays(GL_POINTS, 0, _numFingers); + glDrawArrays(GL_POINTS, 0, _vertexData.size() / 2); _shader->deactivate(); } } void TouchMarker::createVertexList(const std::vector& list) { - _numFingers = static_cast(list.size()); - GLfloat vertexData[MAX_FINGERS]; + _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); + _vertexData[i] = 2 * (c.getX() - 0.5f); + _vertexData[i + 1] = -2 * (c.getY() - 0.5f); i += 2; } glBindVertexArray(_quad); glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, _vertexData.size() * sizeof(GLfloat), _vertexData.data(), GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer( 0,