diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index f25e0cc3c4..5c0ea09939 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -9,6 +9,8 @@ "to": "Actions.StepYaw", "filters": [ + { "type": "deadZone", "min": 0.15 }, + "constrainToInteger", { "type": "pulse", "interval": 0.5 }, { "type": "scale", "scale": 22.5 } ] diff --git a/interface/resources/icons/hud-01.svg b/interface/resources/icons/hud-01.svg new file mode 100644 index 0000000000..4929389268 --- /dev/null +++ b/interface/resources/icons/hud-01.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 241cd91611..e269a63cde 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -299,7 +299,7 @@ FocusScope { if (pinned) { // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ - return !d.isTopLevelWindow(child) && child.visible; + return !d.isTopLevelWindow(child) && child.visible && !child.pinned; }); hiddenChildren.forEach(function(child){ diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 59278a17b4..e127a235e6 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -4,11 +4,13 @@ import QtWebEngine 1.1; import "../desktop" import ".." +import "." Desktop { id: desktop MouseArea { + id: hoverWatch anchors.fill: parent hoverEnabled: true propagateComposedEvents: true @@ -47,7 +49,12 @@ Desktop { } } + + ToggleHudButton { + anchors.bottom: parent.bottom + anchors.bottomMargin: 32 + anchors.horizontalCenter: parent.horizontalCenter + } } - diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml new file mode 100644 index 0000000000..6454f42305 --- /dev/null +++ b/interface/resources/qml/hifi/ToggleHudButton.qml @@ -0,0 +1,36 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "../windows" + +Window { + //frame: HiddenFrame {} + hideBackground: true + resizable: false + destroyOnCloseButton: false + destroyOnHidden: false + closable: false + shown: true + pinned: true + width: 50 + height: 50 + clip: true + visible: true + + Item { + width: 50 + height: 50 + Image { + y: desktop.pinned ? -50 : 0 + id: hudToggleImage + source: "../../icons/hud-01.svg" + } + MouseArea { + readonly property string overlayMenuItem: "Overlays" + anchors.fill: parent + onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)) + } + } +} + + diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 7dc7654d9a..e6be336657 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -23,9 +23,9 @@ Window { title: "Running Scripts" resizable: true destroyOnHidden: true - implicitWidth: 400 + implicitWidth: 424 implicitHeight: isHMD ? 695 : 728 - minSize: Qt.vector2d(200, 300) + minSize: Qt.vector2d(424, 300) HifiConstants { id: hifi } @@ -86,6 +86,11 @@ Window { scripts.reloadAllScripts(); } + function loadDefaults() { + console.log("Load default scripts"); + scripts.loadOneScript(scripts.defaultScriptsPath + "/defaultScripts.js"); + } + function stopAll() { console.log("Stop all scripts"); scripts.stopAllScripts(); @@ -104,13 +109,13 @@ Window { spacing: hifi.dimensions.contentSpacing.x HifiControls.Button { - text: "Reload all" + text: "Reload All" color: hifi.buttons.black onClicked: reloadAll() } HifiControls.Button { - text: "Stop all" + text: "Remove All" color: hifi.buttons.red onClicked: stopAll() } @@ -218,7 +223,6 @@ Window { Row { spacing: hifi.dimensions.contentSpacing.x - anchors.right: parent.right HifiControls.Button { text: "from URL" @@ -256,6 +260,12 @@ Window { onTriggered: ApplicationInterface.loadDialog(); } } + + HifiControls.Button { + text: "Load Defaults" + color: hifi.buttons.black + onClicked: loadDefaults() + } } HifiControls.VerticalSpacer {} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8f4d80a86f..1e6f7ba995 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -197,7 +198,6 @@ static const float PHYSICS_READY_RANGE = 3.0f; // how far from avatar to check f static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); -static const QString INPUT_DEVICE_MENU_PREFIX = "Device: "; Setting::Handle maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS); const QHash Application::_acceptedExtensions { @@ -999,7 +999,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : RenderableWebEntityItem* webEntity = dynamic_cast(entity.get()); if (webEntity) { webEntity->setProxyWindow(_window->windowHandle()); - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _keyboardFocusedItem = entityItemID; @@ -1152,9 +1152,7 @@ void Application::aboutToQuit() { emit beforeAboutToQuit(); foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action->isChecked()) { + if (inputPlugin->isActive()) { inputPlugin->deactivate(); } } @@ -1418,7 +1416,7 @@ void Application::initializeUi() { rootContext->setContextProperty("Overlays", &_overlays); rootContext->setContextProperty("Window", DependencyManager::get().data()); - rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance()); + rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); rootContext->setContextProperty("Stats", Stats::getInstance()); rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); @@ -1476,7 +1474,6 @@ void Application::initializeUi() { } } _window->setMenuBar(new Menu()); - updateInputModes(); auto compositorHelper = DependencyManager::get(); connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, [=] { @@ -2024,7 +2021,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyPressEvent(event); } @@ -2359,7 +2356,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->keyReleaseEvent(event); } @@ -2391,9 +2388,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) { void Application::focusOutEvent(QFocusEvent* event) { auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { - QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action && action->isChecked()) { + if (inputPlugin->isActive()) { inputPlugin->pluginFocusOutEvent(); } } @@ -2478,7 +2473,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseMoveEvent(event); } @@ -2515,7 +2510,7 @@ void Application::mousePressEvent(QMouseEvent* event) { if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mousePressEvent(event); } @@ -2560,7 +2555,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { } if (hasFocus()) { - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mouseReleaseEvent(event); } @@ -2587,7 +2582,7 @@ void Application::touchUpdateEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchUpdateEvent(event); } } @@ -2605,7 +2600,7 @@ void Application::touchBeginEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchBeginEvent(event); } @@ -2622,7 +2617,7 @@ void Application::touchEndEvent(QTouchEvent* event) { return; } - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->touchEndEvent(event); } @@ -2638,7 +2633,7 @@ void Application::wheelEvent(QWheelEvent* event) const { return; } - if (Menu::getInstance()->isOptionChecked(INPUT_DEVICE_MENU_PREFIX + KeyboardMouseDevice::NAME)) { + if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->wheelEvent(event); } } @@ -2771,9 +2766,7 @@ void Application::idle(float nsecsElapsed) { getActiveDisplayPlugin()->idle(); auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); foreach(auto inputPlugin, inputPlugins) { - QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); - QAction* action = Menu::getInstance()->getActionForOption(name); - if (action && action->isChecked()) { + if (inputPlugin->isActive()) { inputPlugin->idle(); } } @@ -2957,6 +2950,27 @@ void Application::loadSettings() { //DependencyManager::get()->setAutomaticLODAdjust(false); Menu::getInstance()->loadSettings(); + + // If there is a preferred plugin, we probably messed it up with the menu settings, so fix it. + auto pluginManager = PluginManager::getInstance(); + auto plugins = pluginManager->getPreferredDisplayPlugins(); + for (auto plugin : plugins) { + auto menu = Menu::getInstance(); + if (auto action = menu->getActionForOption(plugin->getName())) { + action->setChecked(true); + action->trigger(); + // Find and activated highest priority plugin, bail for the rest + break; + } + } + + auto inputs = pluginManager->getInputPlugins(); + for (auto plugin : inputs) { + if (!plugin->isActive()) { + plugin->activate(); + } + } + getMyAvatar()->loadData(); _settingsLoaded = true; @@ -4937,7 +4951,34 @@ void Application::postLambdaEvent(std::function f) { } } -void Application::initPlugins() { +void Application::initPlugins(const QStringList& arguments) { + QCommandLineOption display("display", "Preferred displays", "displays"); + QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); + QCommandLineOption disableInputs("disable-inputs", "Inputs to disable", "inputs"); + + QCommandLineParser parser; + parser.addOption(display); + parser.addOption(disableDisplays); + parser.addOption(disableInputs); + parser.parse(arguments); + + if (parser.isSet(display)) { + auto preferredDisplays = parser.value(display).split(',', QString::SkipEmptyParts); + qInfo() << "Setting prefered display plugins:" << preferredDisplays; + PluginManager::getInstance()->setPreferredDisplayPlugins(preferredDisplays); + } + + if (parser.isSet(disableDisplays)) { + auto disabledDisplays = parser.value(disableDisplays).split(',', QString::SkipEmptyParts); + qInfo() << "Disabling following display plugins:" << disabledDisplays; + PluginManager::getInstance()->disableDisplays(disabledDisplays); + } + + if (parser.isSet(disableInputs)) { + auto disabledInputs = parser.value(disableInputs).split(',', QString::SkipEmptyParts); + qInfo() << "Disabling following input plugins:" << disabledInputs; + PluginManager::getInstance()->disableInputs(disabledInputs); + } } void Application::shutdownPlugins() { @@ -5207,81 +5248,6 @@ void Application::updateDisplayMode() { Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } -static void addInputPluginToMenu(InputPluginPointer inputPlugin) { - auto menu = Menu::getInstance(); - QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); - Q_ASSERT(!menu->menuItemExists(MenuOption::InputMenu, name)); - - static QActionGroup* inputPluginGroup = nullptr; - if (!inputPluginGroup) { - inputPluginGroup = new QActionGroup(menu); - inputPluginGroup->setExclusive(false); - } - - auto parent = menu->getMenu(MenuOption::InputMenu); - auto action = menu->addCheckableActionToQMenuAndActionHash(parent, - name, 0, true, qApp, - SLOT(updateInputModes())); - - inputPluginGroup->addAction(action); - Q_ASSERT(menu->menuItemExists(MenuOption::InputMenu, name)); -} - - -void Application::updateInputModes() { - auto menu = Menu::getInstance(); - auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); - static std::once_flag once; - std::call_once(once, [&] { - foreach(auto inputPlugin, inputPlugins) { - addInputPluginToMenu(inputPlugin); - } - }); - auto offscreenUi = DependencyManager::get(); - - InputPluginList newInputPlugins; - InputPluginList removedInputPlugins; - foreach(auto inputPlugin, inputPlugins) { - QString name = INPUT_DEVICE_MENU_PREFIX + inputPlugin->getName(); - QAction* action = menu->getActionForOption(name); - - auto it = std::find(std::begin(_activeInputPlugins), std::end(_activeInputPlugins), inputPlugin); - if (action->isChecked() && it == std::end(_activeInputPlugins)) { - _activeInputPlugins.push_back(inputPlugin); - newInputPlugins.push_back(inputPlugin); - } else if (!action->isChecked() && it != std::end(_activeInputPlugins)) { - _activeInputPlugins.erase(it); - removedInputPlugins.push_back(inputPlugin); - } - } - - // A plugin was checked - if (newInputPlugins.size() > 0) { - foreach(auto newInputPlugin, newInputPlugins) { - newInputPlugin->activate(); - //newInputPlugin->installEventFilter(qApp); - //newInputPlugin->installEventFilter(offscreenUi.data()); - } - } - if (removedInputPlugins.size() > 0) { // A plugin was unchecked - foreach(auto removedInputPlugin, removedInputPlugins) { - removedInputPlugin->deactivate(); - //removedInputPlugin->removeEventFilter(qApp); - //removedInputPlugin->removeEventFilter(offscreenUi.data()); - } - } - - //if (newInputPlugins.size() > 0 || removedInputPlugins.size() > 0) { - // if (!_currentInputPluginActions.isEmpty()) { - // auto menu = Menu::getInstance(); - // foreach(auto itemInfo, _currentInputPluginActions) { - // menu->removeMenuItem(itemInfo.first, itemInfo.second); - // } - // _currentInputPluginActions.clear(); - // } - //} -} - mat4 Application::getEyeProjection(int eye) const { QMutexLocker viewLocker(&_viewMutex); if (isHMDMode()) { diff --git a/interface/src/Application.h b/interface/src/Application.h index a17250a58e..f93434f581 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -101,7 +101,7 @@ public: }; // FIXME? Empty methods, do we still need them? - static void initPlugins(); + static void initPlugins(const QStringList& arguments); static void shutdownPlugins(); Application(int& argc, char** argv, QElapsedTimer& startup_time); @@ -327,7 +327,6 @@ private slots: void nodeKilled(SharedNodePointer node); static void packetSent(quint64 length); void updateDisplayMode(); - void updateInputModes(); void domainConnectionRefused(const QString& reasonMessage, int reason); private: diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a21aa71753..031564fa7a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -403,12 +403,6 @@ Menu::Menu() { // Developer > Avatar >>> MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); - // Settings > Input Devices - MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced"); - QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); - inputModeGroup->setExclusive(false); - - // Developer > Avatar > Face Tracking MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index fcaf8e6caa..8081e27eb8 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -113,7 +113,6 @@ namespace MenuOption { const QString Help = "Help..."; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; - const QString InputMenu = "Developer>Avatar>Input Devices"; const QString ActionMotorControl = "Enable Default Motor Control"; const QString LeapMotionOnHMD = "Leap Motion on HMD"; const QString LoadScript = "Open and Run Script File..."; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f48104235d..62772c6933 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1214,7 +1214,10 @@ void MyAvatar::updateMotors() { if (_characterController.getState() == CharacterController::State::Hover) { motorRotation = getHead()->getCameraOrientation(); } else { - motorRotation = getOrientation(); + // non-hovering = walking: follow camera twist about vertical but not lift + // so we decompose camera's rotation and store the twist part in motorRotation + glm::quat liftRotation; + swingTwistDecomposition(getHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation); } const float DEFAULT_MOTOR_TIMESCALE = 0.2f; const float INVALID_MOTOR_TIMESCALE = 1.0e6f; diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 13f9470fda..8fc0384aee 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -46,6 +46,12 @@ int main(int argc, const char* argv[]) { bool instanceMightBeRunning = true; + QStringList arguments; + for (int i = 0; i < argc; ++i) { + arguments << argv[i]; + } + + #ifdef Q_OS_WIN // Try to create a shared memory block - if it can't be created, there is an instance of // interface already running. We only do this on Windows for now because of the potential @@ -64,12 +70,6 @@ int main(int argc, const char* argv[]) { // Try to connect - if we can't connect, interface has probably just gone down if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) { - - QStringList arguments; - for (int i = 0; i < argc; ++i) { - arguments << argv[i]; - } - QCommandLineParser parser; QCommandLineOption urlOption("url", "", "value"); parser.addOption(urlOption); @@ -135,7 +135,7 @@ int main(int argc, const char* argv[]) { // Oculus initialization MUST PRECEDE OpenGL context creation. // The nature of the Application constructor means this has to be either here, // or in the main window ctor, before GL startup. - Application::initPlugins(); + Application::initPlugins(arguments); int exitCode; { diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index b0ef6c760d..b803080538 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -14,6 +14,10 @@ ClipboardScriptingInterface::ClipboardScriptingInterface() { } +glm::vec3 ClipboardScriptingInterface::getContentsDimensions() { + return qApp->getEntityClipboard()->getContentsDimensions(); +} + float ClipboardScriptingInterface::getClipboardContentsLargestDimension() { return qApp->getEntityClipboard()->getContentsLargestDimension(); } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 73bebd4836..4737a194df 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -22,6 +22,7 @@ signals: void readyToImport(); public slots: + glm::vec3 getContentsDimensions(); /// returns the overall dimensions of everything on the blipboard float getClipboardContentsLargestDimension(); /// returns the largest dimension of everything on the clipboard bool importEntities(const QString& filename); bool exportEntities(const QString& filename, const QVector& entityIDs); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 16e4bd5437..ff7438bb17 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -632,13 +632,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { #endif int numBytesRead = sourceBuffer - startPosition; - - if (numBytesRead != buffer.size()) { - if (shouldLogError(now)) { - qCWarning(avatars) << "AvatarData packet size mismatch: expected " << numBytesRead << " received " << buffer.size(); - } - } - _averageBytesReceived.updateAverage(numBytesRead); return numBytesRead; } @@ -1270,6 +1263,10 @@ static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); static const QString JSON_AVATAR_SCALE = QStringLiteral("scale"); +static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); + +static const int JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION = 0; +static const int JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION = 1; QJsonValue toJsonValue(const JointData& joint) { QJsonArray result; @@ -1293,6 +1290,8 @@ JointData jointDataFromJsonValue(const QJsonValue& json) { QJsonObject AvatarData::toJson() const { QJsonObject root; + root[JSON_AVATAR_VERSION] = JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION; + if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); } @@ -1359,6 +1358,15 @@ QJsonObject AvatarData::toJson() const { } void AvatarData::fromJson(const QJsonObject& json) { + + int version; + if (json.contains(JSON_AVATAR_VERSION)) { + version = json[JSON_AVATAR_VERSION].toInt(); + } else { + // initial data did not have a version field. + version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION; + } + // The head setOrientation likes to overwrite the avatar orientation, // so lets do the head first // Most head data is relative to the avatar, and needs no basis correction, @@ -1424,20 +1432,28 @@ void AvatarData::fromJson(const QJsonObject& json) { // } // } - // Joint rotations are relative to the avatar, so they require no basis correction if (json.contains(JSON_AVATAR_JOINT_ARRAY)) { - QVector jointArray; - QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); - jointArray.reserve(jointArrayJson.size()); - int i = 0; - for (const auto& jointJson : jointArrayJson) { - auto joint = jointDataFromJsonValue(jointJson); - jointArray.push_back(joint); - setJointData(i, joint.rotation, joint.translation); - _jointData[i].rotationSet = true; // Have to do that to broadcast the avatar new pose - i++; + if (version == JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION) { + // because we don't have the full joint hierarchy skeleton of the model, + // we can't properly convert from relative rotations into absolute rotations. + quint64 now = usecTimestampNow(); + if (shouldLogError(now)) { + qCWarning(avatars) << "Version 0 avatar recordings not supported. using default rotations"; + } + } else { + QVector jointArray; + QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray(); + jointArray.reserve(jointArrayJson.size()); + int i = 0; + for (const auto& jointJson : jointArrayJson) { + auto joint = jointDataFromJsonValue(jointJson); + jointArray.push_back(joint); + setJointData(i, joint.rotation, joint.translation); + _jointData[i].rotationSet = true; // Have to do that to broadcast the avatar new pose + i++; + } + setRawJointData(jointArray); } - setRawJointData(jointArray); } } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 15c2bffd80..d09fc60d9b 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1121,6 +1121,27 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { return result; } +QVector EntityScriptingInterface::getChildrenIDs(const QUuid& parentID) { + QVector result; + if (!_entityTree) { + return result; + } + + EntityItemPointer entity = _entityTree->findEntityByEntityItemID(parentID); + if (!entity) { + qDebug() << "EntityScriptingInterface::getChildrenIDs - no entity with ID" << parentID; + return result; + } + + _entityTree->withReadLock([&] { + entity->forEachChild([&](SpatiallyNestablePointer child) { + result.push_back(child->getID()); + }); + }); + + return result; +} + QVector EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex) { QVector result; if (!_entityTree) { diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 2f5446874b..8ae6a77dab 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -171,6 +171,7 @@ public slots: Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); + Q_INVOKABLE QVector getChildrenIDs(const QUuid& parentID); Q_INVOKABLE QVector getChildrenIDsOfJoint(const QUuid& parentID, int jointIndex); signals: diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b85ee1dcf9..581e0a9568 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1284,6 +1284,7 @@ class ContentsDimensionOperator : public RecurseOctreeOperator { public: virtual bool preRecursion(OctreeElementPointer element); virtual bool postRecursion(OctreeElementPointer element) { return true; } + glm::vec3 getDimensions() const { return _contentExtents.size(); } float getLargestDimension() const { return _contentExtents.largestDimension(); } private: Extents _contentExtents; @@ -1295,6 +1296,12 @@ bool ContentsDimensionOperator::preRecursion(OctreeElementPointer element) { return true; } +glm::vec3 EntityTree::getContentsDimensions() { + ContentsDimensionOperator theOperator; + recurseTreeWithOperator(&theOperator); + return theOperator.getDimensions(); +} + float EntityTree::getContentsLargestDimension() { ContentsDimensionOperator theOperator; recurseTreeWithOperator(&theOperator); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 03b8a9b55a..a85624c9ae 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -207,6 +207,7 @@ public: bool skipThoseWithBadParents) override; virtual bool readFromMap(QVariantMap& entityDescription) override; + glm::vec3 getContentsDimensions(); float getContentsLargestDimension(); virtual void resetEditStats() override { diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 37a0f36d2f..a7baeb361b 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -55,7 +55,7 @@ void EntityTreeElement::debugExtraEncodeData(EncodeBitstreamParams& params) cons if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); qCDebug(entities) << " encode data:" << entityTreeElementExtraEncodeData; } else { qCDebug(entities) << " encode data: MISSING!!"; @@ -97,7 +97,7 @@ bool EntityTreeElement::shouldIncludeChildData(int childIndex, EncodeBitstreamPa if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); bool childCompleted = entityTreeElementExtraEncodeData->childCompleted[childIndex]; @@ -126,7 +126,7 @@ bool EntityTreeElement::alreadyFullyEncoded(EncodeBitstreamParams& params) const if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); // If we know that ALL subtrees below us have already been recursed, then we don't // need to recurse this child. @@ -140,7 +140,7 @@ void EntityTreeElement::updateEncodedData(int childIndex, AppendState childAppen assert(extraEncodeData); // EntityTrees always require extra encode data on their encoding passes if (extraEncodeData->contains(this)) { EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); if (childAppendState == OctreeElement::COMPLETED) { entityTreeElementExtraEncodeData->childCompleted[childIndex] = true; @@ -165,7 +165,7 @@ void EntityTreeElement::elementEncodeComplete(EncodeBitstreamParams& params) con assert(extraEncodeData->contains(this)); EntityTreeElementExtraEncodeData* thisExtraEncodeData - = static_cast(extraEncodeData->value(this)); + = static_cast((*extraEncodeData)[this]); // Note: this will be called when OUR element has finished running through encodeTreeBitstreamRecursion() // which means, it's possible that our parent element hasn't finished encoding OUR data... so @@ -241,7 +241,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData bool hadElementExtraData = false; if (extraEncodeData && extraEncodeData->contains(this)) { entityTreeElementExtraEncodeData = - static_cast(extraEncodeData->value(this)); + static_cast((*extraEncodeData)[this]); hadElementExtraData = true; } else { // if there wasn't one already, then create one @@ -268,7 +268,7 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData //assert(extraEncodeData); //assert(extraEncodeData->contains(this)); - //entityTreeElementExtraEncodeData = static_cast(extraEncodeData->value(this)); + //entityTreeElementExtraEncodeData = static_cast((*extraEncodeData)[this]); LevelDetails elementLevel = packetData->startLevel(); diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 388ca26482..14518ac37a 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _renderer->_renderControl->_renderWindow = _proxyWindow; + connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // Create a QML engine. _qmlEngine = new QQmlEngine; if (!_qmlEngine->incubationController()) { @@ -742,3 +744,21 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function funct return function(); } + +void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + if (!object) { + setFocusText(false); + return; + } + + QInputMethodQueryEvent query(Qt::ImEnabled); + qApp->sendEvent(object, &query); + setFocusText(query.value(Qt::ImEnabled).toBool()); +} + +void OffscreenQmlSurface::setFocusText(bool newFocusText) { + if (newFocusText != _focusText) { + _focusText = newFocusText; + emit focusTextChanged(_focusText); + } +} diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 22a1b99fe6..1ce7276877 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -30,7 +30,7 @@ class OffscreenQmlRenderThread; class OffscreenQmlSurface : public QObject { Q_OBJECT - + Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: OffscreenQmlSurface(); virtual ~OffscreenQmlSurface(); @@ -55,6 +55,7 @@ public: _mouseTranslator = mouseTranslator; } + bool isFocusText() const { return _focusText; } void pause(); void resume(); bool isPaused() const; @@ -70,6 +71,8 @@ public: signals: void textureUpdated(unsigned int texture); + void focusObjectChanged(QObject* newFocus); + void focusTextChanged(bool focusText); public slots: void requestUpdate(); @@ -78,6 +81,7 @@ public slots: protected: bool filterEnabled(QObject* originalDestination, QEvent* event) const; + void setFocusText(bool newFocusText); private: QObject* finishQmlLoad(std::function f); @@ -85,6 +89,7 @@ private: private slots: void updateQuick(); + void onFocusObjectChanged(QObject* newFocus); private: friend class OffscreenQmlRenderThread; @@ -97,6 +102,7 @@ private: bool _render{ false }; bool _polish{ true }; bool _paused{ true }; + bool _focusText { false }; uint8_t _maxFps{ 60 }; MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; QWindow* _proxyWindow { nullptr }; diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 4d59adb602..32c28af2ef 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -25,7 +25,6 @@ InputPluginList getInputPlugins() { for (int i = 0; PLUGIN_POOL[i]; ++i) { InputPlugin* plugin = PLUGIN_POOL[i]; if (plugin->isSupported()) { - plugin->init(); result.push_back(InputPluginPointer(plugin)); } } diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index eb6465aab2..7161132c5e 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -25,6 +25,50 @@ PluginManager* PluginManager::getInstance() { return &_manager; } +QString getPluginNameFromMetaData(QJsonObject object) { + static const char* METADATA_KEY = "MetaData"; + static const char* NAME_KEY = "name"; + + if (!object.contains(METADATA_KEY) || !object[METADATA_KEY].isObject()) { + return QString(); + } + + auto metaDataObject = object[METADATA_KEY].toObject(); + + if (!metaDataObject.contains(NAME_KEY) || !metaDataObject[NAME_KEY].isString()) { + return QString(); + } + + return metaDataObject[NAME_KEY].toString(); +} + +QString getPluginIIDFromMetaData(QJsonObject object) { + static const char* IID_KEY = "IID"; + + if (!object.contains(IID_KEY) || !object[IID_KEY].isString()) { + return QString(); + } + + return object[IID_KEY].toString(); +} + +QStringList preferredDisplayPlugins; +QStringList disabledDisplays; +QStringList disabledInputs; + +bool isDisabled(QJsonObject metaData) { + auto name = getPluginNameFromMetaData(metaData); + auto iid = getPluginIIDFromMetaData(metaData); + + if (iid == DisplayProvider_iid) { + return disabledDisplays.contains(name); + } else if (iid == InputProvider_iid) { + return disabledInputs.contains(name); + } + + return false; +} + using Loader = QSharedPointer; using LoaderList = QList; @@ -43,11 +87,21 @@ const LoaderList& getLoadedPlugins() { qDebug() << "Loading runtime plugins from " << pluginPath; auto candidates = pluginDir.entryList(); for (auto plugin : candidates) { - qDebug() << "Attempting plugins " << plugin; + qDebug() << "Attempting plugin" << qPrintable(plugin); QSharedPointer loader(new QPluginLoader(pluginPath + plugin)); + + if (isDisabled(loader->metaData())) { + qWarning() << "Plugin" << qPrintable(plugin) << "is disabled"; + // Skip this one, it's disabled + continue; + } + if (loader->load()) { - qDebug() << "Plugins " << plugin << " success"; + qDebug() << "Plugin" << qPrintable(plugin) << "loaded successfully"; loadedPlugins.push_back(loader); + } else { + qDebug() << "Plugin" << qPrintable(plugin) << "failed to load:"; + qDebug() << " " << qPrintable(loader->errorString()); } } } @@ -110,7 +164,9 @@ const InputPluginList& PluginManager::getInputPlugins() { InputProvider* inputProvider = qobject_cast(loader->instance()); if (inputProvider) { for (auto inputPlugin : inputProvider->getInputPlugins()) { - inputPlugins.push_back(inputPlugin); + if (inputPlugin->isSupported()) { + inputPlugins.push_back(inputPlugin); + } } } } @@ -124,6 +180,40 @@ const InputPluginList& PluginManager::getInputPlugins() { return inputPlugins; } +void PluginManager::setPreferredDisplayPlugins(const QStringList& displays) { + preferredDisplayPlugins = displays; +} + +DisplayPluginList PluginManager::getPreferredDisplayPlugins() { + static DisplayPluginList displayPlugins; + + static std::once_flag once; + std::call_once(once, [&] { + // Grab the built in plugins + auto plugins = getDisplayPlugins(); + + for (auto pluginName : preferredDisplayPlugins) { + auto it = std::find_if(plugins.begin(), plugins.end(), [&](DisplayPluginPointer plugin) { + return plugin->getName() == pluginName; + }); + if (it != plugins.end()) { + displayPlugins.push_back(*it); + } + } + }); + + return displayPlugins; +} + + +void PluginManager::disableDisplays(const QStringList& displays) { + disabledDisplays << displays; +} + +void PluginManager::disableInputs(const QStringList& inputs) { + disabledInputs << inputs; +} + void PluginManager::saveSettings() { saveInputPluginSettings(getInputPlugins()); } diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index cf0b8efe64..2a94e6490b 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -13,11 +13,17 @@ class PluginManager : public QObject { public: - static PluginManager* getInstance(); - PluginManager(); + static PluginManager* getInstance(); + PluginManager(); - const DisplayPluginList& getDisplayPlugins(); - void disableDisplayPlugin(const QString& name); - const InputPluginList& getInputPlugins(); - void saveSettings(); + const DisplayPluginList& getDisplayPlugins(); + const InputPluginList& getInputPlugins(); + + DisplayPluginList getPreferredDisplayPlugins(); + void setPreferredDisplayPlugins(const QStringList& displays); + + void disableDisplayPlugin(const QString& name); + void disableDisplays(const QStringList& displays); + void disableInputs(const QStringList& inputs); + void saveSettings(); }; diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index cad2a0286f..b2f23f5a04 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -28,7 +28,9 @@ Settings::~Settings() { } void Settings::remove(const QString& key) { - _manager->remove(key); + if (key == "" || _manager->contains(key)) { + _manager->remove(key); + } } QStringList Settings::childGroups() const { @@ -72,7 +74,9 @@ void Settings::endGroup() { } void Settings::setValue(const QString& name, const QVariant& value) { - _manager->setValue(name, value); + if (_manager->value(name) != value) { + _manager->setValue(name, value); + } } QVariant Settings::value(const QString& name, const QVariant& defaultValue) const { diff --git a/plugins/hifiNeuron/src/plugin.json b/plugins/hifiNeuron/src/plugin.json index 0967ef424b..d153b5cebd 100644 --- a/plugins/hifiNeuron/src/plugin.json +++ b/plugins/hifiNeuron/src/plugin.json @@ -1 +1 @@ -{} +{"name":"Neuron"} diff --git a/plugins/hifiSdl2/src/plugin.json b/plugins/hifiSdl2/src/plugin.json index 0967ef424b..a65846ecab 100644 --- a/plugins/hifiSdl2/src/plugin.json +++ b/plugins/hifiSdl2/src/plugin.json @@ -1 +1 @@ -{} +{"name":"SDL2"} diff --git a/plugins/hifiSixense/src/plugin.json b/plugins/hifiSixense/src/plugin.json index 0967ef424b..9e6e15a354 100644 --- a/plugins/hifiSixense/src/plugin.json +++ b/plugins/hifiSixense/src/plugin.json @@ -1 +1 @@ -{} +{"name":"Sixense"} diff --git a/plugins/hifiSpacemouse/src/plugin.json b/plugins/hifiSpacemouse/src/plugin.json index 0967ef424b..294f436039 100644 --- a/plugins/hifiSpacemouse/src/plugin.json +++ b/plugins/hifiSpacemouse/src/plugin.json @@ -1 +1 @@ -{} +{"name":"Spacemouse"} diff --git a/plugins/oculus/src/oculus.json b/plugins/oculus/src/oculus.json index 0967ef424b..86546c8dd5 100644 --- a/plugins/oculus/src/oculus.json +++ b/plugins/oculus/src/oculus.json @@ -1 +1 @@ -{} +{"name":"Oculus Rift"} diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index f89e71b829..8e044fbc16 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -31,7 +31,7 @@ using namespace oglplus; -const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift (0.5) (Legacy)"); +const QString OculusLegacyDisplayPlugin::NAME("Oculus Rift"); OculusLegacyDisplayPlugin::OculusLegacyDisplayPlugin() { } diff --git a/plugins/oculusLegacy/src/oculus.json b/plugins/oculusLegacy/src/oculus.json index 0967ef424b..86546c8dd5 100644 --- a/plugins/oculusLegacy/src/oculus.json +++ b/plugins/oculusLegacy/src/oculus.json @@ -1 +1 @@ -{} +{"name":"Oculus Rift"} diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fe406cc29a..fbade9fd68 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -36,12 +36,14 @@ vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount]; static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; +bool _openVrDisplayActive { false }; bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported(); } bool OpenVrDisplayPlugin::internalActivate() { + _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); if (!_system) { @@ -94,6 +96,7 @@ bool OpenVrDisplayPlugin::internalActivate() { void OpenVrDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); + _openVrDisplayActive = false; _container->setIsOptionChecked(StandingHMDSensorMode, false); if (_system) { // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 155bc9f079..1ff1c65ef8 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -14,9 +14,13 @@ #include #include #include +#include +#include #include +#include + Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -90,6 +94,121 @@ void releaseOpenVrSystem() { } } +static char textArray[8192]; + +static QMetaObject::Connection _focusConnection, _focusTextConnection; +extern bool _openVrDisplayActive; +static vr::IVROverlay* _overlay { nullptr }; +static QObject* _keyboardFocusObject { nullptr }; +static QString _existingText; +static Qt::InputMethodHints _currentHints; +extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; +static bool _keyboardShown { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 100; + +void showOpenVrKeyboard(bool show = true) { + if (!_overlay) { + return; + } + + if (show) { + // To avoid flickering the keyboard when a text element is only briefly selected, + // show the keyboard asynchrnously after a very short delay, but only after we check + // that the current focus object is still one that is text enabled + QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] { + auto offscreenUi = DependencyManager::get(); + auto currentFocus = offscreenUi->getWindow()->focusObject(); + QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints); + qApp->sendEvent(currentFocus, &query); + // Current focus isn't text enabled, bail early. + if (!query.value(Qt::ImEnabled).toBool()) { + return; + } + // We're going to show the keyboard now... + _keyboardFocusObject = currentFocus; + _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt()); + vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal; + if (_currentHints & Qt::ImhHiddenText) { + inputMode = vr::k_EGamepadTextInputModePassword; + } + vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine; + if (_currentHints & Qt::ImhMultiLine) { + lineMode = vr::k_EGamepadTextInputLineModeMultipleLines; + } + _existingText = query.value(Qt::ImSurroundingText).toString(); + + auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, + _existingText.toLocal8Bit().toStdString().c_str(), false, 0); + + if (vr::VROverlayError_None == showKeyboardResult) { + _keyboardShown = true; + // Try to position the keyboard slightly below where the user is looking. + mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); + keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); + auto keyboardTransformVr = toOpenVr(keyboardTransform); + _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); + } + }); + } else { + _keyboardFocusObject = nullptr; + if (_keyboardShown) { + _overlay->HideKeyboard(); + _keyboardShown = false; + } + } +} + +void finishOpenVrKeyboardInput() { + auto offscreenUi = DependencyManager::get(); + auto chars = _overlay->GetKeyboardText(textArray, 8192); + auto newText = QString(QByteArray(textArray, chars)); + // TODO modify the new text to match the possible input hints: + // ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly + // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly + QInputMethodEvent event(_existingText, QList()); + event.setCommitString(newText, 0, _existingText.size()); + qApp->sendEvent(_keyboardFocusObject, &event); + // Simulate an enter press on the top level window to trigger the action + if (0 == (_currentHints & Qt::ImhMultiLine)) { + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers())); + } +} + +static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); +bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + +void enableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } + auto offscreenUi = DependencyManager::get(); + _overlay = vr::VROverlay(); + + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { + if (object != _keyboardFocusObject) { + showOpenVrKeyboard(false); + } + }); + + _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { + if (_openVrDisplayActive) { + showOpenVrKeyboard(focusText); + } + }); +} + + +void disableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } + QObject::disconnect(_focusTextConnection); + QObject::disconnect(_focusConnection); +} + + void handleOpenVrEvents() { if (!activeHmd) { return; @@ -107,6 +226,10 @@ void handleOpenVrEvents() { activeHmd->AcknowledgeQuit_Exiting(); break; + case vr::VREvent_KeyboardDone: + finishOpenVrKeyboardInput(); + break; + default: break; } @@ -114,3 +237,4 @@ void handleOpenVrEvents() { } } + diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 1e5914844c..426178cd65 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -18,6 +18,9 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); +void enableOpenVrKeyboard(); +void disableOpenVrKeyboard(); + template void openvr_for_each_eye(F f) { @@ -41,3 +44,13 @@ inline mat4 toGlm(const vr::HmdMatrix34_t& m) { m.m[0][3], m.m[1][3], m.m[2][3], 1.0f); return result; } + +inline vr::HmdMatrix34_t toOpenVr(const mat4& m) { + vr::HmdMatrix34_t result; + for (uint8_t i = 0; i < 3; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + result.m[i][j] = m[j][i]; + } + } + return result; +} diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 739d3cde10..6d3ce46e82 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,8 +11,6 @@ #include "ViveControllerManager.h" -#include - #include #include #include @@ -22,6 +20,7 @@ #include #include #include +#include #include @@ -68,6 +67,8 @@ bool ViveControllerManager::activate() { } Q_ASSERT(_system); + enableOpenVrKeyboard(); + // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code /* @@ -132,6 +133,8 @@ bool ViveControllerManager::activate() { void ViveControllerManager::deactivate() { InputPlugin::deactivate(); + disableOpenVrKeyboard(); + _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); diff --git a/plugins/openvr/src/plugin.json b/plugins/openvr/src/plugin.json index 0967ef424b..d68c8e68d3 100644 --- a/plugins/openvr/src/plugin.json +++ b/plugins/openvr/src/plugin.json @@ -1 +1 @@ -{} +{"name":"OpenVR (Vive)"} diff --git a/scripts/system/assets/images/tools/hmd-switch-01.svg b/scripts/system/assets/images/tools/hmd-switch-01.svg index 15ecb02b6b..31389d355c 100644 --- a/scripts/system/assets/images/tools/hmd-switch-01.svg +++ b/scripts/system/assets/images/tools/hmd-switch-01.svg @@ -4,99 +4,121 @@ viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index be4bffe58c..8a96e9d80c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -92,6 +92,7 @@ var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new posit // other constants // +var HOTSPOT_DRAW_DISTANCE = 10; var RIGHT_HAND = 1; var LEFT_HAND = 0; @@ -179,47 +180,85 @@ var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_HOLD_SEARCHING: - return "hold_searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP: - return "equip"; - case STATE_HOLD: - return "hold"; - case STATE_CONTINUE_HOLD: - return "continue_hold"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - return "waiting_for_equip_thumb_release"; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - return "waiting_for_release_thumb_release"; - } +var CONTROLLER_STATE_MACHINE = {}; - return "unknown"; +CONTROLLER_STATE_MACHINE[STATE_OFF] = { + name: "off", + updateMethod: "off" +}; +CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { + name: "searching", + updateMethod: "search", + enterMethod: "searchEnter", + exitMethod: "searchExit" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { + name: "hold_searching", + updateMethod: "search" +}; +CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { + name: "distance_holding", + updateMethod: "distanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_DISTANCE_HOLDING] = { + name: "continue_distance_holding", + updateMethod: "continueDistanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { + name: "near_grabbing", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { + name: "equip", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD] = { + name: "hold", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_GRABBING] = { + name: "continue_near_grabbing", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_HOLD] = { + name: "continue_hold", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_EQUIP] = { + name: "continue_equip", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { + name: "near_trigger", + updateMethod: "nearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_TRIGGER] = { + name: "continue_near_trigger", + updateMethod: "continueNearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { + name: "far_trigger", + updateMethod: "farTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { + name: "continue_far_trigger", + updateMethod: "continueFarTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_RELEASE] = { + name: "release", + updateMethod: "release" +}; +CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { + name: "waiting_for_equip_thumb_release", + updateMethod: "waitingForEquipThumbRelease" +}; +CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_RELEASE_THUMB_RELEASE] = { + name: "waiting_for_release_thumb_release", + updateMethod: "waitingForReleaseThumbRelease" +}; + +function stateToName(state) { + return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; } function getTag() { @@ -249,6 +288,14 @@ function entityIsGrabbedByOther(entityID) { return false; } +function propsArePhysical(props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType != 'none'); + return isPhysical; +} + // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; @@ -314,55 +361,22 @@ function MyController(hand) { this.update = function() { this.updateSmoothedTrigger(); + if (isIn2DMode()) { _this.turnOffVisualizations(); return; } - switch (this.state) { - case STATE_OFF: - this.off(); - break; - case STATE_SEARCHING: - case STATE_HOLD_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - case STATE_HOLD: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - this.waitingForEquipThumbRelease(); - break; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - this.waitingForReleaseThumbRelease(); - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_HOLD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; + + if (CONTROLLER_STATE_MACHINE[this.state]) { + var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; + var updateMethod = this[updateMethodName]; + if (updateMethod) { + updateMethod.call(this); + } else { + print("WARNING: could not find updateMethod for state " + stateToName(this.state)); + } + } else { + print("WARNING: could not find state " + this.state + " in state machine"); } }; @@ -374,9 +388,33 @@ function MyController(hand) { this.setState = function(newState) { this.grabSphereOff(); if (WANT_DEBUG || WANT_DEBUG_STATE) { - print("STATE (" + this.hand + "): " + stateToName(this.state) + " --> " + - stateToName(newState) + ", hand: " + this.hand); + var oldStateName = stateToName(this.state); + var newStateName = stateToName(newState); + print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName); } + + // exit the old state + if (CONTROLLER_STATE_MACHINE[this.state]) { + var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; + var exitMethod = this[exitMethodName]; + if (exitMethod) { + exitMethod.call(this); + } + } else { + print("WARNING: could not find state " + this.state + " in state machine"); + } + + // enter the new state + if (CONTROLLER_STATE_MACHINE[newState]) { + var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; + var enterMethod = this[enterMethodName]; + if (enterMethod) { + enterMethod.call(this); + } + } else { + print("WARNING: could not find newState " + newState + " in state machine"); + } + this.state = newState; }; @@ -759,14 +797,6 @@ function MyController(hand) { } }; - this.propsArePhysical = function(props) { - if (!props.dynamic) { - return false; - } - var isPhysical = (props.shapeType && props.shapeType != 'none'); - return isPhysical; - } - this.turnOffVisualizations = function() { if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOff(); @@ -852,6 +882,56 @@ function MyController(hand) { } }; + this.searchEnter = function() { + this.equipHotspotOverlays = []; + + // find entities near the avatar that might be equipable. + var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); + var i, l = entities.length; + for (i = 0; i < l; i++) { + + // is this entity equipable? + var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); + var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); + if (grabData) { + + var hotspotPos = grabProps.position; + + // does this entity have an attach point? + var wearableData = getEntityCustomData("wearable", entities[i], undefined); + if (wearableData) { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (wearableData[handJointName]) { + // draw the hotspot around the attach point. + hotspotPos = wearableData[handJointName][0]; + } + } + + // draw a hotspot! + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { + position: hotspotPos, + size: 0.2, + color: { red: 90, green: 255, blue: 90 }, + alpha: 0.7, + solid: true, + visible: true, + ignoreRayIntersection: false, + drawInFront: false + })); + } + } + }; + + this.searchExit = function() { + + // delete all equip hotspots + var i, l = this.equipHotspotOverlays.length; + for (i = 0; i < l; i++) { + Overlays.deleteOverlay(this.equipHotspotOverlays[i]); + } + this.equipHotspotOverlays = []; + }; + this.search = function() { this.grabbedEntity = null; this.isInitialGrab = false; @@ -863,6 +943,7 @@ function MyController(hand) { this.setState(STATE_RELEASE); return; } + if (this.state == STATE_HOLD_SEARCHING && this.bumperReleased()) { this.setState(STATE_RELEASE); return; @@ -951,7 +1032,7 @@ function MyController(hand) { var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - var isPhysical = this.propsArePhysical(propsForCandidate); + var isPhysical = propsArePhysical(propsForCandidate); var grabbable; if (isPhysical) { // physical things default to grabbable @@ -1030,7 +1111,7 @@ function MyController(hand) { if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.bumperSqueezed())) { // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; - var isPhysical = this.propsArePhysical(props); + var isPhysical = propsArePhysical(props); // near or far trigger if (grabbableData.wantsTrigger) { @@ -1462,7 +1543,7 @@ function MyController(hand) { } } - var isPhysical = this.propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); + var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); if (isPhysical && this.state == STATE_NEAR_GRABBING) { // grab entity via action if (!this.setupHoldAction()) { @@ -1879,7 +1960,7 @@ function MyController(hand) { var forceVelocity = false; var doSetVelocity = false; - if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && this.propsArePhysical(props)) { + if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && propsArePhysical(props)) { // TODO: EntityScriptingInterface::convertLocationToScriptSemantics should be setting up // props.velocity to be a world-frame velocity and localVelocity to be vs parent. Until that // is done, we use a measured velocity here so that things held via a bumper-grab / parenting-grab diff --git a/scripts/system/edit.js b/scripts/system/edit.js index afbc679ec4..38d596f83e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -181,7 +181,7 @@ var toolBar = (function() { function initialize() { toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.edit.toolbar", function(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2, + x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), y: windowDimensions.y }; }, { diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 9d33e473af..6f4268182c 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -60,7 +60,7 @@ var toolBar = (function() { function initialize() { toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2, + x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), y: windowDimensions.y }; }, { @@ -135,4 +135,4 @@ var toolBar = (function() { }()); Controller.mousePressEvent.connect(toolBar.mousePressEvent) -Script.scriptEnding.connect(toolBar.cleanup); \ No newline at end of file +Script.scriptEnding.connect(toolBar.cleanup); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 75d9829905..00b5e912c0 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -13,7 +13,7 @@ Script.include("libraries/toolBars.js"); function initialPosition(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2 - Tool.IMAGE_WIDTH, + x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 1), y: windowDimensions.y }; } diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 277af68315..8b91e45676 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -22,7 +22,7 @@ var desktopMenuItemName = "Desktop"; function initialPosition(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2 + (2 * Tool.IMAGE_WIDTH), + x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 2.5), y: windowDimensions.y }; } diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index d752734de4..33408fb821 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -51,7 +51,7 @@ ApplicationWindow { Button { text: "toggle desktop" - onClicked: desktop.toggleVisible() + onClicked: desktop.togglePinned() } // Error alerts