diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8b3f09d1f7..d75a2c3245 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -389,8 +389,6 @@ void DomainServer::setupNodeListAndAssignments() { const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { nodeList->setSessionUUID(idValueVariant->toString()); - } else { - nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); diff --git a/interface/resources/controllers/touchscreen.json b/interface/resources/controllers/touchscreen.json new file mode 100644 index 0000000000..5b2ff62a8d --- /dev/null +++ b/interface/resources/controllers/touchscreen.json @@ -0,0 +1,23 @@ +{ + "name": "Touchscreen to Actions", + "channels": [ + { "from": "Touchscreen.GesturePinchOut", "to": "Actions.BoomOut", "filters": [ { "type": "scale", "scale": 0.02 } ] }, + { "from": "Touchscreen.GesturePinchIn", "to": "Actions.BoomIn", "filters": [ { "type": "scale", "scale": 0.02 } ] }, + + { "from": { "makeAxis" : [ + [ "Touchscreen.DragLeft" ], + [ "Touchscreen.DragRight" ] + ] + }, + "to": "Actions.Yaw", "filters": [ { "type": "scale", "scale": 0.12 } ] + }, + + { "from": { "makeAxis" : [ + [ "Touchscreen.DragUp" ], + [ "Touchscreen.DragDown" ] + ] + }, + "to": "Actions.Pitch", "filters": [ { "type": "scale", "scale": 0.04 } ] + } + ] +} diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml old mode 100755 new mode 100644 diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml old mode 100755 new mode 100644 diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml old mode 100755 new mode 100644 diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml old mode 100755 new mode 100644 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b58fa57d3f..902312a022 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -957,8 +957,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : return DependencyManager::get()->navigationFocused() ? 1 : 0; }); - // Setup the keyboardMouseDevice and the user input mapper with the default bindings + // Setup the _keyboardMouseDevice, _touchscreenDevice and the user input mapper with the default bindings userInputMapper->registerDevice(_keyboardMouseDevice->getInputDevice()); + // if the _touchscreenDevice is not supported it will not be registered + if (_touchscreenDevice) { + userInputMapper->registerDevice(_touchscreenDevice->getInputDevice()); + } // force the model the look at the correct directory (weird order of operations issue) scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation()); @@ -1600,6 +1604,9 @@ void Application::initializeUi() { if (KeyboardMouseDevice::NAME == inputPlugin->getName()) { _keyboardMouseDevice = std::dynamic_pointer_cast(inputPlugin); } + if (TouchscreenDevice::NAME == inputPlugin->getName()) { + _touchscreenDevice = std::dynamic_pointer_cast(inputPlugin); + } } _window->setMenuBar(new Menu()); @@ -2096,6 +2103,9 @@ bool Application::event(QEvent* event) { case QEvent::TouchUpdate: touchUpdateEvent(static_cast(event)); return true; + case QEvent::Gesture: + touchGestureEvent((QGestureEvent*)event); + return true; case QEvent::Wheel: wheelEvent(static_cast(event)); return true; @@ -2727,6 +2737,9 @@ void Application::touchUpdateEvent(QTouchEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchUpdateEvent(event); } + if (_touchscreenDevice->isActive()) { + _touchscreenDevice->touchUpdateEvent(event); + } } void Application::touchBeginEvent(QTouchEvent* event) { @@ -2745,6 +2758,9 @@ void Application::touchBeginEvent(QTouchEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchBeginEvent(event); } + if (_touchscreenDevice->isActive()) { + _touchscreenDevice->touchBeginEvent(event); + } } @@ -2762,10 +2778,19 @@ void Application::touchEndEvent(QTouchEvent* event) { if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchEndEvent(event); } + if (_touchscreenDevice->isActive()) { + _touchscreenDevice->touchEndEvent(event); + } // put any application specific touch behavior below here.. } +void Application::touchGestureEvent(QGestureEvent* event) { + if (_touchscreenDevice->isActive()) { + _touchscreenDevice->touchGestureEvent(event); + } +} + void Application::wheelEvent(QWheelEvent* event) const { _altPressed = false; _controllerScriptingInterface->emitWheelEvent(event); // send events to any registered scripts diff --git a/interface/src/Application.h b/interface/src/Application.h index 1ccef79198..a1e3e2f930 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -403,6 +404,7 @@ private: void touchBeginEvent(QTouchEvent* event); void touchEndEvent(QTouchEvent* event); void touchUpdateEvent(QTouchEvent* event); + void touchGestureEvent(QGestureEvent* event); void wheelEvent(QWheelEvent* event) const; void dropEvent(QDropEvent* event); @@ -455,6 +457,7 @@ private: std::shared_ptr _applicationStateDevice; // Default ApplicationDevice reflecting the state of different properties of the session std::shared_ptr _keyboardMouseDevice; // Default input device, the good old keyboard mouse and maybe touchpad + std::shared_ptr _touchscreenDevice; // the good old touchscreen SimpleMovingAverage _avatarSimsPerSecond {10}; int _avatarSimsPerSecondReport {0}; quint64 _lastAvatarSimsPerSecondUpdate {0}; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 6b81ad7ff3..a857dc39e0 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -43,7 +43,6 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : void ModelOverlay::update(float deltatime) { if (_updateModel) { _updateModel = false; - _model->setSnapModelToCenter(true); _model->setScaleToFit(true, getDimensions()); _model->setRotation(getRotation()); @@ -87,23 +86,15 @@ void ModelOverlay::render(RenderArgs* args) { void ModelOverlay::setProperties(const QVariantMap& properties) { auto position = getPosition(); auto rotation = getRotation(); - auto scale = getDimensions(); - + Volume3DOverlay::setProperties(properties); - + if (position != getPosition() || rotation != getRotation()) { _updateModel = true; } - if (scale != getDimensions()) { - auto newScale = getDimensions(); - if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) { - setDimensions(scale); - } else { - _updateModel = true; - } - } - + _updateModel = true; + auto urlValue = properties["url"]; if (urlValue.isValid() && urlValue.canConvert()) { _url = urlValue.toString(); diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 6fc9c41160..8b0bd9981f 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -43,6 +43,7 @@ int GLWidget::getDeviceHeight() const { void GLWidget::initializeGL() { setAttribute(Qt::WA_AcceptTouchEvents); + grabGesture(Qt::PinchGesture); setAcceptDrops(true); // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. setAutoBufferSwap(false); @@ -81,6 +82,7 @@ bool GLWidget::event(QEvent* event) { case QEvent::TouchBegin: case QEvent::TouchEnd: case QEvent::TouchUpdate: + case QEvent::Gesture: case QEvent::Wheel: case QEvent::DragEnter: case QEvent::Drop: diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 32c28af2ef..acd7a20327 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -13,11 +13,13 @@ #include #include "KeyboardMouseDevice.h" +#include "TouchscreenDevice.h" // TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class InputPluginList getInputPlugins() { InputPlugin* PLUGIN_POOL[] = { new KeyboardMouseDevice(), + new TouchscreenDevice(), nullptr }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp old mode 100644 new mode 100755 index 915ec1db87..56894efc4c --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -19,6 +19,7 @@ #include const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; +bool KeyboardMouseDevice::_enableTouch = true; void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get(); @@ -79,7 +80,7 @@ void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event) { // if we pressed and released at the same location within a small time window, then create a "_CLICKED" // input for this button we might want to add some small tolerance to this so if you do a small drag it - // till counts as a clicked. + // still counts as a click. static const int CLICK_TIME = USECS_PER_MSEC * 500; // 500 ms to click if (!_mouseMoved && (usecTimestampNow() - _mousePressTime < CLICK_TIME)) { _inputDevice->_buttonPressedMap.insert(_inputDevice->makeInput((Qt::MouseButton) event->button(), true).getChannel()); @@ -98,7 +99,7 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { _inputDevice->_axisStateMap[MOUSE_AXIS_X_POS] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); _inputDevice->_axisStateMap[MOUSE_AXIS_X_NEG] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - // Y mouse is inverted positive is pointing up the screen + // Y mouse is inverted positive is pointing up the screen _inputDevice->_axisStateMap[MOUSE_AXIS_Y_POS] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); _inputDevice->_axisStateMap[MOUSE_AXIS_Y_NEG] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); @@ -132,34 +133,40 @@ glm::vec2 evalAverageTouchPoints(const QList& points) { } void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { - _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); - _lastTouch = evalAverageTouchPoints(event->touchPoints()); - _lastTouchTime = _clock.now(); + if (_enableTouch) { + _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); + } } void KeyboardMouseDevice::touchEndEvent(const QTouchEvent* event) { - _isTouching = false; - _lastTouch = evalAverageTouchPoints(event->touchPoints()); - _lastTouchTime = _clock.now(); + if (_enableTouch) { + _isTouching = false; + _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); + } } void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { - auto currentPos = evalAverageTouchPoints(event->touchPoints()); - _lastTouchTime = _clock.now(); - - if (!_isTouching) { - _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); - } else { - auto currentMove = currentPos - _lastTouch; - - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); - // Y mouse is inverted positive is pointing up the screen - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); - } + if (_enableTouch) { + auto currentPos = evalAverageTouchPoints(event->touchPoints()); + _lastTouchTime = _clock.now(); - _lastTouch = currentPos; + if (!_isTouching) { + _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + } else { + auto currentMove = currentPos - _lastTouch; + + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); + // Y mouse is inverted positive is pointing up the screen + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); + } + + _lastTouch = currentPos; + } } controller::Input KeyboardMouseDevice::InputDevice::makeInput(Qt::Key code) const { @@ -247,4 +254,3 @@ QString KeyboardMouseDevice::InputDevice::getDefaultMappingConfig() const { static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/keyboardMouse.json"; return MAPPING_JSON; } - diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index a66cc7060b..2fdecf0bba 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -84,6 +84,8 @@ public: void touchUpdateEvent(const QTouchEvent* event); void wheelEvent(QWheelEvent* event); + + static void enableTouch(bool enableTouch) { _enableTouch = enableTouch; } static const QString NAME; @@ -122,6 +124,8 @@ protected: bool _isTouching = false; std::chrono::high_resolution_clock _clock; std::chrono::high_resolution_clock::time_point _lastTouchTime; + + static bool _enableTouch; }; #endif // hifi_KeyboardMouseDevice_h diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp new file mode 100644 index 0000000000..64f02b5df3 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -0,0 +1,132 @@ +// +// TouchscreenDevice.cpp +// input-plugins/src/input-plugins +// +// Created by Triplelexx on 01/31/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "TouchscreenDevice.h" +#include "KeyboardMouseDevice.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +const QString TouchscreenDevice::NAME = "Touchscreen"; + +bool TouchscreenDevice::isSupported() const { + for (auto touchDevice : QTouchDevice::devices()) { + if (touchDevice->type() == QTouchDevice::TouchScreen) { + return true; + } + } + return false; +} + +void TouchscreenDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->update(deltaTime, inputCalibrationData); + }); + + float distanceScaleX, distanceScaleY; + if (_touchPointCount == 1) { + if (_firstTouchVec.x < _currentTouchVec.x) { + distanceScaleX = (_currentTouchVec.x - _firstTouchVec.x) / _screenDPIScale.x; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = distanceScaleX; + } else if (_firstTouchVec.x > _currentTouchVec.x) { + distanceScaleX = (_firstTouchVec.x - _currentTouchVec.x) / _screenDPIScale.x; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = distanceScaleX; + } + // Y axis is inverted, positive is pointing up the screen + if (_firstTouchVec.y > _currentTouchVec.y) { + distanceScaleY = (_firstTouchVec.y - _currentTouchVec.y) / _screenDPIScale.y; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = distanceScaleY; + } else if (_firstTouchVec.y < _currentTouchVec.y) { + distanceScaleY = (_currentTouchVec.y - _firstTouchVec.y) / _screenDPIScale.y; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = distanceScaleY; + } + } else if (_touchPointCount == 2) { + if (_pinchScale > _lastPinchScale && _pinchScale != 0) { + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()] = 1.0f; + } else if (_pinchScale != 0) { + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()] = 1.0f; + } + _lastPinchScale = _pinchScale; + } +} + +void TouchscreenDevice::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { + _axisStateMap.clear(); +} + +void TouchscreenDevice::InputDevice::focusOutEvent() { +} + +void TouchscreenDevice::touchBeginEvent(const QTouchEvent* event) { + const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); + _firstTouchVec = glm::vec2(point.pos().x(), point.pos().y()); + KeyboardMouseDevice::enableTouch(false); + QScreen* eventScreen = event->window()->screen(); + if (_screenDPI != eventScreen->physicalDotsPerInch()) { + _screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX(); + _screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY(); + _screenDPI = eventScreen->physicalDotsPerInch(); + } +} + +void TouchscreenDevice::touchEndEvent(const QTouchEvent* event) { + _touchPointCount = 0; + KeyboardMouseDevice::enableTouch(true); +} + +void TouchscreenDevice::touchUpdateEvent(const QTouchEvent* event) { + const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); + _currentTouchVec = glm::vec2(point.pos().x(), point.pos().y()); + _touchPointCount = event->touchPoints().count(); +} + +void TouchscreenDevice::touchGestureEvent(const QGestureEvent* event) { + if (QGesture* gesture = event->gesture(Qt::PinchGesture)) { + QPinchGesture* pinch = static_cast(gesture); + _pinchScale = pinch->totalScaleFactor(); + } +} + +controller::Input TouchscreenDevice::InputDevice::makeInput(TouchscreenDevice::TouchAxisChannel axis) const { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); +} + +controller::Input TouchscreenDevice::InputDevice::makeInput(TouchscreenDevice::TouchGestureAxisChannel gesture) const { + return controller::Input(_deviceID, gesture, controller::ChannelType::AXIS); +} + +controller::Input::NamedVector TouchscreenDevice::InputDevice::getAvailableInputs() const { + using namespace controller; + static QVector availableInputs; + static std::once_flag once; + std::call_once(once, [&] { + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_POS), "DragRight")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_NEG), "DragLeft")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_POS), "DragUp")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_NEG), "DragDown")); + + availableInputs.append(Input::NamedPair(makeInput(TOUCH_GESTURE_PINCH_POS), "GesturePinchOut")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_GESTURE_PINCH_NEG), "GesturePinchIn")); + }); + return availableInputs; +} + +QString TouchscreenDevice::InputDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/touchscreen.json"; + return MAPPING_JSON; +} diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h new file mode 100644 index 0000000000..f89f247ce8 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h @@ -0,0 +1,84 @@ +// +// TouchscreenDevice.h +// input-plugins/src/input-plugins +// +// Created by Triplelexx on 1/31/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_TouchscreenDevice_h +#define hifi_TouchscreenDevice_h + +#include +#include "InputPlugin.h" +#include + +class QTouchEvent; +class QGestureEvent; + +class TouchscreenDevice : public InputPlugin { + Q_OBJECT +public: + + enum TouchAxisChannel { + TOUCH_AXIS_X_POS = 0, + TOUCH_AXIS_X_NEG, + TOUCH_AXIS_Y_POS, + TOUCH_AXIS_Y_NEG, + }; + + enum TouchGestureAxisChannel { + TOUCH_GESTURE_PINCH_POS = TOUCH_AXIS_Y_NEG + 1, + TOUCH_GESTURE_PINCH_NEG, + }; + + // Plugin functions + virtual bool isSupported() const override; + virtual const QString& getName() const override { return NAME; } + + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + + void touchBeginEvent(const QTouchEvent* event); + void touchEndEvent(const QTouchEvent* event); + void touchUpdateEvent(const QTouchEvent* event); + void touchGestureEvent(const QGestureEvent* event); + + static const QString NAME; + +protected: + + class InputDevice : public controller::InputDevice { + public: + InputDevice() : controller::InputDevice("Touchscreen") {} + private: + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; + virtual void focusOutEvent() override; + + controller::Input makeInput(TouchAxisChannel axis) const; + controller::Input makeInput(TouchGestureAxisChannel gesture) const; + + friend class TouchscreenDevice; + }; + +public: + const std::shared_ptr& getInputDevice() const { return _inputDevice; } + +protected: + qreal _lastPinchScale; + qreal _pinchScale; + qreal _screenDPI; + glm::vec2 _screenDPIScale; + glm::vec2 _firstTouchVec; + glm::vec2 _currentTouchVec; + int _touchPointCount; + std::shared_ptr _inputDevice { std::make_shared() }; +}; + +#endif // hifi_TouchscreenDevice_h diff --git a/libraries/model/src/model/skybox.slv b/libraries/model/src/model/skybox.slv index 810afb1033..5df1aa0a4a 100755 --- a/libraries/model/src/model/skybox.slv +++ b/libraries/model/src/model/skybox.slv @@ -36,4 +36,4 @@ void main(void) { // Position is supposed to come in clip space gl_Position = vec4(inPosition.xy, 0.0, 1.0); -} \ No newline at end of file +} diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index a03fa43093..d7a2d47fab 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -522,7 +522,6 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, const NodePermissions& permissions, const QUuid& connectionSecret) { - QReadLocker readLocker(&_nodeMutex); NodeHash::const_iterator it = _nodeHash.find(uuid); if (it != _nodeHash.end()) { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index c9054ac6d7..483aa0734c 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -131,7 +131,7 @@ public: std::function linkedDataCreateCallback; - size_t size() const { QReadLocker readLock(&_nodeMutex); return _nodeHash.size(); } + size_t size() const { return _nodeHash.size(); } SharedNodePointer nodeWithUUID(const QUuid& nodeUUID); @@ -287,7 +287,7 @@ protected: QUuid _sessionUUID; NodeHash _nodeHash; - mutable QReadWriteLock _nodeMutex; + QReadWriteLock _nodeMutex; udt::Socket _nodeSocket; QUdpSocket* _dtlsSocket; HifiSockAddr _localSockAddr; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 02350ac23c..fd1442d639 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -513,16 +513,9 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { if (_domainHandler.getSockAddr().isNull()) { - qWarning() << "IGNORING DomainList packet while not connected to a Domain Server"; // refuse to process this packet if we aren't currently connected to the DS return; } - QUuid domainHandlerUUID = _domainHandler.getUUID(); - QUuid messageSourceUUID = message->getSourceID(); - if (!domainHandlerUUID.isNull() && domainHandlerUUID != messageSourceUUID) { - qWarning() << "IGNORING DomainList packet from" << messageSourceUUID << "while connected to" << domainHandlerUUID; - return; - } // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4b42ceb4fc..964f309ec7 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -26,9 +26,11 @@ var WANT_DEBUG_SEARCH_NAME = null; // these tune time-averaging and "on" value for analog trigger // +var SPARK_MODEL_SCALE_FACTOR = 0.75; + var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.75; // Squeezed far enough to complete distant grab +var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; @@ -43,6 +45,10 @@ var DRAW_GRAB_BOXES = false; var DRAW_HAND_SPHERES = false; var DROP_WITHOUT_SHAKE = false; +var EQUIP_SPHERE_COLOR = { red: 179, green: 120, blue: 211 }; +var EQUIP_SPHERE_ALPHA = 0.15; +var EQUIP_SPHERE_SCALE_FACTOR = 0.65; + // // distant manipulation // @@ -72,11 +78,14 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray +var EQUIP_RADIUS_EMBIGGEN_FACTOR = 1.1; + // // near grabbing // var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping. +var EQUIP_HOTSPOT_RENDER_RADIUS = 0.3; // radius used for palm vs equip-hotspot for rendering hot-spots var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position @@ -179,9 +188,7 @@ CONTROLLER_STATE_MACHINE[STATE_OFF] = { }; CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { name: "searching", - updateMethod: "search", - enterMethod: "searchEnter", - exitMethod: "searchExit" + updateMethod: "search" }; CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", @@ -261,7 +268,8 @@ function propsArePhysical(props) { var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; var EDIT_SETTING = "io.highfidelity.isEditting"; function isEditing() { - return EXTERNALLY_MANAGED_2D_MINOR_MODE && Settings.getValue(EDIT_SETTING); + var actualSettingValue = Settings.getValue(EDIT_SETTING) === "false" ? false : !!Settings.getValue(EDIT_SETTING); + return EXTERNALLY_MANAGED_2D_MINOR_MODE && actualSettingValue; } function isIn2DMode() { // In this version, we make our own determination of whether we're aimed a HUD element, @@ -281,17 +289,17 @@ function restore2DMode() { function EntityPropertiesCache() { this.cache = {}; } -EntityPropertiesCache.prototype.clear = function() { +EntityPropertiesCache.prototype.clear = function () { this.cache = {}; }; -EntityPropertiesCache.prototype.findEntities = function(position, radius) { +EntityPropertiesCache.prototype.findEntities = function (position, radius) { var entities = Entities.findEntities(position, radius); var _this = this; - entities.forEach(function(x) { + entities.forEach(function (x) { _this.updateEntity(x); }); }; -EntityPropertiesCache.prototype.updateEntity = function(entityID) { +EntityPropertiesCache.prototype.updateEntity = function (entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); // convert props.userData from a string to an object. @@ -307,14 +315,14 @@ EntityPropertiesCache.prototype.updateEntity = function(entityID) { this.cache[entityID] = props; }; -EntityPropertiesCache.prototype.getEntities = function() { +EntityPropertiesCache.prototype.getEntities = function () { return Object.keys(this.cache); }; -EntityPropertiesCache.prototype.getProps = function(entityID) { +EntityPropertiesCache.prototype.getProps = function (entityID) { var obj = this.cache[entityID]; return obj ? obj : undefined; }; -EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { +EntityPropertiesCache.prototype.getGrabbableProps = function (entityID) { var props = this.cache[entityID]; if (props) { return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA; @@ -322,7 +330,7 @@ EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { return undefined; } }; -EntityPropertiesCache.prototype.getGrabProps = function(entityID) { +EntityPropertiesCache.prototype.getGrabProps = function (entityID) { var props = this.cache[entityID]; if (props) { return props.userData.grabKey ? props.userData.grabKey : {}; @@ -330,7 +338,7 @@ EntityPropertiesCache.prototype.getGrabProps = function(entityID) { return undefined; } }; -EntityPropertiesCache.prototype.getWearableProps = function(entityID) { +EntityPropertiesCache.prototype.getWearableProps = function (entityID) { var props = this.cache[entityID]; if (props) { return props.userData.wearable ? props.userData.wearable : {}; @@ -338,7 +346,7 @@ EntityPropertiesCache.prototype.getWearableProps = function(entityID) { return undefined; } }; -EntityPropertiesCache.prototype.getEquipHotspotsProps = function(entityID) { +EntityPropertiesCache.prototype.getEquipHotspotsProps = function (entityID) { var props = this.cache[entityID]; if (props) { return props.userData.equipHotspots ? props.userData.equipHotspots : {}; @@ -356,7 +364,7 @@ function MyController(hand) { this.getHandPosition = MyAvatar.getLeftPalmPosition; // this.getHandRotation = MyAvatar.getLeftPalmRotation; } - this.getHandRotation = function() { + this.getHandRotation = function () { var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; return Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); }; @@ -398,16 +406,18 @@ function MyController(hand) { this.entityPropertyCache = new EntityPropertiesCache(); + this.equipOverlayInfoSetMap = {}; + var _this = this; var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; - this.ignoreInput = function() { + this.ignoreInput = function () { // We've made the decision to use 'this' for new code, even though it is fragile, // in order to keep/ the code uniform without making any no-op line changes. return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; - this.update = function(deltaTime) { + this.update = function (deltaTime) { this.updateSmoothedTrigger(); @@ -429,12 +439,12 @@ function MyController(hand) { } }; - this.callEntityMethodOnGrabbed = function(entityMethodName) { + this.callEntityMethodOnGrabbed = function (entityMethodName) { var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); }; - this.setState = function(newState, reason) { + this.setState = function (newState, reason) { if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); @@ -467,7 +477,7 @@ function MyController(hand) { } }; - this.debugLine = function(closePoint, farPoint, color) { + this.debugLine = function (closePoint, farPoint, color) { Entities.addEntity({ type: "Line", name: "Grab Debug Entity", @@ -487,7 +497,7 @@ function MyController(hand) { }); }; - this.lineOn = function(closePoint, farPoint, color) { + this.lineOn = function (closePoint, farPoint, color) { // draw a line if (this.pointer === null) { this.pointer = Entities.addEntity({ @@ -519,7 +529,7 @@ function MyController(hand) { }; var SEARCH_SPHERE_ALPHA = 0.5; - this.searchSphereOn = function(location, size, color) { + this.searchSphereOn = function (location, size, color) { if (this.searchSphere === null) { var sphereProperties = { position: location, @@ -542,10 +552,10 @@ function MyController(hand) { } }; - this.overlayLineOn = function(closePoint, farPoint, color) { + this.overlayLineOn = function (closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { - lineWidth: 5, + glow: 1.0, start: closePoint, end: farPoint, color: color, @@ -570,7 +580,7 @@ function MyController(hand) { } }; - this.searchIndicatorOn = function(distantPickRay) { + this.searchIndicatorOn = function (distantPickRay) { var handPosition = distantPickRay.origin; var SEARCH_SPHERE_SIZE = 0.011; var SEARCH_SPHERE_FOLLOW_RATE = 0.50; @@ -591,7 +601,7 @@ function MyController(hand) { } }; - this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { + this.handleDistantParticleBeam = function (handPosition, objectPosition, color) { var handToObject = Vec3.subtract(objectPosition, handPosition); var finalRotationObject = Quat.rotationBetween(Vec3.multiply(-1, Vec3.UP), handToObject); @@ -607,7 +617,7 @@ function MyController(hand) { } }; - this.createParticleBeam = function(positionObject, orientationObject, color, speed, spread, lifespan) { + this.createParticleBeam = function (positionObject, orientationObject, color, speed, spread, lifespan) { var particleBeamPropertiesObject = { type: "ParticleEffect", @@ -661,7 +671,7 @@ function MyController(hand) { this.particleBeamObject = Entities.addEntity(particleBeamPropertiesObject); }; - this.updateParticleBeam = function(positionObject, orientationObject, color, speed, spread, lifespan) { + this.updateParticleBeam = function (positionObject, orientationObject, color, speed, spread, lifespan) { Entities.editEntity(this.particleBeamObject, { rotation: orientationObject, position: positionObject, @@ -673,7 +683,7 @@ function MyController(hand) { }); }; - this.evalLightWorldTransform = function(modelPos, modelRot) { + this.evalLightWorldTransform = function (modelPos, modelRot) { var MODEL_LIGHT_POSITION = { x: 0, @@ -693,7 +703,7 @@ function MyController(hand) { }; }; - this.handleSpotlight = function(parentID) { + this.handleSpotlight = function (parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -730,7 +740,7 @@ function MyController(hand) { } }; - this.handlePointLight = function(parentID) { + this.handlePointLight = function (parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -762,21 +772,21 @@ function MyController(hand) { } }; - this.lineOff = function() { + this.lineOff = function () { if (this.pointer !== null) { Entities.deleteEntity(this.pointer); } this.pointer = null; }; - this.overlayLineOff = function() { + this.overlayLineOff = function () { if (this.overlayLine !== null) { Overlays.deleteOverlay(this.overlayLine); } this.overlayLine = null; }; - this.searchSphereOff = function() { + this.searchSphereOff = function () { if (this.searchSphere !== null) { Overlays.deleteOverlay(this.searchSphere); this.searchSphere = null; @@ -785,14 +795,14 @@ function MyController(hand) { } }; - this.particleBeamOff = function() { + this.particleBeamOff = function () { if (this.particleBeamObject !== null) { Entities.deleteEntity(this.particleBeamObject); this.particleBeamObject = null; } }; - this.turnLightsOff = function() { + this.turnLightsOff = function () { if (this.spotlight !== null) { Entities.deleteEntity(this.spotlight); this.spotlight = null; @@ -804,7 +814,7 @@ function MyController(hand) { } }; - this.turnOffVisualizations = function() { + this.turnOffVisualizations = function () { if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOff(); } @@ -821,63 +831,62 @@ function MyController(hand) { }; - this.triggerPress = function(value) { + this.triggerPress = function (value) { _this.rawTriggerValue = value; }; - this.secondaryPress = function(value) { + this.secondaryPress = function (value) { _this.rawSecondaryValue = value; }; - this.updateSmoothedTrigger = function() { + this.updateSmoothedTrigger = function () { var triggerValue = this.rawTriggerValue; // smooth out trigger value this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); }; - this.triggerSmoothedGrab = function() { + this.triggerSmoothedGrab = function () { return this.triggerValue > TRIGGER_GRAB_VALUE; }; - this.triggerSmoothedSqueezed = function() { + this.triggerSmoothedSqueezed = function () { return this.triggerValue > TRIGGER_ON_VALUE; }; - this.triggerSmoothedReleased = function() { + this.triggerSmoothedReleased = function () { return this.triggerValue < TRIGGER_OFF_VALUE; }; - this.secondarySqueezed = function() { + this.secondarySqueezed = function () { return _this.rawSecondaryValue > BUMPER_ON_VALUE; }; - this.secondaryReleased = function() { + this.secondaryReleased = function () { return _this.rawSecondaryValue < BUMPER_ON_VALUE; }; - // this.triggerOrsecondarySqueezed = function() { + // this.triggerOrsecondarySqueezed = function () { // return triggerSmoothedSqueezed() || secondarySqueezed(); // } - // this.triggerAndSecondaryReleased = function() { + // this.triggerAndSecondaryReleased = function () { // return triggerSmoothedReleased() && secondaryReleased(); // } - this.thumbPress = function(value) { + this.thumbPress = function (value) { _this.rawThumbValue = value; }; - this.thumbPressed = function() { + this.thumbPressed = function () { return _this.rawThumbValue > THUMB_ON_VALUE; }; - this.thumbReleased = function() { + this.thumbReleased = function () { return _this.rawThumbValue < THUMB_ON_VALUE; }; - this.off = function() { - + this.off = function () { if (this.triggerSmoothedReleased()) { this.waitForTriggerRelease = false; } @@ -887,165 +896,129 @@ function MyController(hand) { this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; if (this.triggerSmoothedSqueezed()) { this.setState(STATE_SEARCHING, "trigger squeeze detected"); + return; } } - }; - this.createHotspots = function() { - var _this = this; - - var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; - var HAND_EQUIP_SPHERE_ALPHA = 0.7; - var HAND_EQUIP_SPHERE_RADIUS = 0.01; - - var HAND_GRAB_SPHERE_COLOR = { red: 90, green: 90, blue: 255 }; - var HAND_GRAB_SPHERE_ALPHA = 0.3; - var HAND_GRAB_SPHERE_RADIUS = NEAR_GRAB_RADIUS; - - var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 }; - var EQUIP_SPHERE_ALPHA = 0.3; - - var GRAB_BOX_COLOR = { red: 90, green: 90, blue: 255 }; - var GRAB_BOX_ALPHA = 0.1; - - this.hotspotOverlays = []; - - var overlay; - if (DRAW_HAND_SPHERES) { - // add tiny green sphere around the palm. - var handPosition = this.getHandPosition(); - overlay = Overlays.addOverlay("sphere", { - position: handPosition, - size: HAND_EQUIP_SPHERE_RADIUS * 2, - color: HAND_EQUIP_SPHERE_COLOR, - alpha: HAND_EQUIP_SPHERE_ALPHA, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - }); - this.hotspotOverlays.push({ - entityID: undefined, - overlay: overlay, - type: "hand" - }); - - // add larger blue sphere around the palm. - overlay = Overlays.addOverlay("sphere", { - position: handPosition, - size: HAND_GRAB_SPHERE_RADIUS * 2, - color: HAND_GRAB_SPHERE_COLOR, - alpha: HAND_GRAB_SPHERE_ALPHA, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - }); - this.hotspotOverlays.push({ - entityID: undefined, - overlay: overlay, - type: "hand", - localPosition: {x: 0, y: 0, z: 0} - }); - } - - // find entities near the avatar that might be equipable. this.entityPropertyCache.clear(); - this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); + this.entityPropertyCache.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); + var candidateEntities = this.entityPropertyCache.getEntities(); - if (DRAW_GRAB_BOXES) { - // add blue box overlays for grabbable entities. - this.entityPropertyCache.getEntities().forEach(function(entityID) { - var props = _this.entityPropertyCache.getProps(entityID); - if (_this.entityIsGrabbable(entityID)) { - var overlay = Overlays.addOverlay("cube", { - rotation: props.rotation, - position: props.position, - size: props.dimensions, - color: GRAB_BOX_COLOR, - alpha: GRAB_BOX_ALPHA, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - }); - _this.hotspotOverlays.push({ - entityID: entityID, - overlay: overlay, - type: "near", - localPosition: {x: 0, y: 0, z: 0} - }); - } - }); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + if (!this.waitForTriggerRelease) { + this.updateEquipHaptics(potentialEquipHotspot); } - // add green spheres for each equippable hotspot. - flatten(this.entityPropertyCache.getEntities().map(function(entityID) { - return _this.collectEquipHotspots(entityID); - })).filter(function(hotspot) { - return _this.hotspotIsEquippable(hotspot); - }).forEach(function(hotspot) { - var overlay = Overlays.addOverlay("sphere", { - position: hotspot.worldPosition, - size: hotspot.radius * 2, - color: EQUIP_SPHERE_COLOR, - alpha: EQUIP_SPHERE_ALPHA, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - }); - _this.hotspotOverlays.push({ - entityID: hotspot.entityID, - overlay: overlay, - type: "equip", - localPosition: hotspot.localPosition + var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); + this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot); + }; + + this.clearEquipHaptics = function () { + this.prevPotentialEquipHotspot = null; + }; + + this.updateEquipHaptics = function (potentialEquipHotspot) { + if (potentialEquipHotspot && !this.prevPotentialEquipHotspot || + !potentialEquipHotspot && this.prevPotentialEquipHotspot) { + Controller.triggerShortHapticPulse(0.5, this.hand); + } + this.prevPotentialEquipHotspot = potentialEquipHotspot; + }; + + this.clearEquipHotspotRendering = function () { + var keys = Object.keys(this.equipOverlayInfoSetMap); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.equipOverlayInfoSetMap[keys[i]]; + this.deleteOverlayInfoSet(overlayInfoSet); + } + this.equipOverlayInfoSetMap = {}; + }; + + this.createOverlayInfoSet = function (hotspot, timestamp) { + var overlayInfoSet = { + timestamp: timestamp, + entityID: hotspot.entityID, + localPosition: hotspot.localPosition, + hotspot: hotspot, + overlays: [] + }; + + var diameter = hotspot.radius * 2; + + overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", { + position: hotspot.worldPosition, + rotation: {x: 0, y: 0, z: 0, w: 1}, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, + color: EQUIP_SPHERE_COLOR, + alpha: EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + })); + + return overlayInfoSet; + }; + + this.updateOverlayInfoSet = function (overlayInfoSet, timestamp, potentialEquipHotspot) { + overlayInfoSet.timestamp = timestamp; + + var diameter = overlayInfoSet.hotspot.radius * 2; + + // embiggen the overlays if it maches the potentialEquipHotspot + if (potentialEquipHotspot && overlayInfoSet.entityID == potentialEquipHotspot.entityID && + Vec3.equal(overlayInfoSet.localPosition, potentialEquipHotspot.localPosition)) { + diameter = diameter * EQUIP_RADIUS_EMBIGGEN_FACTOR; + } + + var props = _this.entityPropertyCache.getProps(overlayInfoSet.entityID); + var entityXform = new Xform(props.rotation, props.position); + var position = entityXform.xformPoint(overlayInfoSet.localPosition); + + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.editOverlay(overlay, { + position: position, + rotation: props.rotation, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR }); }); }; - this.updateHotspots = function() { + this.deleteOverlayInfoSet = function (overlayInfoSet) { + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.deleteOverlay(overlay); + }); + }; + + this.updateEquipHotspotRendering = function (hotspots, potentialEquipHotspot) { + var now = Date.now(); var _this = this; - var props; - this.hotspotOverlays.forEach(function(overlayInfo) { - if (overlayInfo.type === "hand") { - Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() }); - } else if (overlayInfo.type === "equip") { - _this.entityPropertyCache.updateEntity(overlayInfo.entityID); - props = _this.entityPropertyCache.getProps(overlayInfo.entityID); - var entityXform = new Xform(props.rotation, props.position); - Overlays.editOverlay(overlayInfo.overlay, { - position: entityXform.xformPoint(overlayInfo.localPosition), - rotation: props.rotation - }); - } else if (overlayInfo.type === "near") { - _this.entityPropertyCache.updateEntity(overlayInfo.entityID); - props = _this.entityPropertyCache.getProps(overlayInfo.entityID); - Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation }); + + hotspots.forEach(function (hotspot) { + var overlayInfoSet = _this.equipOverlayInfoSetMap[hotspot.key]; + if (overlayInfoSet) { + _this.updateOverlayInfoSet(overlayInfoSet, now, potentialEquipHotspot); + } else { + _this.equipOverlayInfoSetMap[hotspot.key] = _this.createOverlayInfoSet(hotspot, now); } }); - }; - this.destroyHotspots = function() { - this.hotspotOverlays.forEach(function(overlayInfo) { - Overlays.deleteOverlay(overlayInfo.overlay); - }); - this.hotspotOverlays = []; - }; - - this.searchEnter = function() { - this.createHotspots(); - }; - - this.searchExit = function() { - this.destroyHotspots(); + // delete sets with old timestamps. + var keys = Object.keys(this.equipOverlayInfoSetMap); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.equipOverlayInfoSetMap[keys[i]]; + if (overlayInfoSet.timestamp !== now) { + this.deleteOverlayInfoSet(overlayInfoSet); + delete this.equipOverlayInfoSetMap[keys[i]]; + } + } }; // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND // @returns {object} returns object with two keys entityID and distance // - this.calcRayPickInfo = function(hand) { + this.calcRayPickInfo = function (hand) { var standardControllerValue = (hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; var pose = Controller.getPoseValue(standardControllerValue); @@ -1098,7 +1071,7 @@ function MyController(hand) { } }; - this.entityWantsTrigger = function(entityID) { + this.entityWantsTrigger = function (entityID) { var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); return grabbableProps && grabbableProps.wantsTrigger; }; @@ -1106,13 +1079,14 @@ function MyController(hand) { // returns a list of all equip-hotspots assosiated with this entity. // @param {UUID} entityID // @returns {Object[]} array of objects with the following fields. + // * key {string} a string that can be used to uniquely identify this hotspot // * entityID {UUID} // * localPosition {Vec3} position of the hotspot in object space. // * worldPosition {vec3} position of the hotspot in world space. // * radius {number} radius of equip hotspot // * joints {Object} keys are joint names values are arrays of two elements: // offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint. - this.collectEquipHotspots = function(entityID) { + this.collectEquipHotspots = function (entityID) { var result = []; var props = this.entityPropertyCache.getProps(entityID); var entityXform = new Xform(props.rotation, props.position); @@ -1123,6 +1097,7 @@ function MyController(hand) { var hotspot = equipHotspotsProps[i]; if (hotspot.position && hotspot.radius && hotspot.joints) { result.push({ + key: entityID.toString() + i.toString(), entityID: entityID, localPosition: hotspot.position, worldPosition: entityXform.xformPoint(hotspot.position), @@ -1135,6 +1110,7 @@ function MyController(hand) { var wearableProps = this.entityPropertyCache.getWearableProps(entityID); if (wearableProps && wearableProps.joints) { result.push({ + key: entityID.toString() + "0", entityID: entityID, localPosition: {x: 0, y: 0, z: 0}, worldPosition: entityXform.pos, @@ -1146,7 +1122,7 @@ function MyController(hand) { return result; }; - this.hotspotIsEquippable = function(hotspot) { + this.hotspotIsEquippable = function (hotspot) { var props = this.entityPropertyCache.getProps(hotspot.entityID); var grabProps = this.entityPropertyCache.getGrabProps(hotspot.entityID); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); @@ -1165,7 +1141,7 @@ function MyController(hand) { return true; }; - this.entityIsGrabbable = function(entityID) { + this.entityIsGrabbable = function (entityID) { var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); var grabProps = this.entityPropertyCache.getGrabProps(entityID); var props = this.entityPropertyCache.getProps(entityID); @@ -1217,7 +1193,7 @@ function MyController(hand) { return true; }; - this.entityIsDistanceGrabbable = function(entityID, handPosition) { + this.entityIsDistanceGrabbable = function (entityID, handPosition) { if (!this.entityIsGrabbable(entityID)) { return false; } @@ -1254,7 +1230,7 @@ function MyController(hand) { return true; }; - this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) { + this.entityIsNearGrabbable = function (entityID, handPosition, maxDistance) { if (!this.entityIsGrabbable(entityID)) { return false; @@ -1275,12 +1251,36 @@ function MyController(hand) { return true; }; - this.search = function() { + this.chooseNearEquipHotspots = function (candidateEntities, distance) { + var equippableHotspots = flatten(candidateEntities.map(function (entityID) { + return _this.collectEquipHotspots(entityID); + })).filter(function (hotspot) { + return (_this.hotspotIsEquippable(hotspot) && + Vec3.distance(hotspot.worldPosition, _this.getHandPosition()) < hotspot.radius + distance); + }); + return equippableHotspots; + }; + + this.chooseBestEquipHotspot = function (candidateEntities) { + var DISTANCE = 0; + var equippableHotspots = this.chooseNearEquipHotspots(candidateEntities, DISTANCE); + if (equippableHotspots.length > 0) { + // sort by distance + equippableHotspots.sort(function (a, b) { + var aDistance = Vec3.distance(a.worldPosition, this.getHandPosition()); + var bDistance = Vec3.distance(b.worldPosition, this.getHandPosition()); + return aDistance - bDistance; + }); + return equippableHotspots[0]; + } else { + return null; + } + }; + + this.search = function () { var _this = this; var name; - this.updateHotspots(); - this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; @@ -1298,31 +1298,17 @@ function MyController(hand) { this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS); var candidateEntities = this.entityPropertyCache.getEntities(); - var equippableHotspots = flatten(candidateEntities.map(function(entityID) { - return _this.collectEquipHotspots(entityID); - })).filter(function(hotspot) { - return _this.hotspotIsEquippable(hotspot) && Vec3.distance(hotspot.worldPosition, handPosition) < hotspot.radius; - }); - - var entity; - if (equippableHotspots.length > 0) { - // sort by distance - equippableHotspots.sort(function(a, b) { - var aDistance = Vec3.distance(a.worldPosition, handPosition); - var bDistance = Vec3.distance(b.worldPosition, handPosition); - return aDistance - bDistance; - }); + var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); + if (potentialEquipHotspot) { if (this.triggerSmoothedGrab()) { - this.grabbedHotspot = equippableHotspots[0]; - this.grabbedEntity = equippableHotspots[0].entityID; + this.grabbedHotspot = potentialEquipHotspot; + this.grabbedEntity = potentialEquipHotspot.entityID; this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(this.grabbedEntity).name + "'"); return; - } else { - // TODO: highlight the equippable object? } } - var grabbableEntities = candidateEntities.filter(function(entity) { + var grabbableEntities = candidateEntities.filter(function (entity) { return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE); }); @@ -1339,9 +1325,10 @@ function MyController(hand) { this.intersectionDistance = 0; } + var entity; if (grabbableEntities.length > 0) { // sort by distance - grabbableEntities.sort(function(a, b) { + grabbableEntities.sort(function (a, b) { var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); return aDistance - bDistance; @@ -1354,11 +1341,10 @@ function MyController(hand) { this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); return; } else { - // TODO: highlight the near-triggerable object? + // potentialNearTriggerEntity = entity; } } else { if (this.triggerSmoothedGrab()) { - var props = this.entityPropertyCache.getProps(entity); var grabProps = this.entityPropertyCache.getGrabProps(entity); var refCount = grabProps.refCount ? grabProps.refCount : 0; @@ -1373,10 +1359,9 @@ function MyController(hand) { this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); return; } else { - // TODO: highlight the grabbable object? + // potentialNearGrabEntity = entity; } } - return; } if (rayPickInfo.entityID) { @@ -1388,7 +1373,7 @@ function MyController(hand) { this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); return; } else { - // TODO: highlight the far-triggerable object? + // potentialFarTriggerEntity = entity; } } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing()) { @@ -1396,11 +1381,16 @@ function MyController(hand) { this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); return; } else { - // TODO: highlight the far-grabbable object? + // potentialFarGrabEntity = entity; } } } + this.updateEquipHaptics(potentialEquipHotspot); + + var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); + this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot); + // search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { this.lineOn(rayPickInfo.searchRay.origin, @@ -1412,7 +1402,7 @@ function MyController(hand) { Reticle.setVisible(false); }; - this.distanceGrabTimescale = function(mass, distance) { + this.distanceGrabTimescale = function (mass, distance) { var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / DISTANCE_HOLDING_UNITY_MASS * distance / DISTANCE_HOLDING_UNITY_DISTANCE; @@ -1422,11 +1412,14 @@ function MyController(hand) { return timeScale; }; - this.getMass = function(dimensions, density) { + this.getMass = function (dimensions, density) { return (dimensions.x * dimensions.y * dimensions.z) * density; }; - this.distanceHoldingEnter = function() { + this.distanceHoldingEnter = function () { + + this.clearEquipHotspotRendering(); + this.clearEquipHaptics(); // controller pose is in avatar frame var avatarControllerPose = @@ -1485,7 +1478,7 @@ function MyController(hand) { this.previousControllerRotation = controllerRotation; }; - this.distanceHolding = function() { + this.distanceHolding = function () { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "trigger released"); @@ -1617,7 +1610,7 @@ function MyController(hand) { this.previousControllerRotation = controllerRotation; }; - this.setupHoldAction = function() { + this.setupHoldAction = function () { this.actionID = Entities.addAction("hold", this.grabbedEntity, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, @@ -1637,7 +1630,7 @@ function MyController(hand) { return true; }; - this.projectVectorAlongAxis = function(position, axisStart, axisEnd) { + this.projectVectorAlongAxis = function (position, axisStart, axisEnd) { var aPrime = Vec3.subtract(position, axisStart); var bPrime = Vec3.subtract(axisEnd, axisStart); var bPrimeMagnitude = Vec3.length(bPrime); @@ -1653,12 +1646,12 @@ function MyController(hand) { return projection; }; - this.dropGestureReset = function() { + this.dropGestureReset = function () { this.fastHandMoveDetected = false; this.fastHandMoveTimer = 0; }; - this.dropGestureProcess = function(deltaTime) { + this.dropGestureProcess = function (deltaTime) { var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; var pose = Controller.getPoseValue(standardControllerValue); var worldHandVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); @@ -1697,12 +1690,16 @@ function MyController(hand) { return (DROP_WITHOUT_SHAKE || this.fastHandMoveDetected) && handIsUpsideDown; }; - this.nearGrabbingEnter = function() { + this.nearGrabbingEnter = function () { this.lineOff(); this.overlayLineOff(); this.dropGestureReset(); + this.clearEquipHaptics(); + this.clearEquipHotspotRendering(); + + Controller.triggerShortHapticPulse(1.0, this.hand); if (this.entityActivated) { var saveGrabbedID = this.grabbedEntity; @@ -1802,7 +1799,7 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.nearGrabbing = function(deltaTime) { + this.nearGrabbing = function (deltaTime) { var dropDetected = this.dropGestureProcess(deltaTime); @@ -1919,15 +1916,23 @@ function MyController(hand) { } }; - this.nearTriggerEnter = function() { + this.nearTriggerEnter = function () { + + this.clearEquipHotspotRendering(); + this.clearEquipHaptics(); + + Controller.triggerShortHapticPulse(1.0, this.hand); this.callEntityMethodOnGrabbed("startNearTrigger"); }; - this.farTriggerEnter = function() { + this.farTriggerEnter = function () { + this.clearEquipHotspotRendering(); + this.clearEquipHaptics(); + this.callEntityMethodOnGrabbed("startFarTrigger"); }; - this.nearTrigger = function() { + this.nearTrigger = function () { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopNearTrigger"); this.setState(STATE_OFF, "trigger released"); @@ -1936,7 +1941,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueNearTrigger"); }; - this.farTrigger = function() { + this.farTrigger = function () { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopFarTrigger"); this.setState(STATE_OFF, "trigger released"); @@ -1969,11 +1974,11 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueFarTrigger"); }; - this.offEnter = function() { + this.offEnter = function () { this.release(); }; - this.release = function() { + this.release = function () { this.turnLightsOff(); this.turnOffVisualizations(); @@ -2025,14 +2030,14 @@ function MyController(hand) { } }; - this.cleanup = function() { + this.cleanup = function () { this.release(); Entities.deleteEntity(this.particleBeamObject); Entities.deleteEntity(this.spotLight); Entities.deleteEntity(this.pointLight); }; - this.heartBeat = function(entityID) { + this.heartBeat = function (entityID) { var now = Date.now(); if (now - this.lastHeartBeat > HEART_BEAT_INTERVAL) { var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); @@ -2042,7 +2047,7 @@ function MyController(hand) { } }; - this.resetAbandonedGrab = function(entityID) { + this.resetAbandonedGrab = function (entityID) { print("cleaning up abandoned grab on " + entityID); var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); data["refCount"] = 1; @@ -2050,7 +2055,7 @@ function MyController(hand) { this.deactivateEntity(entityID, false); }; - this.activateEntity = function(entityID, grabbedProperties, wasLoaded) { + this.activateEntity = function (entityID, grabbedProperties, wasLoaded) { if (this.entityActivated) { return; } @@ -2112,18 +2117,18 @@ function MyController(hand) { return data; }; - this.checkForStrayChildren = function() { + this.checkForStrayChildren = function () { // sometimes things can get parented to a hand and this script is unaware. Search for such entities and // unhook them. var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); - children.forEach(function(childID) { + children.forEach(function (childID) { print("disconnecting stray child of hand: (" + _this.hand + ") " + childID); Entities.editEntity(childID, {parentID: NULL_UUID}); }); }; - this.deactivateEntity = function(entityID, noVelocity) { + this.deactivateEntity = function (entityID, noVelocity) { var deactiveProps; if (!this.entityActivated) { @@ -2208,7 +2213,7 @@ function MyController(hand) { setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; - this.getOtherHandController = function() { + this.getOtherHandController = function () { return (this.hand === RIGHT_HAND) ? leftController : rightController; }; } @@ -2250,7 +2255,7 @@ Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); -var handleHandMessages = function(channel, message, sender) { +var handleHandMessages = function (channel, message, sender) { var data; if (sender === MyAvatar.sessionUUID) { if (channel === 'Hifi-Hand-Disabler') { diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp index 15b768bb36..1a4f8742e9 100644 --- a/tests/controllers/src/main.cpp +++ b/tests/controllers/src/main.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -91,7 +92,7 @@ public: virtual QOpenGLContext* getPrimaryContext() override { return nullptr; } virtual ui::Menu* getPrimaryMenu() override { return nullptr; } virtual bool isForeground() const override { return true; } - virtual DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } + virtual DisplayPluginPointer getActiveDisplayPlugin() const override { return DisplayPluginPointer(); } }; class MyControllerScriptingInterface : public controller::ScriptingInterface { @@ -144,6 +145,9 @@ int main(int argc, char** argv) { if (name == KeyboardMouseDevice::NAME) { userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)->getInputDevice()); } + if (name == TouchscreenDevice::NAME) { + userInputMapper->registerDevice(std::dynamic_pointer_cast(inputPlugin)->getInputDevice()); + } inputPlugin->pluginUpdate(0, calibrationData); } rootContext->setContextProperty("Controllers", new MyControllerScriptingInterface());