Merge pull request #1115 from OpenSpace/feature/continuous-pinch

Feature/continuous pinch
This commit is contained in:
Alexander Bock
2020-03-28 20:34:33 +01:00
committed by GitHub
6 changed files with 143 additions and 78 deletions
+13 -2
View File
@@ -32,6 +32,9 @@
namespace openspace {
// The TouchInput represents a single finger/device-input at a specific point in time.
// the fingerId and touchDeviceId coupled with the timestamp allows this to be compared
// with other TouchInputs in order to calculate gesture-like behaviour.
struct TouchInput {
TouchInput(size_t touchDeviceId, size_t fingerId, float x, float y, double timestamp);
glm::vec2 screenCoordinates(glm::vec2 resolution) const;
@@ -46,19 +49,25 @@ struct TouchInput {
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
double timestamp; // timestamp in seconds from global touch initialization
};
// The TouchInputHolder holds one or many TouchInputs, in order to track the history of
// the finger/input device
class TouchInputHolder {
public:
TouchInputHolder(TouchInput input);
// tryAddInput:
// Succeeds upon a different input than last.
// Fails upon a too similar input as last.
// Updates time for the last input if same position.
bool tryAddInput(TouchInput input);
void clearInputs();
// Checks whether or not this Holder actually holds a specific input (based on IDs)
// Succeeds when `input` is held by this Holder
// Fails if `input` is not held by this Holder
bool holdsInput(const TouchInput &input) const;
size_t touchDeviceId() const;
@@ -72,12 +81,14 @@ public:
double gestureTime() const;
size_t numInputs() const;
const TouchInput& firstInput() 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;
TouchInput _firstInput;
size_t _touchDeviceId;
size_t _fingerId;
+4 -3
View File
@@ -35,6 +35,7 @@
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/vector/ivec2property.h>
#include <openspace/properties/vector/vec4property.h>
#include <array>
#include <chrono>
#include <memory>
@@ -96,7 +97,7 @@ public:
std::vector<TouchInput>& lastProcessed);
// Calculates the new camera state with velocities and time since last frame
void step(double dt);
void step(double dt, bool directTouch = false);
// Called each frame we have no new input, used to reset data
void resetAfterInput();
@@ -166,6 +167,7 @@ private:
properties::IntProperty _deceleratesPerSecond;
properties::FloatProperty _touchScreenSize;
properties::FloatProperty _tapZoomFactor;
properties::FloatProperty _pinchZoomFactor;
properties::FloatProperty _nodeRadiusThreshold;
properties::FloatProperty _rollAngleThreshold;
properties::FloatProperty _orbitSpeedThreshold;
@@ -202,7 +204,7 @@ private:
double pinchConsecZoomFactor = 0;
//int stepVelUpdate = 0;
#endif
std::array<TouchInputHolder, 2> _pinchInputs;
// Class variables
VelocityStates _vel;
VelocityStates _lastVel;
@@ -242,4 +244,3 @@ private:
} // openspace namespace
#endif // __OPENSPACE_MODULE_TOUCH___TOUCH_INTERACTION___H__
+86 -54
View File
@@ -117,6 +117,13 @@ namespace {
"" // @TODO Missing documentation
};
constexpr openspace::properties::Property::PropertyInfo PinchZoomFactorInfo = {
"PinchZoomFactor",
"Scaling distance travelled on pinch",
"This value is used to reduce the amount of pinching needed. A linear kind of "
"sensitivity that will alter the pinch-zoom speed."
};
constexpr openspace::properties::Property::PropertyInfo DirectManipulationInfo = {
"DirectManipulationRadius",
"Radius a planet has to have to activate direct-manipulation",
@@ -264,6 +271,7 @@ TouchInteraction::TouchInteraction()
, _deceleratesPerSecond(DecelatesPerSecondInfo, 240, 60, 300)
, _touchScreenSize(TouchScreenSizeInfo, 55.0f, 5.5f, 150.0f)
, _tapZoomFactor(TapZoomFactorInfo, 0.2f, 0.f, 0.5f)
, _pinchZoomFactor(PinchZoomFactorInfo, 0.01f, 0.f, 0.2f)
, _nodeRadiusThreshold(DirectManipulationInfo, 0.2f, 0.0f, 1.0f)
, _rollAngleThreshold(RollThresholdInfo, 0.025f, 0.f, 0.05f)
, _orbitSpeedThreshold(OrbitSpinningThreshold, 0.005f, 0.f, 0.01f)
@@ -277,7 +285,12 @@ TouchInteraction::TouchInteraction()
0.25f
)
, _zoomBoundarySphereMultiplier(ZoomBoundarySphereMultiplierInfo, 1.001f, 1.f, 1.01f)
, _zoomOutLimit(ZoomOutLimitInfo, std::numeric_limits<double>::max(), 1000.0, std::numeric_limits<double>::max())
, _zoomOutLimit(
ZoomOutLimitInfo,
std::numeric_limits<double>::max(),
1000.0,
std::numeric_limits<double>::max()
)
, _zoomInLimit(ZoomInLimitInfo, -1.0, 0.0, std::numeric_limits<double>::max())
, _inputStillThreshold(InputSensitivityInfo, 0.0005f, 0.f, 0.001f)
// used to void wrongly interpreted roll interactions
@@ -307,8 +320,9 @@ TouchInteraction::TouchInteraction()
{ "Ignore GUI", "Disable GUI touch interaction", "" },
false
)
, _pinchInputs({ TouchInput(0, 0, 0.0, 0.0, 0.0), TouchInput(0, 0, 0.0, 0.0, 0.0) })
, _vel{ glm::dvec2(0.0), 0.0, 0.0, glm::dvec2(0.0) }
, _sensitivity{ glm::dvec2(0.08, 0.045), 12.0 /*4.0*/, 2.75, glm::dvec2(0.08, 0.045) }
, _sensitivity{ glm::dvec2(0.08, 0.045), 12.0, 2.75, glm::dvec2(0.08, 0.045) }
, _constTimeDecay_secs(ConstantTimeDecaySecsInfo, 1.75f, 0.1f, 4.0f)
// calculated with two vectors with known diff in length, then
// projDiffLength/diffLength.
@@ -320,6 +334,7 @@ TouchInteraction::TouchInteraction()
addProperty(_deceleratesPerSecond);
addProperty(_touchScreenSize);
addProperty(_tapZoomFactor);
addProperty(_pinchZoomFactor);
addProperty(_nodeRadiusThreshold);
addProperty(_rollAngleThreshold);
addProperty(_orbitSpeedThreshold);
@@ -494,7 +509,7 @@ void TouchInteraction::directControl(const std::vector<TouchInputHolder>& list)
_vel.pan = glm::dvec2(par.at(4), par.at(5));
}
}
step(1.0);
step(1.0, true);
// Reset velocities after setting new camera state
_lastVel = _vel;
@@ -777,6 +792,15 @@ int TouchInteraction::interpretInteraction(const std::vector<TouchInputHolder>&
return ROLL;
}
else {
const bool sameInput0 = _pinchInputs[0].holdsInput(list[0].latestInput());
const bool sameInput1 = _pinchInputs[1].holdsInput(list[1].latestInput());
if (sameInput0 && sameInput1) {
_pinchInputs[0].tryAddInput(list[0].latestInput());
_pinchInputs[1].tryAddInput(list[1].latestInput());
} else {
_pinchInputs[0] = TouchInputHolder(list[0].latestInput());
_pinchInputs[1] = TouchInputHolder(list[1].latestInput());
}
return PINCH;
}
}
@@ -816,6 +840,9 @@ void TouchInteraction::computeVelocities(const std::vector<TouchInputHolder>& li
#endif
const TouchInputHolder& inputHolder = list.at(0);
const glm::ivec2 windowSize = global::windowDelegate.currentWindowSize();
const float aspectRatio =
static_cast<float>(windowSize.x) / static_cast<float>(windowSize.y);
switch (action) {
case ROT: { // add rotation velocity
_vel.orbit += glm::dvec2(inputHolder.speedX() *
@@ -829,54 +856,31 @@ void TouchInteraction::computeVelocities(const std::vector<TouchInputHolder>& li
}
case PINCH: {
// add zooming velocity - dependant on distance difference between contact
// points this/last frame
double distance = std::accumulate(
list.begin(),
list.end(),
0.0,
[&](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.f,
[&](float d, const TouchInput& p) {
const glm::vec2 lastPos = { p.x, p.y };
return d + glm::distance(lastPos, _centroid);
}
) / lastProcessed.size();
// points this/first frame
using namespace glm;
const TouchInput& startFinger0 = _pinchInputs[0].firstInput();
const TouchInput& startFinger1 = _pinchInputs[1].firstInput();
const dvec2 startVec0 = dvec2(startFinger0.x * aspectRatio, startFinger0.y);
const dvec2 startVec1 = dvec2(startFinger1.x * aspectRatio, startFinger1.y);
double distToCentroidStart = length(startVec0 - startVec1) / 2.0;
glm::dvec3 camPos = _camera->positionVec3();
glm::dvec3 centerPos = anchor->worldPosition();
glm::dvec3 currDistanceToFocusNode = camPos - centerPos;
const TouchInput& endFinger0 = _pinchInputs[0].latestInput();
const TouchInput& endFinger1 = _pinchInputs[1].latestInput();
const dvec2 endVec0 = dvec2(endFinger0.x * aspectRatio, endFinger0.y);
const dvec2 endVec1 = dvec2(endFinger1.x * aspectRatio, endFinger1.y);
double distToCentroidEnd = length(endVec0 - endVec1) / 2.0;
const double distanceFromFocusSurface =
length(currDistanceToFocusNode) - anchor->boundingSphere();
double zoomFactor = (distance - lastDistance);
double zoomFactor = distToCentroidEnd - distToCentroidStart;
#ifdef TOUCH_DEBUG_PROPERTIES
pinchConsecCt++;
pinchConsecZoomFactor += zoomFactor;
#endif
_constTimeDecayCoeff.zoom = computeConstTimeDecayCoefficient(_vel.zoom);
if (distanceFromFocusSurface > 0.1) {
const double ratioOfDistanceToNodeVsSurface =
length(currDistanceToFocusNode) / distanceFromFocusSurface;
if (ratioOfDistanceToNodeVsSurface > _zoomSensitivityDistanceThreshold) {
zoomFactor *= pow(
std::abs(distanceFromFocusSurface),
static_cast<float>(_zoomSensitivityExponential)
);
}
}
else {
zoomFactor = 1.0;
}
_vel.zoom = zoomFactor * _zoomSensitivityProportionalDist *
std::max(_touchScreenSize.value() * 0.1, 1.0);
_constTimeDecayCoeff.zoom = 1.0;
_vel.zoom = zoomFactor *
_pinchZoomFactor *
_zoomSensitivityProportionalDist *
std::max(_touchScreenSize.value() * 0.1, 1.0);
break;
}
case ROLL: {
@@ -917,7 +921,7 @@ void TouchInteraction::computeVelocities(const std::vector<TouchInputHolder>& li
break;
}
case PAN: {
// add local rotation velocity
// add local rotation velocity
_vel.pan += glm::dvec2(inputHolder.speedX() *
_sensitivity.pan.x, inputHolder.speedY() * _sensitivity.pan.y);
double panVelocityAvg = glm::distance(_vel.pan.x, _vel.pan.y);
@@ -925,7 +929,7 @@ void TouchInteraction::computeVelocities(const std::vector<TouchInputHolder>& li
break;
}
case PICK: {
// pick something in the scene as focus node
// pick something in the scene as focus node
if (_pickingSelected) {
setFocusNode(_pickingSelected);
@@ -992,7 +996,7 @@ double TouchInteraction::computeTapZoomDistance(double zoomGain) {
// Main update call, calculates the new orientation and position for the camera depending
// on _vel and dt. Called every frame
void TouchInteraction::step(double dt) {
void TouchInteraction::step(double dt, bool directTouch) {
using namespace glm;
const SceneGraphNode* anchor =
@@ -1103,22 +1107,45 @@ void TouchInteraction::step(double dt) {
_loggerCat, _zoomOutLimit.value()
));
}
const double currentPosDistance = length(centerToCamera);
//Apply the velocity to update camera position
glm::dvec3 zoomDistanceIncrement = directionToCenter * _vel.zoom * dt;
double zoomVelocity = _vel.zoom;
if (!directTouch) {
const double distanceFromSurface =
length(currentPosDistance) - anchor->boundingSphere();
if (distanceFromSurface > 0.1) {
const double ratioOfDistanceToNodeVsSurface =
length(currentPosDistance) / distanceFromSurface;
if (ratioOfDistanceToNodeVsSurface > _zoomSensitivityDistanceThreshold) {
zoomVelocity *= pow(
std::abs(distanceFromSurface),
static_cast<float>(_zoomSensitivityExponential)
);
}
}
else {
zoomVelocity = 1.0;
}
}
const glm::dvec3 zoomDistanceIncrement = directionToCenter * zoomVelocity * dt;
const double newPosDistance = length(centerToCamera + zoomDistanceIncrement);
const double currentPosDistance = length(centerToCamera);
// Possible with other navigations performed outside touch interaction
const bool currentPosViolatingZoomOutLimit =
(currentPosDistance >= _zoomOutLimit.value());
(currentPosDistance >= _zoomOutLimit);
const bool willNewPositionViolateZoomOutLimit =
(newPosDistance >= _zoomOutLimit.value());
(newPosDistance >= _zoomOutLimit);
bool willNewPositionViolateZoomInLimit =
(newPosDistance < zoomInBounds);
bool willNewPositionViolateDirection =
(currentPosDistance <= length(zoomDistanceIncrement));
if (!willNewPositionViolateZoomInLimit
&& !willNewPositionViolateZoomOutLimit) {
if (!willNewPositionViolateZoomInLimit &&
!willNewPositionViolateDirection &&
!willNewPositionViolateZoomOutLimit)
{
camPos += zoomDistanceIncrement;
}
else if (currentPosViolatingZoomOutLimit) {
@@ -1218,7 +1245,6 @@ void TouchInteraction::resetAfterInput() {
module.touchInput.active = false;
module.touchInput.action = 0;
}
_lmSuccess = true;
// Ensure that _guiON is consistent with properties in OnScreenGUI and
_guiON = module.gui.isEnabled();
@@ -1228,6 +1254,11 @@ void TouchInteraction::resetAfterInput() {
_lastVel.zoom = 0.0;
_lastVel.roll = 0.0;
_lastVel.pan = glm::dvec2(0.0);
_constTimeDecayCoeff.zoom = computeConstTimeDecayCoefficient(_vel.zoom);
_pinchInputs[0].clearInputs();
_pinchInputs[1].clearInputs();
_selected.clear();
_pickingSelected = nullptr;
}
@@ -1240,6 +1271,7 @@ void TouchInteraction::resetToDefault() {
_deceleratesPerSecond.set(240);
_touchScreenSize.set(55.0f);
_tapZoomFactor.set(0.2f);
_pinchZoomFactor.set(0.01f);
_nodeRadiusThreshold.set(0.2f);
_rollAngleThreshold.set(0.025f);
_orbitSpeedThreshold.set(0.005f);
+18 -9
View File
@@ -40,12 +40,18 @@
#define ENABLE_DIRECTMSG
namespace {
using namespace std::chrono;
constexpr const char* _loggerCat = "win32_touch";
HHOOK gTouchHook = nullptr;
std::thread* gMouseHookThread;
HHOOK gMouseHook = nullptr;
bool gStarted = false;
std::chrono::microseconds gStartTime = std::chrono::microseconds(0);
microseconds gStartTime = microseconds(0);
const long long gFrequency = []() -> long long {
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
return frequency.QuadPart;
}();
std::unordered_map<
UINT32,
std::unique_ptr<openspace::TouchInputHolder>
@@ -79,10 +85,14 @@ LRESULT CALLBACK HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
break;
}
using namespace std::chrono;
const microseconds timestamp = duration_cast<microseconds>(
high_resolution_clock::now().time_since_epoch()
) - gStartTime;
//Implementation from microsoft STL of high_resolution_clock(steady_clock):
const long long freq = gFrequency;
const long long whole = (info.PerformanceCount / freq) * std::micro::den;
const long long part = (info.PerformanceCount % freq) *
std::micro::den / freq;
const microseconds timestamp =
duration<UINT64, std::micro>(whole + part) - gStartTime;
RECT rect;
GetClientRect(pStruct->hwnd, reinterpret_cast<LPRECT>(&rect));
@@ -100,7 +110,7 @@ LRESULT CALLBACK HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
static_cast<size_t>(info.pointerId),
xPos,
yPos,
static_cast<double>(timestamp.count())/1'000'000.0
static_cast<double>(timestamp.count()) / 1'000'000.0
);
if (info.pointerFlags & POINTER_FLAG_DOWN) {
@@ -213,9 +223,8 @@ 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()
);
gStartTime =
duration_cast<microseconds>(high_resolution_clock::now().time_since_epoch());
#ifdef ENABLE_TUIOMESSAGES
gTuioServer = new TUIO::TuioServer("localhost", 3333);
TUIO::TuioTime::initSession();
+1 -1
View File
@@ -221,7 +221,7 @@ void TouchModule::internalInitialize(const ghoul::Dictionary& /*dictionary*/){
global::callback::touchUpdated.push_back(
[this](TouchInput i) {
updateOrAddTouchInput(i);
return true;
return true;
}
);
+21 -9
View File
@@ -73,32 +73,41 @@ float TouchInput::angleToPos(float otherX, float otherY) const {
TouchInputHolder::TouchInputHolder(TouchInput input)
: _inputs{ input }
, _firstInput(input)
, _touchDeviceId(input.touchDeviceId)
, _fingerId(input.fingerId)
{}
bool TouchInputHolder::tryAddInput(TouchInput input) {
if(_inputs.empty()) {
_inputs.emplace_front(input);
return true;
}
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()) {
const bool sameTimeAsLastInput = (input.timestamp - lastInput.timestamp) < ONE_MS;
bool successful = false;
if (!sameTimeAsLastInput && isMoving()) {
_inputs.emplace_front(input);
wasInserted = true;
successful = true;
}
else if (sameTimeAsLastInput && input.isMoving()) {
else if (!sameTimeAsLastInput && input.isMoving()) {
_inputs.emplace_front(input);
wasInserted = true;
successful = true;
}
else if (!sameTimeAsLastInput){
_inputs.front().timestamp = input.timestamp;
successful = true;
}
constexpr const int MaxInputs = 128;
if (_inputs.size() > MaxInputs) {
_inputs.pop_back();
}
return wasInserted;
return successful;
}
void TouchInputHolder::clearInputs() {
@@ -142,8 +151,7 @@ bool TouchInputHolder::isMoving() const {
if (_inputs.size() <= 1) {
return false;
}
const TouchInput& currentInput = _inputs[0];
return currentInput.dx != 0.f || currentInput.dy != 0.f;
return latestInput().isMoving();
}
float TouchInputHolder::gestureDistance() const {
@@ -174,6 +182,10 @@ size_t TouchInputHolder::numInputs() const {
return _inputs.size();
}
const TouchInput& TouchInputHolder::firstInput() const {
return _firstInput;
}
const TouchInput& TouchInputHolder::latestInput() const {
return _inputs.front();
}