Feature/internal touchhandling (#1038)

* Removal of dead code and compiler warnings

* Added basic internal touch

This commit only adds the description-shell of the touch implementation

* Added callbacks and first WIP of internal touch

Makes use of the TouchInput/TouchInputs class in the TouchModule.
Internally we cache the TouchInputs as an input deque and utilizes it
for motion-vectors.
This commit has bugs and issues, which will be worked upon.

* Happy new year!

Bumped year on branch-local files

* Improvements to internal touch

Almost reached feature-parity with tuio-handled touch events

- Added most of the touch-logic to touchinteraction
- Added helper functions to new TouchInput/TouchInputs classes

* Naming changes to touch interface

* Translate TUIO to TouchInput

This commit translates TUIO messages to an internal TouchInput structure
while still trying to keep feature parity.
Removed TUIO-dependencies from many files.
Changed behavior on tuioear to lock-swap its content.

* Minor cleanup and fixes

- Should fix touch roll
- Simplified some functions

* Build fix

* Use internal touch in webgui

- Added consume-logic to touch callbacks
- Constrained touch-input to either webgui or 3D application as mouse is
  - This fixes some flaws with previous implementation,
    such as ghost inputs

- Initialize touchmodule through init-functions rather than constructor

* Cleanup of comments

* Simplified touch classes

Added timestamp through constructor meaning no more sprinkled timestamps
Renamed TouchInputs to TouchInputHolder for clarity
Added helper functions to the Holder to see if it holds an input
Remade addInput as tryAddInput which return true on successful insertion
+ other cleanup

* Code style cleanup and tweaks

Removed avoidable zero-comparison for code clarity
Cleanup of code style

* Added comments to DirectInputSolver

Clarifying the use of the DirectInputSolver.

* Changes for coding style
Change SGCT version to make it checkout-able

* Clarify magic bitmask

* const -> constexpr const for magic bitmasks

Co-authored-by: Alexander Bock <mail@alexanderbock.eu>
This commit is contained in:
Mikael Pettersson
2020-01-13 08:27:13 +01:00
committed by GitHub
parent a1a4e56b5b
commit 4e75b161db
23 changed files with 1191 additions and 822 deletions

View File

@@ -27,6 +27,7 @@
#include <openspace/util/keys.h>
#include <openspace/util/mouse.h>
#include <openspace/util/touch.h>
#include <functional>
#include <vector>
@@ -53,6 +54,10 @@ std::vector<std::function<bool(MouseButton, MouseAction, KeyModifier)>>& gMouseB
std::vector<std::function<void(double, double)>>& gMousePosition();
std::vector<std::function<bool(double, double)>>& gMouseScrollWheel();
std::vector<std::function<bool(TouchInput)>>& gTouchDetected();
std::vector<std::function<bool(TouchInput)>>& gTouchUpdated();
std::vector<std::function<void(TouchInput)>>& gTouchExit();
} // namespace detail
namespace callback {
@@ -76,7 +81,12 @@ static std::vector<std::function<void(double, double)>>& mousePosition =
detail::gMousePosition();
static std::vector<std::function<bool(double, double)>>& mouseScrollWheel =
detail::gMouseScrollWheel();
static std::vector<std::function<bool(TouchInput)>>& touchDetected =
detail::gTouchDetected();
static std::vector<std::function<bool(TouchInput)>>& touchUpdated =
detail::gTouchUpdated();
static std::vector<std::function<void(TouchInput)>>& touchExit =
detail::gTouchExit();
/**
* If the framerate becomes slow, Chromium Embedded Framework (used in Web Browser Module)

View File

@@ -28,6 +28,7 @@
#include <openspace/properties/stringproperty.h>
#include <openspace/util/keys.h>
#include <openspace/util/mouse.h>
#include <openspace/util/touch.h>
#include <openspace/util/versionchecker.h>
#include <ghoul/glm.h>
#include <memory>
@@ -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<char> encode();
void decode(std::vector<char> data);
@@ -105,7 +108,6 @@ private:
void loadFonts();
void runGlobalCustomizationScripts();
void configureLogging();
std::unique_ptr<Scene> _scene;
std::unique_ptr<AssetManager> _assetManager;

View File

@@ -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();

View File

@@ -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 <glm/detail/type_vec2.hpp>
#include <cstdint>
#include <deque>
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<TouchInput>& peekInputs() const;
private:
//A deque of recorded inputs. Adding newer points to the front of the queue
std::deque<TouchInput> _inputs;
size_t _touchDeviceId;
size_t _fingerId;
};
} // namespace openspace
#endif // __OPENSPACE_CORE___TOUCH___H__

View File

@@ -25,8 +25,8 @@
#ifndef __OPENSPACE_MODULE_TOUCH___DIRECTINPUT_SOLVER___H__
#define __OPENSPACE_MODULE_TOUCH___DIRECTINPUT_SOLVER___H__
#include <openspace/util/touch.h>
#include <modules/touch/ext/levmarq.h>
#include <modules/touch/ext/libTUIO11/TUIO/TuioCursor.h>
#include <vector>
@@ -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<TUIO::TuioCursor>& 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<TouchInputHolder>& list,
const std::vector<SelectedBody>& selectedBodies,
std::vector<double>* calculatedValues, const Camera& camera);
int getNDof() const;
const LMstat& getLevMarqStat();
int nDof() const;
const LMstat& levMarqStat();
void setLevMarqVerbosity(bool verbose);
private:

View File

@@ -28,8 +28,6 @@
#include <openspace/properties/propertyowner.h>
#include <modules/touch/include/directinputsolver.h>
#include <modules/touch/include/tuioear.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/doubleproperty.h>
@@ -37,7 +35,7 @@
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/vector/vec4property.h>
#include <chrono>
#include <memory>
//#define TOUCH_DEBUG_PROPERTIES
@@ -66,8 +64,6 @@ private:
class TouchInteraction : public properties::PropertyOwner {
public:
using Point = std::pair<int, TUIO::TuioPoint>;
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<TUIO::TuioCursor>& list,
std::vector<Point>& lastProcessed);
void updateStateFromInput(const std::vector<TouchInputHolder>& list,
std::vector<TouchInput>& 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<TUIO::TuioCursor>& 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<TUIO::TuioCursor>& 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<TUIO::TuioCursor>& list);
void directControl(const std::vector<TouchInputHolder>& 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<TUIO::TuioCursor>& list);
void findSelectedNode(const std::vector<TouchInputHolder>& 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<TUIO::TuioCursor>& list,
const std::vector<Point>& lastProcessed);
int interpretInteraction(const std::vector<TouchInputHolder>& list,
const std::vector<TouchInput>& lastProcessed);
// Compute new velocity according to the interpreted action
void computeVelocities(const std::vector<TUIO::TuioCursor>& list,
const std::vector<Point>& lastProcessed);
void computeVelocities(const std::vector<TouchInputHolder>& list,
const std::vector<TouchInput>& 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<DirectInputSolver::SelectedBody> _selected;
SceneGraphNode* _pickingSelected = nullptr;
DirectInputSolver _solver;
glm::dquat _toSlerp;
glm::dvec3 _centroid;
glm::vec2 _centroid = glm::vec2(0.f);
FrameTimeAverage _frameTimeAvg;

View File

@@ -25,9 +25,6 @@
#ifndef __OPENSPACE_MODULE_TOUCH___TOUCH_MARKER___H__
#define __OPENSPACE_MODULE_TOUCH___TOUCH_MARKER___H__
#include <modules/touch/include/tuioear.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <openspace/rendering/renderable.h>
#include <openspace/properties/propertyowner.h>
#include <openspace/properties/vector/vec3property.h>
@@ -35,10 +32,10 @@
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <openspace/util/touch.h>
#include <ghoul/glm.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/uniformcache.h>
#include <memory>
#include <vector>
@@ -54,10 +51,10 @@ public:
void initialize();
void deinitialize();
void render(const std::vector<TUIO::TuioCursor>& list);
void render(const std::vector<openspace::TouchInputHolder>& list);
private:
void createVertexList(const std::vector<TUIO::TuioCursor>& list);
void createVertexList(const std::vector<openspace::TouchInputHolder>& list);
properties::BoolProperty _visible;
properties::FloatProperty _radiusSize;

View File

@@ -38,8 +38,6 @@
#include <modules/touch/ext/libTUIO11/TUIO/TuioListener.h>
#include <modules/touch/ext/libTUIO11/TUIO/TuioClient.h>
#include <modules/touch/ext/libTUIO11/TUIO/UdpReceiver.h>
#include <modules/touch/ext/libTUIO11/TUIO/TcpReceiver.h>
#if (defined(__GNUC__) && !defined(__clang__))
#pragma GCC diagnostic pop
@@ -48,72 +46,52 @@
#pragma clang diagnostic pop
#endif // __clang__
#include <openspace/util/touch.h>
#include <ghoul/glm.h>
#include <algorithm>
#include <math.h>
#include <vector>
#include <mutex>
#include <numeric>
#include <algorithm>
#include <vector>
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<TouchInput> takeInput();
std::vector<TouchInput> 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<TUIO::TuioCursor> 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<TUIO::TuioCursor> _list;
/**
* A list that tracks all of the cursor ID's that got removed since last frame
*/
std::vector<long> _removeList;
std::vector<TouchInput> _inputList;
std::vector<TouchInput> _removalList;
std::mutex _mx;
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_TOUCH___TUIO_EAR___H__

View File

@@ -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<TUIO::TuioCursor>& list,
bool DirectInputSolver::solve(const std::vector<TouchInputHolder>& list,
const std::vector<SelectedBody>& selectedBodies,
std::vector<double>* parameters, const Camera& camera)
std::vector<double>* parameters, const Camera& camera)
{
int nFingers = std::min(static_cast<int>(list.size()), 3);
_nDof = std::min(nFingers * 2, 6);
@@ -216,29 +223,9 @@ bool DirectInputSolver::solve(const std::vector<TUIO::TuioCursor>& 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<TuioCursor>::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<ImGUIModule>()->touchInput = {
// true,
// glm::dvec2(0.0, 0.0),
// 1
// };
// resetAfterInput();
// return;
// }
}
FunctionData fData = {
@@ -265,11 +252,11 @@ bool DirectInputSolver::solve(const std::vector<TUIO::TuioCursor>& list,
return result;
}
int DirectInputSolver::getNDof() const {
int DirectInputSolver::nDof() const {
return _nDof;
}
const LMstat& DirectInputSolver::getLevMarqStat() {
const LMstat& DirectInputSolver::levMarqStat() {
return _lmstat;
}

View File

@@ -24,39 +24,35 @@
#include <openspace/engine/globals.h>
#include <modules/touch/include/touchinteraction.h>
#include <modules/touch/include/directinputsolver.h>
#include <modules/imgui/imguimodule.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scene/scene.h>
#include <openspace/util/time.h>
#include <openspace/util/keys.h>
#include <ghoul/misc/invariants.h>
#include <ghoul/logging/logmanager.h>
#include <openspace/util/camera.h>
#include <openspace/util/updatestructures.h>
#include <glm/gtx/quaternion.hpp>
#ifdef OPENSPACE_MODULE_GLOBEBROWSING_ENABLED
#include <modules/globebrowsing/src/basictypes.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#endif
#ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED
#include <modules/webbrowser/webbrowsermodule.h>
#endif
#include <cmath>
#include <modules/imgui/imguimodule.h>
#include <modules/touch/include/touchinteraction.h>
#include <modules/touch/include/directinputsolver.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/camera.h>
#include <openspace/util/keys.h>
#include <openspace/util/time.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/invariants.h>
#include <glm/gtx/quaternion.hpp>
#include <cmath>
#include <functional>
#include <fstream>
#include <numeric>
#ifdef WIN32
#pragma warning (push)
@@ -69,10 +65,6 @@
#pragma warning (pop)
#endif // WIN32
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/navigationhandler.h>
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::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch()
);
}
// Called each frame if there is any input
void TouchInteraction::updateStateFromInput(const std::vector<TuioCursor>& list,
std::vector<Point>& lastProcessed)
void TouchInteraction::updateStateFromInput(const std::vector<TouchInputHolder>& list,
std::vector<TouchInput>& 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<milliseconds>(
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<int>(
res.x * (1.0f - bottomCornerSizeForZoomTap_fraction)
const int zoomTapThresholdX = static_cast<int>(
res.x * (1.f - bottomCornerSizeForZoomTap_fraction)
);
int zoomTapThresholdY = static_cast<int>(
res.y * (1.0f - bottomCornerSizeForZoomTap_fraction)
const int zoomTapThresholdY = static_cast<int>(
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<TuioCursor>& 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<TuioCursor>& 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<TuioCursor>& 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<WebBrowserModule>());
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<TuioCursor>& 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<ImGUIModule>());
_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<TuioCursor>& list) {
LINFO(fmt::format(
"GUI mode is {}. Inside box by: ({}%, {}%)",
_guiON ? "activated" : "deactivated",
static_cast<int>(100 * (pos.x / _guiButton.value().x)),
static_cast<int>(100 * (pos.y / _guiButton.value().y))
static_cast<int>(100 * (screenPosition.x / _guiButton.value().x)),
static_cast<int>(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<TuioCursor>& list) {
void TouchInteraction::directControl(const std::vector<TouchInputHolder>& 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<TuioCursor>& list) {
// finds best transform values for the new camera state and stores them in par
std::vector<double> 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<TuioCursor>& 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<ImGUIModule>()->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<ImGUIModule>()->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<TuioCursor>& list) {
//trim list to only contain visible nodes that make sense
void TouchInteraction::findSelectedNode(const std::vector<TouchInputHolder>& 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<TuioCursor>& list) {
glm::dquat camToWorldSpace = _camera->rotationQuaternion();
glm::dvec3 camPos = _camera->positionVec3();
std::vector<DirectInputSolver::SelectedBody> newSelected;
//node & distance
std::tuple<SceneGraphNode*, double> currentlyPicked = {
// node & distance
std::pair<SceneGraphNode*, double> currentlyPicked = {
nullptr,
std::numeric_limits<double>::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<double>(node->boundingSphere()) *
static_cast<double>(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<TuioCursor>& 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<TuioCursor>& 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<TuioCursor>& 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<TuioCursor>& 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<TuioCursor>& list) {
_selected = std::move(newSelected);
}
// Interprets the input gesture to a specific interaction
int TouchInteraction::interpretInteraction(const std::vector<TuioCursor>& list,
const std::vector<Point>& lastProcessed)
int TouchInteraction::interpretInteraction(const std::vector<TouchInputHolder>& list,
const std::vector<TouchInput>& 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<float>(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<TuioCursor>& 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<float>(_centroid.x),
static_cast<float>(_centroid.y)
);
float currentAngle = c.getAngle(
static_cast<float>(_centroid.x),
static_cast<float>(_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<float>()) {
res = currentAngle + (2.0 * glm::pi<float>() - lastAngle);
}
else if (currentAngle > lastAngle + 1.5 * M_PI) {
res = (2 * M_PI - currentAngle) + lastAngle;
else if (currentAngle > lastAngle + 1.5 * glm::pi<float>()) {
res = (2.0 * glm::pi<float>() - currentAngle) + lastAngle;
}
else {
res = currentAngle - lastAngle;
@@ -810,9 +761,7 @@ int TouchInteraction::interpretInteraction(const std::vector<TuioCursor>& list,
return ROT;
}
else {
float avgDistance = static_cast<float>(
std::abs(dist - lastDist) / list.at(0).getMotionSpeed()
);
float avgDistance = static_cast<float>(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<TuioCursor>& list,
}
}
// Calculate how much interpreted interaction (_vel) should change the camera state
void TouchInteraction::computeVelocities(const std::vector<TuioCursor>& list,
const std::vector<Point>& lastProcessed)
void TouchInteraction::computeVelocities(const std::vector<TouchInputHolder>& list,
const std::vector<TouchInput>& 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<TuioCursor>& 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<TuioCursor>& list,
list.begin(),
list.end(),
0.0,
[&](double d, const TuioCursor& c) {
return d + c.getDistance(
static_cast<float>(_centroid.x),
static_cast<float>(_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<float>(_centroid.x),
static_cast<float>(_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<TuioCursor>& 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<TuioCursor>& 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<float>(_zoomSensitivityExponential)
);
}
@@ -940,27 +885,26 @@ void TouchInteraction::computeVelocities(const std::vector<TuioCursor>& 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<float>(_centroid.x),
static_cast<float>(_centroid.y)
[&inputHolder](const TouchInput& input) {
return inputHolder.holdsInput(input);
}
);
double currentAngle = c.getAngle(
static_cast<float>(_centroid.x),
static_cast<float>(_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<float>()) {
res += currentAngle + (2 * glm::pi<float>() - lastAngle);
}
else if (currentAngle > lastAngle + 1.5 * M_PI) {
res += (2 * M_PI - currentAngle) + lastAngle;
else if (currentAngle > lastAngle + 1.5 * glm::pi<float>()) {
res += (2 * glm::pi<float>() - currentAngle) + lastAngle;
}
else {
res += currentAngle - lastAngle;
@@ -968,15 +912,14 @@ void TouchInteraction::computeVelocities(const std::vector<TuioCursor>& 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<TuioCursor>& 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<TuioCursor> lastFrame = {
{ TuioCursor(0, 10, 0.45f, 0.4f) }, // session id, cursor id, x, y
{ TuioCursor(1, 11, 0.55f, 0.6f) }
};
std::vector<TuioCursor> 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

View File

@@ -27,9 +27,8 @@
#include <openspace/engine/globals.h>
#include <openspace/rendering/renderengine.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/opengl/programobject.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/programobject.h>
namespace {
constexpr const std::array<const char*, 4> 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<TUIO::TuioCursor>& list) {
void TouchMarker::render(const std::vector<openspace::TouchInputHolder>& list) {
if (_visible && !list.empty()) {
createVertexList(list);
_shader->activate();
@@ -131,7 +125,6 @@ void TouchMarker::render(const std::vector<TUIO::TuioCursor>& 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<int>(_vertexData.size() / 2));
@@ -139,13 +132,13 @@ void TouchMarker::render(const std::vector<TUIO::TuioCursor>& list) {
}
}
void TouchMarker::createVertexList(const std::vector<TUIO::TuioCursor>& list) {
void TouchMarker::createVertexList(const std::vector<openspace::TouchInputHolder>& 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;
}

View File

@@ -29,11 +29,12 @@
#include <openspace/interaction/navigationhandler.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/rendering/screenspacerenderable.h>
#include <ghoul/logging/logmanager.h>
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<long>::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<size_t>(tcur->getTuioSourceID()),
static_cast<size_t>(tcur->getCursorID()),
tcur->getX(),
tcur->getY(),
static_cast<double>(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<size_t>(tcur->getTuioSourceID()),
static_cast<size_t>(tcur->getCursorID()),
tcur->getX(),
tcur->getY(),
static_cast<double>(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<size_t>(tcur->getTuioSourceID()),
static_cast<size_t>(tcur->getCursorID()),
tcur->getX(),
tcur->getY(),
static_cast<double>(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<TuioCursor> 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<TouchInput> TuioEar::takeInput() {
std::vector<TouchInput> outputList;
{
std::lock_guard lock(_mx);
outputList.swap(_inputList);
}
else {
return _tap;
return outputList;
}
std::vector<TouchInput> TuioEar::takeRemovals() {
std::vector<TouchInput> 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

View File

@@ -26,84 +26,133 @@
#include <modules/touch/include/win32_touch.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/openspaceengine.h>
#include <openspace/engine/windowdelegate.h>
#include <ghoul/logging/logmanager.h>
#include <TUIO/TuioServer.h>
#include <chrono>
#include <thread>
#include <tchar.h>
#include <tpcshrd.h>
// #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<openspace::TouchInputHolder>
> gTouchInputsMap;
#ifdef ENABLE_TUIOMESSAGES
TUIO::TuioServer* gTuioServer = nullptr;
std::unordered_map<UINT, TUIO::TuioCursor*> 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<LPMSG>(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<LPRECT>(&rect));
POINT p = pointerInfo.ptPixelLocation;
// native touch to screen conversion
ScreenToClient(pStruct->hwnd, reinterpret_cast<LPPOINT>(&p));
float xPos = static_cast<float>(p.x) /
static_cast<float>(rect.right - rect.left);
float yPos = static_cast<float>(p.y) /
static_cast<float>(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<LPMSG>(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<microseconds>(
high_resolution_clock::now().time_since_epoch()
) - gStartTime;
RECT rect;
GetClientRect(pStruct->hwnd, reinterpret_cast<LPRECT>(&rect));
POINT p = info.ptPixelLocation;
// native touch to screen conversion
ScreenToClient(pStruct->hwnd, reinterpret_cast<LPPOINT>(&p));
float xPos = static_cast<float>(p.x) /
static_cast<float>(rect.right - rect.left);
float yPos = static_cast<float>(p.y) /
static_cast<float>(rect.bottom - rect.top);
TouchInput touchInput(
reinterpret_cast<size_t>(info.sourceDevice),
static_cast<size_t>(info.pointerId),
xPos,
yPos,
static_cast<double>(timestamp.count())/1'000'000.0
);
if (info.pointerFlags & POINTER_FLAG_DOWN) {
#ifdef ENABLE_DIRECTMSG
std::unique_ptr<TouchInputHolder> points =
std::make_unique<TouchInputHolder>(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::microseconds>(
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<LPMSLLHOOKSTRUCT>(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

View File

@@ -23,95 +23,94 @@
****************************************************************************************/
#include <modules/touch/touchmodule.h>
#include <modules/touch/include/win32_touch.h>
#include <modules/webgui/webguimodule.h>
#include <modules/touch/include/tuioear.h>
#include <modules/touch/include/win32_touch.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/globalscallbacks.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/interaction/interactionmonitor.h>
#include <openspace/interaction/navigationhandler.h>
#include <openspace/interaction/orbitalnavigator.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/rendering/screenspacerenderable.h>
#include <ghoul/logging/logmanager.h>
#include <sstream>
#include <string>
#include <iostream>
#ifdef OPENSPACE_MODULE_WEBBROWSER_ENABLED
#include <modules/webbrowser/webbrowsermodule.h>
#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<TouchInput> earInputs = _ear->takeInput();
std::vector<TouchInput> 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<TuioCursor>::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<TouchInputHolder>::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<TuioCursor>& 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<WebBrowserModule>());
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<WebBrowserModule>());
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<Win32TouchHook>(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

View File

@@ -25,43 +25,52 @@
#ifndef __OPENSPACE_MODULE_TOUCH___TOUCHMODULE___H__
#define __OPENSPACE_MODULE_TOUCH___TOUCHMODULE___H__
#include <openspace/util/openspacemodule.h>
#include <modules/touch/include/touchmarker.h>
#include <modules/touch/include/touchinteraction.h>
#include <openspace/util/openspacemodule.h>
#include <openspace/util/touch.h>
#include <memory>
namespace openspace {
#ifdef WIN32
class Win32TouchHook;
#endif //WIN32
class TouchModule : public OpenSpaceModule {
using Point = std::pair<int, TUIO::TuioPoint>;
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<TUIO::TuioCursor>& listOfContactPoints);
TuioEar _ear;
TouchInteraction _touch;
TouchMarker _markers;
std::vector<TUIO::TuioCursor> _listOfContactPoints;
// contains an id and the TuioPoint that was processed last frame
std::vector<Point> _lastProcessed;
glm::ivec2 _webPositionCallback = glm::ivec2(0,0);
#ifdef WIN32
std::unique_ptr<Win32TouchHook> _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<TuioEar> _ear;
TouchInteraction _touch;
TouchMarker _markers;
std::vector<TouchInputHolder> _touchPoints;
std::vector<TouchInput> _deferredRemovals;
std::vector<TouchInput> _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> _win32TouchHook;
#endif //WIN32
bool _tap = false;
};
} // namespace openspace

View File

@@ -27,6 +27,7 @@
#include <openspace/util/keys.h>
#include <openspace/util/mouse.h>
#include <openspace/util/touch.h>
#include <ghoul/glm.h>
#include <chrono>
@@ -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<TouchInput> _validTouchStates;
/**
* determines if a click should be sent as a double click or not
* @return

View File

@@ -29,7 +29,6 @@
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/interaction/interactionmonitor.h>
#include <ghoul/logging/logmanager.h>
#include <fmt/format.h>
@@ -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<int>(windowPos.x),
static_cast<int>(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<float>(x);
_mousePosition.y = static_cast<float>(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<float>(x);
_mousePosition.y = static_cast<float>(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<int>(x), static_cast<int>(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) {

View File

@@ -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

View File

@@ -96,6 +96,21 @@ std::vector<std::function<bool(double, double)>>& gMouseScrollWheel() {
return g;
}
std::vector<std::function<bool(TouchInput)>>& gTouchDetected() {
static std::vector<std::function<bool(TouchInput)>> g;
return g;
}
std::vector<std::function<bool(TouchInput)>>& gTouchUpdated() {
static std::vector<std::function<bool(TouchInput)>> g;
return g;
}
std::vector<std::function<void(TouchInput)>>& gTouchExit() {
static std::vector<std::function<void(TouchInput)>> g;
return g;
}
} // namespace openspace::global::detail
namespace openspace::global::callback {

View File

@@ -1295,6 +1295,34 @@ void OpenSpaceEngine::mouseScrollWheelCallback(double posX, double posY) {
global::interactionMonitor.markInteraction();
}
void OpenSpaceEngine::touchDetectionCallback(TouchInput input) {
using F = std::function<bool (TouchInput)>;
for (const F& func : global::callback::touchDetected) {
bool isConsumed = func(input);
if (isConsumed) {
return;
}
}
}
void OpenSpaceEngine::touchUpdateCallback(TouchInput input) {
using F = std::function<bool(TouchInput)>;
for (const F& func : global::callback::touchUpdated) {
bool isConsumed = func(input);
if (isConsumed) {
return;
}
}
}
void OpenSpaceEngine::touchExitCallback(TouchInput input) {
using F = std::function<void(TouchInput)>;
for (const F& func : global::callback::touchExit) {
func(input);
}
}
std::vector<char> OpenSpaceEngine::encode() {
std::vector<char> buffer = global::syncEngine.encodeSyncables();
return buffer;

View File

@@ -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) {

View File

@@ -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);
}
});

185
src/util/touch.cpp Normal file
View File

@@ -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 <openspace/util/touch.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <cmath>
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<float>() + std::asin(side / distance);
if (height < 0.f) {
angle = 2.f * glm::pi<float>() - 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<float>(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<float>(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<TouchInput>& TouchInputHolder::peekInputs() const {
return _inputs;
}
} // namespace openspace