From e42dada208c00c11c9ea70afb3aa5fd22431a9c9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 2 Dec 2020 21:46:03 +0100 Subject: [PATCH] Load interaction profile suggestions from xml instead of hardcoding them. --- apps/openmw/CMakeLists.txt | 3 + apps/openmw/engine.cpp | 23 +- apps/openmw/mwvr/openxractionset.cpp | 3 +- apps/openmw/mwvr/openxractionset.hpp | 2 +- apps/openmw/mwvr/openxrinput.cpp | 51 +++- apps/openmw/mwvr/openxrinput.hpp | 6 + apps/openmw/mwvr/openxrmanagerimpl.cpp | 6 +- apps/openmw/mwvr/vrinput.hpp | 3 +- apps/openmw/mwvr/vrinputmanager.cpp | 399 +++++++------------------ apps/openmw/mwvr/vrinputmanager.hpp | 27 +- files/xrcontrollersuggestions.xml | 260 ++++++++++++++++ 11 files changed, 470 insertions(+), 313 deletions(-) create mode 100644 files/xrcontrollersuggestions.xml diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index db6f3c9a3..93faa009f 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -258,6 +258,9 @@ if(BUILD_OPENMW_VR) ${GAME} ${GAME_HEADER} ${APPLE_BUNDLE_RESOURCES} ) + + configure_resource_file(${OpenMW_SOURCE_DIR}/files/xrcontrollersuggestions.xml + "${OpenMW_BINARY_DIR}" "xrcontrollersuggestions.xml") ########### Import the OpenXR SDK # Force the openxr-sdk to use its bundled jsoncpp to avoid problems from system jsoncpp if present diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f6ae860aa..da6e0ef3d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -718,10 +718,29 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) Version::getOpenmwVersionDescription(mResDir.string()), mCfgMgr.getUserConfigPath().string()); mEnvironment.setWindowManager (window); - MWInput::InputManager* input = #ifdef USE_OPENXR - new MWVR::VRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); + const std::string xrinputuserdefault = mCfgMgr.getUserConfigPath().string() + "/xrcontrollersuggestions.xml"; + const std::string xrinputlocaldefault = mCfgMgr.getLocalPath().string() + "/xrcontrollersuggestions.xml"; + const std::string xrinputglobaldefault = mCfgMgr.getGlobalPath().string() + "/xrcontrollersuggestions.xml"; + + std::string xrControllerSuggestions; + if (boost::filesystem::exists(xrinputuserdefault)) + xrControllerSuggestions = xrinputuserdefault; + else if (boost::filesystem::exists(xrinputlocaldefault)) + xrControllerSuggestions = xrinputlocaldefault; + else if (boost::filesystem::exists(xrinputglobaldefault)) + xrControllerSuggestions = xrinputglobaldefault; + else + xrControllerSuggestions = ""; //if it doesn't exist, pass in an empty string + + Log(Debug::Verbose) << "xrinputuserdefault: " << xrinputuserdefault; + Log(Debug::Verbose) << "xrinputlocaldefault: " << xrinputlocaldefault; + Log(Debug::Verbose) << "xrinputglobaldefault: " << xrinputglobaldefault; + + MWInput::InputManager* input = + new MWVR::VRInputManager(mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab, xrControllerSuggestions); #else + MWInput::InputManager* input = new MWInput::InputManager (mWindow, mViewer, mScreenCaptureHandler, mScreenCaptureOperation, keybinderUser, keybinderUserExists, userGameControllerdb, gameControllerdb, mGrab); #endif mEnvironment.setInputManager (input); diff --git a/apps/openmw/mwvr/openxractionset.cpp b/apps/openmw/mwvr/openxractionset.cpp index 92dab2d15..43f6b3d71 100644 --- a/apps/openmw/mwvr/openxractionset.cpp +++ b/apps/openmw/mwvr/openxractionset.cpp @@ -120,7 +120,8 @@ namespace MWVR const std::string& actionName, const std::string& localName) { - mActionMap.emplace(openMWAction, new A(openMWAction, std::move(createXRAction(AT, mInternalName + "_" + actionName, mLocalizedName + " " + localName)))); + auto xrAction = createXRAction(AT, mInternalName + "_" + actionName, mLocalizedName + " " + localName); + mActionMap.emplace(actionName, new A(openMWAction, std::move(xrAction))); } XrActionSet diff --git a/apps/openmw/mwvr/openxractionset.hpp b/apps/openmw/mwvr/openxractionset.hpp index c1913daab..f44b1aa87 100644 --- a/apps/openmw/mwvr/openxractionset.hpp +++ b/apps/openmw/mwvr/openxractionset.hpp @@ -43,7 +43,7 @@ namespace MWVR XrActionSet mActionSet{ nullptr }; std::string mLocalizedName{}; std::string mInternalName{}; - std::map> mActionMap; + std::map> mActionMap; std::map> mTrackerMap; std::map> mHapticsMap; std::deque mActionQueue{}; diff --git a/apps/openmw/mwvr/openxrinput.cpp b/apps/openmw/mwvr/openxrinput.cpp index 978c2cbd3..d16a23a34 100644 --- a/apps/openmw/mwvr/openxrinput.cpp +++ b/apps/openmw/mwvr/openxrinput.cpp @@ -28,9 +28,9 @@ namespace MWVR return it->second; } - void OpenXRInput::suggestBindings(ActionSet actionSet, std::string profile, const SuggestedBindings& mwSuggestedBindings) + void OpenXRInput::suggestBindings(ActionSet actionSet, std::string profilePath, const SuggestedBindings& mwSuggestedBindings) { - getActionSet(actionSet).suggestBindings(mSuggestedBindings[profile], mwSuggestedBindings); + getActionSet(actionSet).suggestBindings(mSuggestedBindings[profilePath], mwSuggestedBindings); } void OpenXRInput::attachActionSets() @@ -48,6 +48,8 @@ namespace MWVR xrProfileSuggestedBindings.suggestedBindings = profile.second.data(); xrProfileSuggestedBindings.countSuggestedBindings = (uint32_t)profile.second.size(); CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrProfileSuggestedBindings)); + mInteractionProfileNames[profilePath] = profile.first; + mInteractionProfilePaths[profile.first] = profilePath; } // OpenXR requires that xrAttachSessionActionSets be called at most once per session. @@ -62,4 +64,49 @@ namespace MWVR attachInfo.actionSets = actionSets.data(); CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().xrSession(), &attachInfo)); } + + void OpenXRInput::notifyInteractionProfileChanged() + { + auto xr = MWVR::Environment::get().getManager(); + xr->impl().xrSession(); + + // Unfortunately, openxr does not tell us WHICH profile has changed. + std::array topLevelUserPaths = + { + "/user/hand/left", + "/user/hand/right", + "/user/head", + "/user/gamepad", + "/user/treadmill" + }; + + for (auto& userPath : topLevelUserPaths) + { + auto pathIt = mInteractionProfilePaths.find(userPath); + if (pathIt == mInteractionProfilePaths.end()) + { + XrPath xrUserPath = XR_NULL_PATH; + CHECK_XRCMD( + xrStringToPath(xr->impl().xrInstance(), userPath.c_str(), &xrUserPath)); + mInteractionProfilePaths[userPath] = xrUserPath; + pathIt = mInteractionProfilePaths.find(userPath); + } + + XrInteractionProfileState interactionProfileState{ + XR_TYPE_INTERACTION_PROFILE_STATE + }; + + xrGetCurrentInteractionProfile(xr->impl().xrSession(), pathIt->second, &interactionProfileState); + if (interactionProfileState.interactionProfile) + { + auto activeProfileIt = mActiveInteractionProfiles.find(pathIt->second); + if (activeProfileIt == mActiveInteractionProfiles.end() || interactionProfileState.interactionProfile != activeProfileIt->second) + { + auto activeProfileNameIt = mInteractionProfileNames.find(interactionProfileState.interactionProfile); + Log(Debug::Verbose) << userPath << ": Interaction profile changed to '" << activeProfileNameIt->second << "'"; + mActiveInteractionProfiles[pathIt->second] = interactionProfileState.interactionProfile; + } + } + } + } } diff --git a/apps/openmw/mwvr/openxrinput.hpp b/apps/openmw/mwvr/openxrinput.hpp index 14fb1f2ea..b81eb96c4 100644 --- a/apps/openmw/mwvr/openxrinput.hpp +++ b/apps/openmw/mwvr/openxrinput.hpp @@ -28,8 +28,14 @@ namespace MWVR //! Set bindings and attach actionSets to the session. void attachActionSets(); + //! Notify that active interaction profile has changed + void notifyInteractionProfileChanged(); + protected: std::map mActionSets{}; + std::map mInteractionProfileNames{}; + std::map mInteractionProfilePaths{}; + std::map mActiveInteractionProfiles; XrProfileSuggestedBindings mSuggestedBindings{}; bool mAttached = false; }; diff --git a/apps/openmw/mwvr/openxrmanagerimpl.cpp b/apps/openmw/mwvr/openxrmanagerimpl.cpp index 07d15ad46..0f96ad8eb 100644 --- a/apps/openmw/mwvr/openxrmanagerimpl.cpp +++ b/apps/openmw/mwvr/openxrmanagerimpl.cpp @@ -2,6 +2,8 @@ #include "openxrdebug.hpp" #include "openxrswapchain.hpp" #include "openxrswapchainimpl.hpp" +#include "vrenvironment.hpp" +#include "vrinputmanager.hpp" #include #include @@ -685,8 +687,10 @@ namespace MWVR return handleSessionStateChanged(*stateChangeEvent); break; } - case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: + MWVR::Environment::get().getInputManager()->notifyInteractionProfileChanged(); + break; + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: default: { diff --git a/apps/openmw/mwvr/vrinput.hpp b/apps/openmw/mwvr/vrinput.hpp index 7f534b9d3..413fc5bbb 100644 --- a/apps/openmw/mwvr/vrinput.hpp +++ b/apps/openmw/mwvr/vrinput.hpp @@ -29,8 +29,8 @@ namespace MWVR /// \brief Suggest a binding by binding an action to a path on a given hand (left or right). struct SuggestedBinding { - int action; std::string path; + std::string action; }; using SuggestedBindings = std::vector; @@ -124,7 +124,6 @@ namespace MWVR void updateAndQueue(std::deque& queue); protected: - std::unique_ptr mXRAction; int mOpenMWAction; float mValue{ 0.f }; diff --git a/apps/openmw/mwvr/vrinputmanager.cpp b/apps/openmw/mwvr/vrinputmanager.cpp index 037659f30..216c475d1 100644 --- a/apps/openmw/mwvr/vrinputmanager.cpp +++ b/apps/openmw/mwvr/vrinputmanager.cpp @@ -35,6 +35,7 @@ #include "../mwrender/camera.hpp" #include +#include #include @@ -57,6 +58,11 @@ namespace MWVR return mXRInput->getActionSet(ActionSet::Gameplay); } + void VRInputManager::notifyInteractionProfileChanged() + { + mXRInput->notifyInteractionProfileChanged(); + } + void VRInputManager::updateActivationIndication(void) { bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); @@ -68,7 +74,6 @@ namespace MWVR } } - /** * Makes it possible to use ItemModel::moveItem to move an item from an inventory to the world. */ @@ -184,302 +189,86 @@ namespace MWVR } } - void VRInputManager::suggestBindingsSimple() + void VRInputManager::throwDocumentError(TiXmlElement* element, std::string error) { - std::string simpleProfilePath = "/interaction_profiles/khr/simple_controller"; - // Set up default bindings for the khronos simple controller. - // Note: The simple controller is the equivalent to a universal "default". - // It has highly reduced functionality. Only poses and two click actions - // are available for each hand, reducing the possible functionality of the profile - // to that of a wonky preview. - // The available click actions are 'select' and 'menu', and i cannot control what - // real buttons this is mapped to. On the Oculus Touch they are X, Y, A, and B. - - // In-game character controls - SuggestedBindings simpleGameplayBindings{ - {MWInput::A_Use, "/user/hand/left/input/select/click"}, // Touch: X - {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, // Touch: Y - {A_Recenter, "/user/hand/left/input/menu/click"}, // Touch: Y - {A_ActivateTouch, "/user/hand/right/input/select/click"}, // Touch: A - {MWInput::A_AutoMove, "/user/hand/right/input/menu/click"}, // Touch: B - }; - - // GUI controls - SuggestedBindings simpleGUIBindings{ - {MWInput::A_Use, "/user/hand/left/input/select/click"}, // Touch: X - {MWInput::A_GameMenu, "/user/hand/left/input/menu/click"}, // Touch: Y - {A_Recenter, "/user/hand/left/input/menu/click"}, // Touch: Y - {A_MenuSelect, "/user/hand/right/input/select/click"}, // Touch: A - {A_MenuBack, "/user/hand/right/input/menu/click"}, // Touch: B - }; - mXRInput->suggestBindings(ActionSet::Gameplay, simpleProfilePath, simpleGameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, simpleProfilePath, simpleGUIBindings); + std::stringstream ss; + ss << mXrControllerSuggestionsFile << "." << element->Row() << "." << element->Value(); + ss << ": " << error; + throw std::runtime_error(ss.str()); } - void VRInputManager::suggestBindingsOculusTouch() + std::string VRInputManager::requireAttribute(TiXmlElement* element, std::string attribute) { - std::string controllerProfilePath = "/interaction_profiles/oculus/touch_controller"; - - // In-game character controls - SuggestedBindings gameplayBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, - {MWInput::A_Sneak, "/user/hand/left/input/squeeze/value"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, - {MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, - {MWInput::A_ToggleSpell, "/user/hand/left/input/x/click"}, - {MWInput::A_Rest, "/user/hand/left/input/y/click"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, - {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/value"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/value"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, - {MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - }; - - // GUI controls - SuggestedBindings GUIBindings{ - {A_MenuUpDown, "/user/hand/right/input/thumbstick/y"}, - {A_MenuLeftRight, "/user/hand/right/input/thumbstick/x"}, - {A_MenuSelect, "/user/hand/right/input/a/click"}, - {A_MenuBack, "/user/hand/right/input/b/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {MWInput::A_GameMenu, "/user/hand/left/input/menu/click"}, - {A_Recenter, "/user/hand/left/input/menu/click"}, - }; - - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); + const char* value = element->Attribute(attribute.c_str()); + if (!value) + throwDocumentError(element, std::string() + "Missing attribute '" + attribute + "'"); + return value; } - void VRInputManager::suggestBindingsHpMixedReality() + void VRInputManager::readInteractionProfile(TiXmlElement* element) { - std::string controllerProfilePath = "/interaction_profiles/hp/mixed_reality_controller"; + std::string interactionProfilePath = requireAttribute(element, "Path"); + mInteractionProfileLocalNames[interactionProfilePath] = requireAttribute(element, "LocalName"); - // In-game character controls - SuggestedBindings gameplayBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, - {MWInput::A_Sneak, "/user/hand/left/input/squeeze/value"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, - {MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, - {MWInput::A_ToggleSpell, "/user/hand/left/input/x/click"}, - {MWInput::A_Rest, "/user/hand/left/input/y/click"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, - {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, - {MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/value"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/value"}, - }; + Log(Debug::Verbose) << "Configuring interaction profile '" << interactionProfilePath << "' (" << mInteractionProfileLocalNames[interactionProfilePath] << ")"; - // GUI controls - SuggestedBindings GUIBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {MWInput::A_GameMenu, "/user/hand/left/input/menu/click"}, - {A_MenuUpDown, "/user/hand/right/input/thumbstick/y"}, - {A_MenuLeftRight, "/user/hand/right/input/thumbstick/x"}, - {A_MenuSelect, "/user/hand/right/input/a/click"}, - {A_MenuBack, "/user/hand/right/input/b/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - }; + // Check extension if present + TiXmlElement* extensionElement = element->FirstChildElement("Extension"); + if (extensionElement) + { + std::string extension = requireAttribute(extensionElement, "Name"); + auto xr = MWVR::Environment::get().getManager(); + if (!xr->xrExtensionIsEnabled(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME)) + { + Log(Debug::Verbose) << " Required extension '" << extension << "' not supported. Skipping interaction profile."; + return; + } + } - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); + TiXmlElement* actionSetGameplay = nullptr; + TiXmlElement* actionSetGUI = nullptr; + TiXmlElement* child = element->FirstChildElement("ActionSet"); + while (child) + { + std::string name = requireAttribute(child, "Name"); + if (name == "Gameplay") + actionSetGameplay = child; + else if (name == "GUI") + actionSetGUI = child; + + child = child->NextSiblingElement("ActionSet"); + } + + if (!actionSetGameplay) + throwDocumentError(element, "Gameplay action set missing"); + if (!actionSetGUI) + throwDocumentError(element, "GUI action set missing"); + + readInteractionProfileActionSet(actionSetGameplay, ActionSet::Gameplay, interactionProfilePath); + readInteractionProfileActionSet(actionSetGUI, ActionSet::GUI, interactionProfilePath); } - void VRInputManager::suggestBindingsHuaweiController() + void VRInputManager::readInteractionProfileActionSet(TiXmlElement* element, ActionSet actionSet, std::string interactionProfilePath) { - std::string controllerProfilePath = "/interaction_profiles/huawei/controller"; + SuggestedBindings suggestedBindings; - // In-game character controls - SuggestedBindings gameplayBindings{ - {A_Recenter, "/user/hand/left/input/home/click"}, - {A_VrMetaMenu, "/user/hand/left/input/home/click"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/click"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/trackpad/y"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/trackpad/x"}, - {MWInput::A_ToggleSpell, "/user/hand/left/input/trackpad/click"}, - {MWInput::A_Sneak, "/user/hand/left/input/back/click"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/trackpad/x"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/trackpad/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/click"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/click"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/click"}, - }; + TiXmlElement* child = element->FirstChildElement("Binding"); + while (child) + { + std::string action = requireAttribute(child, "ActionName"); + std::string path = requireAttribute(child, "Path"); - // GUI controls - SuggestedBindings GUIBindings{ - {A_MenuBack, "/user/hand/left/input/trackpad/click"}, - {MWInput::A_GameMenu, "/user/hand/left/input/home/click"}, - {A_Recenter, "/user/hand/left/input/home/click"}, - {A_MenuUpDown, "/user/hand/right/input/thumbstick/y"}, - {A_MenuLeftRight, "/user/hand/right/input/thumbstick/x"}, - {A_MenuSelect, "/user/hand/right/input/trackpad/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/click"}, - }; + suggestedBindings.push_back( + SuggestedBinding{ + path, action + }); - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); - } + Log(Debug::Debug) << " " << action << ": " << path; - void VRInputManager::suggestBindingsMicrosoftMixedReality() - { - std::string controllerProfilePath = "/interaction_profiles/microsoft/motion_controller"; + child = child->NextSiblingElement("Binding"); + } - // In-game character controls - SuggestedBindings gameplayBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {A_VrMetaMenu, "/user/hand/right/input/squeeze/click"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/trackpad/y"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/trackpad/x"}, - {MWInput::A_Rest, "/user/hand/left/input/thumbstick/click"}, - {MWInput::A_ToggleSpell, "/user/hand/left/input/trackpad/click"}, - {MWInput::A_Sneak, "/user/hand/left/input/squeeze/click"}, - {MWInput::A_Inventory, "/user/hand/right/input/thumbstick/click"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/trackpad/x"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/trackpad/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/click"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/click"}, - }; - - // GUI controls - SuggestedBindings GUIBindings{ - {A_MenuBack, "/user/hand/left/input/trackpad/click"}, - {MWInput::A_GameMenu, "/user/hand/left/input/menu/click"}, - {A_Recenter, "/user/hand/left/input/menu/click"}, - {A_MenuUpDown, "/user/hand/right/input/trackpad/y"}, - {A_MenuLeftRight, "/user/hand/right/input/trackpad/x"}, - {A_MenuSelect, "/user/hand/right/input/trackpad/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - }; - - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); - } - - void VRInputManager::suggestBindingsIndex() - { - std::string controllerProfilePath = "/interaction_profiles/valve/index_controller"; - // In-game character controls - SuggestedBindings gameplayBindings{ - {MWInput::A_ToggleSpell, "/user/hand/left/input/a/click"}, - {MWInput::A_Rest, "/user/hand/left/input/b/click"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, - {A_Recenter, "/user/hand/left/input/trackpad/force"}, - {A_VrMetaMenu, "/user/hand/left/input/trackpad/force"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, - {MWInput::A_Sneak, "/user/hand/left/input/squeeze/force"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, - {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/force"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/force"}, - }; - - // GUI controls - SuggestedBindings GUIBindings{ - {A_Recenter, "/user/hand/left/input/thumbstick/click"}, - {MWInput::A_GameMenu, "/user/hand/left/input/trackpad/force"}, - {A_MenuSelect, "/user/hand/right/input/a/click"}, - {A_MenuBack, "/user/hand/right/input/b/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {A_MenuUpDown, "/user/hand/right/input/thumbstick/y"}, - {A_MenuLeftRight, "/user/hand/right/input/thumbstick/x"}, - }; - - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); - } - - void VRInputManager::suggestBindingsVive() - { - std::string controllerProfilePath = "/interaction_profiles/htc/vive_controller"; - - // In-game character controls - SuggestedBindings gameplayBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, - {A_VrMetaMenu, "/user/hand/right/input/squeeze/click"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/trackpad/y"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/trackpad/x"}, - {MWInput::A_ToggleSpell, "/user/hand/left/input/trackpad/click"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/value"}, - {MWInput::A_Sneak, "/user/hand/left/input/squeeze/click"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/trackpad/x"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/trackpad/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/click"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/click"}, - }; - - // GUI controls - SuggestedBindings GUIBindings{ - {A_MenuUpDown, "/user/hand/right/input/trackpad/y"}, - {A_MenuLeftRight, "/user/hand/right/input/trackpad/x"}, - {A_MenuSelect, "/user/hand/right/input/trackpad/click"}, - {A_MenuBack, "/user/hand/left/input/trackpad/click"}, - {MWInput::A_GameMenu, "/user/hand/left/input/menu/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/value"}, - {A_Recenter, "/user/hand/left/input/menu/click"}, - }; - - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); - } - - void VRInputManager::suggestBindingsViveCosmos() - { - std::string controllerProfilePath = "/interaction_profiles/htc/vive_cosmos_controller"; - - // In-game character controls - SuggestedBindings gameplayBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {A_VrMetaMenu, "/user/hand/left/input/menu/click"}, - {MWInput::A_Sneak, "/user/hand/left/input/squeeze/value"}, - {MWInput::A_MoveForwardBackward,"/user/hand/left/input/thumbstick/y"}, - {MWInput::A_MoveLeftRight, "/user/hand/left/input/thumbstick/x"}, - {MWInput::A_AlwaysRun, "/user/hand/left/input/thumbstick/click"}, - {MWInput::A_Jump, "/user/hand/left/input/trigger/click"}, - {MWInput::A_ToggleSpell, "/user/hand/left/input/x/click"}, - {MWInput::A_Rest, "/user/hand/left/input/y/click"}, - {MWInput::A_ToggleWeapon, "/user/hand/right/input/a/click"}, - {MWInput::A_Inventory, "/user/hand/right/input/b/click"}, - {MWInput::A_LookLeftRight, "/user/hand/right/input/thumbstick/x"}, - {MWInput::A_AutoMove, "/user/hand/right/input/thumbstick/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/click"}, - {A_ActivateTouch, "/user/hand/right/input/squeeze/value"}, - {MWInput::A_Activate, "/user/hand/right/input/squeeze/value"}, - }; - - // GUI controls - SuggestedBindings GUIBindings{ - {A_Recenter, "/user/hand/left/input/menu/click"}, - {MWInput::A_GameMenu, "/user/hand/left/input/menu/click"}, - {A_MenuUpDown, "/user/hand/right/input/thumbstick/y"}, - {A_MenuLeftRight, "/user/hand/right/input/thumbstick/x"}, - {A_MenuSelect, "/user/hand/right/input/a/click"}, - {A_MenuBack, "/user/hand/right/input/b/click"}, - {MWInput::A_Use, "/user/hand/right/input/trigger/click"}, - }; - - mXRInput->suggestBindings(ActionSet::Gameplay, controllerProfilePath, gameplayBindings); - mXRInput->suggestBindings(ActionSet::GUI, controllerProfilePath, GUIBindings); - } - - void VRInputManager::suggestBindingsXboxController() - { - //TODO + mXRInput->suggestBindings(actionSet, interactionProfilePath, suggestedBindings); } void VRInputManager::requestRecenter() @@ -497,7 +286,8 @@ namespace MWVR bool userFileExists, const std::string& userControllerBindingsFile, const std::string& controllerBindingsFile, - bool grab) + bool grab, + const std::string& xrControllerSuggestionsFile) : MWInput::InputManager( window, viewer, @@ -509,23 +299,46 @@ namespace MWVR controllerBindingsFile, grab) , mXRInput(new OpenXRInput) + , mXrControllerSuggestionsFile(xrControllerSuggestionsFile) , mHapticsEnabled{ Settings::Manager::getBool("haptics enabled", "VR") } { - auto xr = MWVR::Environment::get().getManager(); + if (xrControllerSuggestionsFile.empty()) + throw std::runtime_error("No interaction profiles available (xrcontrollersuggestions.xml not found)"); - suggestBindingsSimple(); - suggestBindingsOculusTouch(); - suggestBindingsMicrosoftMixedReality(); - suggestBindingsIndex(); - suggestBindingsVive(); - suggestBindingsXboxController(); + Log(Debug::Verbose) << "Reading Input Profile Path suggestions from " << xrControllerSuggestionsFile; - if (xr->xrExtensionIsEnabled(XR_EXT_HP_MIXED_REALITY_CONTROLLER_EXTENSION_NAME)) - suggestBindingsHpMixedReality(); - if (xr->xrExtensionIsEnabled(XR_HUAWEI_CONTROLLER_INTERACTION_EXTENSION_NAME)) - suggestBindingsHuaweiController(); - if (xr->xrExtensionIsEnabled(XR_HTC_VIVE_COSMOS_CONTROLLER_INTERACTION_EXTENSION_NAME)) - suggestBindingsViveCosmos(); + TiXmlDocument* xmlDoc = nullptr; + TiXmlElement* xmlRoot = nullptr; + + xmlDoc = new TiXmlDocument(xrControllerSuggestionsFile.c_str()); + xmlDoc->LoadFile(); + + if (xmlDoc->Error()) + { + std::ostringstream message; + message << "TinyXml reported an error reading \"" + xrControllerSuggestionsFile + "\". Row " << + (int)xmlDoc->ErrorRow() << ", Col " << (int)xmlDoc->ErrorCol() << ": " << + xmlDoc->ErrorDesc(); + Log(Debug::Error) << message.str(); + throw std::runtime_error(message.str()); + + delete xmlDoc; + return; + } + + xmlRoot = xmlDoc->RootElement(); + if (std::string(xmlRoot->Value()) != "Root") { + Log(Debug::Verbose) << "Error: Invalid xr controllers file. Missing element."; + delete xmlDoc; + return; + } + + TiXmlElement* profile = xmlRoot->FirstChildElement("Profile"); + while (profile) + { + readInteractionProfile(profile); + profile = profile->NextSiblingElement("Profile"); + } mXRInput->attachActionSets(); } diff --git a/apps/openmw/mwvr/vrinputmanager.hpp b/apps/openmw/mwvr/vrinputmanager.hpp index 57d36d25f..25d296e83 100644 --- a/apps/openmw/mwvr/vrinputmanager.hpp +++ b/apps/openmw/mwvr/vrinputmanager.hpp @@ -2,6 +2,7 @@ #define VR_INPUT_MANAGER_HPP #include "vrtypes.hpp" +#include "vrinput.hpp" #include "../mwinput/inputmanagerimp.hpp" @@ -11,6 +12,8 @@ #include "../mwworld/ptr.hpp" +class TiXmlElement; + namespace MWVR { struct OpenXRInput; @@ -31,7 +34,8 @@ namespace MWVR osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, const std::string& userFile, bool userFileExists, const std::string& userControllerBindingsFile, - const std::string& controllerBindingsFile, bool grab); + const std::string& controllerBindingsFile, bool grab, + const std::string& xrControllerSuggestionsFile); virtual ~VRInputManager(); @@ -50,6 +54,9 @@ namespace MWVR /// Currently active action set OpenXRActionSet& activeActionSet(); + /// Notify input manager that the active interaction profile has changed + void notifyInteractionProfileChanged(); + protected: void processAction(const class Action* action, float dt, bool disableControls); @@ -63,21 +70,19 @@ namespace MWVR void applyHapticsRightHand(float intensity) override; void processChangedSettings(const std::set< std::pair >& changed) override; - private: - void suggestBindingsSimple(); - void suggestBindingsOculusTouch(); - void suggestBindingsHpMixedReality(); - void suggestBindingsHuaweiController(); - void suggestBindingsIndex(); - void suggestBindingsMicrosoftMixedReality(); - void suggestBindingsVive(); - void suggestBindingsViveCosmos(); - void suggestBindingsXboxController(); + void throwDocumentError(TiXmlElement* element, std::string error); + std::string requireAttribute(TiXmlElement* element, std::string attribute); + void readInteractionProfile(TiXmlElement* element); + void readInteractionProfileActionSet(TiXmlElement* element, ActionSet actionSet, std::string profilePath); + private: std::unique_ptr mXRInput; std::unique_ptr mRealisticCombat; + std::string mXrControllerSuggestionsFile; bool mActivationIndication{ false }; bool mHapticsEnabled{ true }; + + std::map mInteractionProfileLocalNames; }; } diff --git a/files/xrcontrollersuggestions.xml b/files/xrcontrollersuggestions.xml new file mode 100644 index 000000000..90b03ed54 --- /dev/null +++ b/files/xrcontrollersuggestions.xml @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file