diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index dd324e42e..02b124e79 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -247,7 +247,7 @@ if(BUILD_OPENMW_VR) vrengine.cpp ) add_openmw_dir (mwvr - openxraction openxrinput openxrmanager openxrmanagerimpl openxrswapchain openxrswapchainimpl + openxraction openxractionset openxrinput openxrmanager openxrmanagerimpl openxrswapchain openxrswapchainimpl realisticcombat vranimation vrenvironment vrgui vrinputmanager vrinput vrsession vrframebuffer vrshadow vrtypes vrview vrviewer ) diff --git a/apps/openmw/mwvr/openxractionset.cpp b/apps/openmw/mwvr/openxractionset.cpp new file mode 100644 index 000000000..4fb853c45 --- /dev/null +++ b/apps/openmw/mwvr/openxractionset.cpp @@ -0,0 +1,277 @@ +#include "openxractionset.hpp" + +#include "vrenvironment.hpp" +#include "openxrmanager.hpp" +#include "openxrmanagerimpl.hpp" +#include "openxraction.hpp" + +#include + +#include + +#include + +namespace MWVR +{ + + OpenXRActionSet::OpenXRActionSet(const std::string& actionSetName) + : mActionSet(nullptr) + , mLocalizedName(actionSetName) + , mInternalName(Misc::StringUtils::lowerCase(actionSetName)) + { + mActionSet = createActionSet(actionSetName); + // When starting to account for more devices than oculus touch, this section may need some expansion/redesign. + + // Currently the set of action paths was determined using the oculus touch (i know nothing about the vive and the index). + // The set of action paths may therefore need expansion. E.g. /click vs /value may vary with controllers. + + // To fit more actions onto controllers i created a system of short and long press actions. Allowing one action to activate + // on a short press, and another on long. Here, what actions are short press and what actions are long press is simply + // hardcoded at init, rather than interpreted from bindings. That's bad, and should be fixed, but that's hard to do + // while staying true to openxr's binding system, so if the system i wrote for the oculus touch isn't a good fit for + // the vive/index, we might want to rewrite this to handle bindings ourselves. + generateControllerActionPaths(ActionPath::Select, "/input/select/click"); + generateControllerActionPaths(ActionPath::Squeeze, "/input/squeeze/value"); + generateControllerActionPaths(ActionPath::Pose, "/input/aim/pose"); + generateControllerActionPaths(ActionPath::Haptic, "/output/haptic"); + generateControllerActionPaths(ActionPath::Menu, "/input/menu/click"); + generateControllerActionPaths(ActionPath::ThumbstickX, "/input/thumbstick/x"); + generateControllerActionPaths(ActionPath::ThumbstickY, "/input/thumbstick/y"); + generateControllerActionPaths(ActionPath::ThumbstickClick, "/input/thumbstick/click"); + generateControllerActionPaths(ActionPath::X, "/input/x/click"); + generateControllerActionPaths(ActionPath::Y, "/input/y/click"); + generateControllerActionPaths(ActionPath::A, "/input/a/click"); + generateControllerActionPaths(ActionPath::B, "/input/b/click"); + generateControllerActionPaths(ActionPath::Trigger, "/input/trigger/value"); + + /* + // Applicable actions not (yet) included + A_QuickKey1, + A_QuickKey2, + A_QuickKey3, + A_QuickKey4, + A_QuickKey5, + A_QuickKey6, + A_QuickKey7, + A_QuickKey8, + A_QuickKey9, + A_QuickKey10, + A_QuickKeysMenu, + A_QuickLoad, + A_CycleSpellLeft, + A_CycleSpellRight, + A_CycleWeaponLeft, + A_CycleWeaponRight, + A_Screenshot, // Generate a VR screenshot? + A_Console, // Currently awkward due to a lack of virtual keyboard, but should be included when that's in place + */ + createMWAction(MWInput::A_GameMenu, "game_menu", "Game Menu"); + createMWAction(A_Recenter, "reposition_menu", "Reposition Menu"); + createMWAction(MWInput::A_Inventory, "inventory", "Inventory"); + createMWAction(MWInput::A_Activate, "activate", "Activate"); + createMWAction(MWInput::A_Use, "use", "Use"); + createMWAction(MWInput::A_Jump, "jump", "Jump"); + createMWAction(MWInput::A_ToggleWeapon, "weapon", "Weapon"); + createMWAction(MWInput::A_ToggleSpell, "spell", "Spell"); + createMWAction(MWInput::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left"); + createMWAction(MWInput::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right"); + createMWAction(MWInput::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left"); + createMWAction(MWInput::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right"); + createMWAction(MWInput::A_Sneak, "sneak", "Sneak"); + createMWAction(MWInput::A_QuickMenu, "quick_menu", "Quick Menu"); + createMWAction(MWInput::A_LookLeftRight, "look_left_right", "Look Left Right"); + createMWAction(MWInput::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward"); + createMWAction(MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right"); + createMWAction(MWInput::A_Journal, "journal_book", "Journal Book"); + createMWAction(MWInput::A_QuickSave, "quick_save", "Quick Save"); + createMWAction(MWInput::A_Rest, "rest", "Rest"); + createMWAction(A_ActivateTouch, "activate_touched", "Activate Touch"); + createMWAction(MWInput::A_AlwaysRun, "always_run", "Always Run"); + createMWAction(MWInput::A_AutoMove, "auto_move", "Auto Move"); + createMWAction(MWInput::A_ToggleHUD, "toggle_hud", "Toggle HUD"); + createMWAction(MWInput::A_ToggleDebug, "toggle_debug", "Toggle DEBUG"); + createMWAction(A_MenuUpDown, "menu_up_down", "Menu Up Down"); + createMWAction(A_MenuLeftRight, "menu_left_right", "Menu Left Right"); + createMWAction(A_MenuSelect, "menu_select", "Menu Select"); + createMWAction(A_MenuBack, "menu_back", "Menu Back"); + createPoseAction(TrackedLimb::LEFT_HAND, "left_hand_pose", "Left Hand Pose"); + createPoseAction(TrackedLimb::RIGHT_HAND, "right_hand_pose", "Right Hand Pose"); + createHapticsAction(TrackedLimb::RIGHT_HAND, "right_hand_haptics", "Right Hand Haptics"); + createHapticsAction(TrackedLimb::LEFT_HAND, "left_hand_haptics", "Left Hand Haptics"); + }; + + void + OpenXRActionSet::createPoseAction( + TrackedLimb limb, + const std::string& actionName, + const std::string& localName) + { + mTrackerMap.emplace(limb, new PoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, actionName, localName)))); + } + + void + OpenXRActionSet::createHapticsAction( + TrackedLimb limb, + const std::string& actionName, + const std::string& localName) + { + mHapticsMap.emplace(limb, new HapticsAction(std::move(createXRAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, actionName, localName)))); + } + + template + void + OpenXRActionSet::createMWAction( + int openMWAction, + const std::string& actionName, + const std::string& localName) + { + mActionMap.emplace(openMWAction, new A(openMWAction, std::move(createXRAction(AT, mInternalName + "_" + actionName, mLocalizedName + " " + localName)))); + } + + XrActionSet + OpenXRActionSet::createActionSet(const std::string& name) + { + std::string localized_name = name; + std::string internal_name = Misc::StringUtils::lowerCase(name); + auto* xr = Environment::get().getManager(); + XrActionSet actionSet = XR_NULL_HANDLE; + XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO }; + strcpy_s(createInfo.actionSetName, internal_name.c_str()); + strcpy_s(createInfo.localizedActionSetName, localized_name.c_str()); + createInfo.priority = 0; + CHECK_XRCMD(xrCreateActionSet(xr->impl().xrInstance(), &createInfo, &actionSet)); + return actionSet; + } + + void OpenXRActionSet::suggestBindings(std::vector& xrSuggestedBindings, const SuggestedBindings& mwSuggestedBindings) + { + std::vector suggestedBindings = + { + {*mTrackerMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Pose, Side::LEFT_SIDE)}, + {*mTrackerMap[TrackedLimb::RIGHT_HAND], getXrPath(ActionPath::Pose, Side::RIGHT_SIDE)}, + {*mHapticsMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Haptic, Side::LEFT_SIDE)}, + {*mHapticsMap[TrackedLimb::RIGHT_HAND], getXrPath(ActionPath::Haptic, Side::RIGHT_SIDE)}, + }; + + for (auto& mwSuggestedBinding : mwSuggestedBindings) + { + auto xrAction = mActionMap.find(mwSuggestedBinding.action); + if (xrAction == mActionMap.end()) + { + Log(Debug::Error) << "OpenXRActionSet: Unknown action " << mwSuggestedBinding.action; + continue; + } + suggestedBindings.push_back({ *xrAction->second, getXrPath(mwSuggestedBinding.path, mwSuggestedBinding.side) }); + } + + xrSuggestedBindings.insert(xrSuggestedBindings.end(), suggestedBindings.begin(), suggestedBindings.end()); + } + + void + OpenXRActionSet::generateControllerActionPaths( + ActionPath actionPath, + const std::string& controllerAction) + { + auto* xr = Environment::get().getManager(); + ControllerActionPaths actionPaths; + + std::string left = std::string("/user/hand/left") + controllerAction; + std::string right = std::string("/user/hand/right") + controllerAction; + + CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), left.c_str(), &actionPaths[(int)Side::LEFT_SIDE])); + CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), right.c_str(), &actionPaths[(int)Side::RIGHT_SIDE])); + + mPathMap[actionPath] = actionPaths; + } + + + std::unique_ptr + OpenXRActionSet::createXRAction( + XrActionType actionType, + const std::string& actionName, + const std::string& localName) + { + std::vector subactionPaths; + XrActionCreateInfo createInfo{ XR_TYPE_ACTION_CREATE_INFO }; + createInfo.actionType = actionType; + strcpy_s(createInfo.actionName, actionName.c_str()); + strcpy_s(createInfo.localizedActionName, localName.c_str()); + + XrAction action = XR_NULL_HANDLE; + CHECK_XRCMD(xrCreateAction(mActionSet, &createInfo, &action)); + return std::unique_ptr{new OpenXRAction{ action, actionType, actionName, localName }}; + } + + void + OpenXRActionSet::updateControls() + { + auto* xr = Environment::get().getManager(); + if (!xr->impl().xrSessionRunning()) + return; + + const XrActiveActionSet activeActionSet{ mActionSet, XR_NULL_PATH }; + XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO }; + syncInfo.countActiveActionSets = 1; + syncInfo.activeActionSets = &activeActionSet; + CHECK_XRCMD(xrSyncActions(xr->impl().xrSession(), &syncInfo)); + + for (auto& action : mActionMap) + action.second->updateAndQueue(mActionQueue); + } + + XrPath OpenXRActionSet::generateXrPath(const std::string& path) + { + auto* xr = Environment::get().getManager(); + XrPath xrpath = 0; + CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), path.c_str(), &xrpath)); + return xrpath; + } + + const Action* OpenXRActionSet::nextAction() + { + if (mActionQueue.empty()) + return nullptr; + + const auto* action = mActionQueue.front(); + mActionQueue.pop_front(); + return action; + + } + + Pose + OpenXRActionSet::getLimbPose( + int64_t time, + TrackedLimb limb) + { + auto it = mTrackerMap.find(limb); + if (it == mTrackerMap.end()) + { + Log(Debug::Error) << "OpenXRActionSet: No such tracker: " << limb; + return Pose{}; + } + + it->second->update(time); + return it->second->value(); + } + + void OpenXRActionSet::applyHaptics(TrackedLimb limb, float intensity) + { + auto it = mHapticsMap.find(limb); + if (it == mHapticsMap.end()) + { + Log(Debug::Error) << "OpenXRActionSet: No such tracker: " << limb; + return; + } + + it->second->apply(intensity); + } + XrPath OpenXRActionSet::getXrPath(ActionPath actionPath, Side side) + { + auto it = mPathMap.find(actionPath); + if (it == mPathMap.end()) + { + Log(Debug::Error) << "OpenXRActionSet: No such path: " << (int)actionPath; + } + return it->second[(int)side]; + } +} diff --git a/apps/openmw/mwvr/openxractionset.hpp b/apps/openmw/mwvr/openxractionset.hpp new file mode 100644 index 000000000..bac136e9d --- /dev/null +++ b/apps/openmw/mwvr/openxractionset.hpp @@ -0,0 +1,57 @@ +#ifndef OPENXR_ACTIONSET_HPP +#define OPENXR_ACTIONSET_HPP + +#include "vrinput.hpp" + +#include +#include + +namespace MWVR +{ + /// \brief Generates and manages an OpenXR ActionSet and associated actions. + class OpenXRActionSet + { + public: + using Actions = MWInput::Actions; + using ControllerActionPaths = std::array; + + OpenXRActionSet(const std::string& actionSetName); + + //! Update all controls and queue any actions + void updateControls(); + + //! Get next action from queue (repeat until null is returned) + const Action* nextAction(); + + //! Get current pose of limb in space. + Pose getLimbPose(int64_t time, TrackedLimb limb); + + //! Apply haptics of the given intensity to the given limb + void applyHaptics(TrackedLimb limb, float intensity); + + XrActionSet xrActionSet() { return mActionSet; }; + void suggestBindings(std::vector& xrSuggestedBindings, const SuggestedBindings& mwSuggestedBindings); + + protected: + template + void createMWAction(int openMWAction, const std::string& actionName, const std::string& localName); + void createPoseAction(TrackedLimb limb, const std::string& actionName, const std::string& localName); + void createHapticsAction(TrackedLimb limb, const std::string& actionName, const std::string& localName); + std::unique_ptr createXRAction(XrActionType actionType, const std::string& actionName, const std::string& localName); + XrPath generateXrPath(const std::string& path); + void generateControllerActionPaths(ActionPath actionPath, const std::string& controllerAction); + XrActionSet createActionSet(const std::string& name); + XrPath getXrPath(ActionPath actionPath, Side side); + + XrActionSet mActionSet{ nullptr }; + std::string mLocalizedName{}; + std::string mInternalName{}; + std::map mPathMap; + std::map> mActionMap; + std::map> mTrackerMap; + std::map> mHapticsMap; + std::deque mActionQueue{}; + }; +} + +#endif diff --git a/apps/openmw/mwvr/openxrinput.cpp b/apps/openmw/mwvr/openxrinput.cpp index 9107a0f24..e725c8f36 100644 --- a/apps/openmw/mwvr/openxrinput.cpp +++ b/apps/openmw/mwvr/openxrinput.cpp @@ -7,293 +7,59 @@ #include +#include + #include namespace MWVR { - OpenXRInput::OpenXRInput(const std::vector& suggestedBindings) - : mActionSet(createActionSet()) + OpenXRInput::OpenXRInput() { - // When starting to account for more devices than oculus touch, this section may need some expansion/redesign. - - // Currently the set of action paths was determined using the oculus touch (i know nothing about the vive and the index). - // The set of action paths may therefore need expansion. E.g. /click vs /value may vary with controllers. - - // To fit more actions onto controllers i created a system of short and long press actions. Allowing one action to activate - // on a short press, and another on long. Here, what actions are short press and what actions are long press is simply - // hardcoded at init, rather than interpreted from bindings. That's bad, and should be fixed, but that's hard to do - // while staying true to openxr's binding system, so if the system i wrote for the oculus touch isn't a good fit for - // the vive/index, we might want to rewrite this to handle bindings ourselves. - generateControllerActionPaths(ActionPath::Select, "/input/select/click"); - generateControllerActionPaths(ActionPath::Squeeze, "/input/squeeze/value"); - generateControllerActionPaths(ActionPath::Pose, "/input/aim/pose"); - generateControllerActionPaths(ActionPath::Haptic, "/output/haptic"); - generateControllerActionPaths(ActionPath::Menu, "/input/menu/click"); - generateControllerActionPaths(ActionPath::ThumbstickX, "/input/thumbstick/x"); - generateControllerActionPaths(ActionPath::ThumbstickY, "/input/thumbstick/y"); - generateControllerActionPaths(ActionPath::ThumbstickClick, "/input/thumbstick/click"); - generateControllerActionPaths(ActionPath::X, "/input/x/click"); - generateControllerActionPaths(ActionPath::Y, "/input/y/click"); - generateControllerActionPaths(ActionPath::A, "/input/a/click"); - generateControllerActionPaths(ActionPath::B, "/input/b/click"); - generateControllerActionPaths(ActionPath::Trigger, "/input/trigger/value"); - - /* - // Applicable actions not (yet) included - A_QuickKey1, - A_QuickKey2, - A_QuickKey3, - A_QuickKey4, - A_QuickKey5, - A_QuickKey6, - A_QuickKey7, - A_QuickKey8, - A_QuickKey9, - A_QuickKey10, - A_QuickKeysMenu, - A_QuickLoad, - A_CycleSpellLeft, - A_CycleSpellRight, - A_CycleWeaponLeft, - A_CycleWeaponRight, - A_Screenshot, // Generate a VR screenshot? - A_Console, // Currently awkward due to a lack of virtual keyboard, but should be included when that's in place - */ - createMWAction(MWInput::A_GameMenu, "game_menu", "Game Menu"); - createMWAction(A_Recenter, "reposition_menu", "Reposition Menu"); - createMWAction(MWInput::A_Inventory, "inventory", "Inventory"); - createMWAction(MWInput::A_Activate, "activate", "Activate"); - createMWAction(MWInput::A_Use, "use", "Use"); - createMWAction(MWInput::A_Jump, "jump", "Jump"); - createMWAction(MWInput::A_ToggleWeapon, "weapon", "Weapon"); - createMWAction(MWInput::A_ToggleSpell, "spell", "Spell"); - createMWAction(MWInput::A_CycleSpellLeft, "cycle_spell_left", "Cycle Spell Left"); - createMWAction(MWInput::A_CycleSpellRight, "cycle_spell_right", "Cycle Spell Right"); - createMWAction(MWInput::A_CycleWeaponLeft, "cycle_weapon_left", "Cycle Weapon Left"); - createMWAction(MWInput::A_CycleWeaponRight, "cycle_weapon_right", "Cycle Weapon Right"); - createMWAction(MWInput::A_Sneak, "sneak", "Sneak"); - createMWAction(MWInput::A_QuickMenu, "quick_menu", "Quick Menu"); - createMWAction(MWInput::A_LookLeftRight, "look_left_right", "Look Left Right"); - createMWAction(MWInput::A_MoveForwardBackward, "move_forward_backward", "Move Forward Backward"); - createMWAction(MWInput::A_MoveLeftRight, "move_left_right", "Move Left Right"); - createMWAction(MWInput::A_Journal, "journal_book", "Journal Book"); - createMWAction(MWInput::A_QuickSave, "quick_save", "Quick Save"); - createMWAction(MWInput::A_Rest, "rest", "Rest"); - createMWAction(A_ActivateTouch, "activate_touched", "Activate Touch"); - createMWAction(MWInput::A_AlwaysRun, "always_run", "Always Run"); - createMWAction(MWInput::A_AutoMove, "auto_move", "Auto Move"); - createMWAction(MWInput::A_ToggleHUD, "toggle_hud", "Toggle HUD"); - createMWAction(MWInput::A_ToggleDebug, "toggle_debug", "Toggle DEBUG"); - createMWAction(A_MenuUpDown, "menu_up_down", "Menu Up Down"); - createMWAction(A_MenuLeftRight, "menu_left_right", "Menu Left Right"); - createMWAction(A_MenuSelect, "menu_select", "Menu Select"); - createMWAction(A_MenuBack, "menu_back", "Menu Back"); - createPoseAction(TrackedLimb::LEFT_HAND, "left_hand_pose", "Left Hand Pose"); - createPoseAction(TrackedLimb::RIGHT_HAND, "right_hand_pose", "Right Hand Pose"); - createHapticsAction(TrackedLimb::RIGHT_HAND, "right_hand_haptics", "Right Hand Haptics"); - createHapticsAction(TrackedLimb::LEFT_HAND, "left_hand_haptics", "Left Hand Haptics"); - - - for (auto& sb : suggestedBindings) - suggestBindings(sb); - - auto* xr = Environment::get().getManager(); - { // Set up the action set - XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO }; - attachInfo.countActionSets = 1; - attachInfo.actionSets = &mActionSet; - CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().xrSession(), &attachInfo)); - } + mActionSets.emplace(ActionSet::Gameplay, "Gameplay"); + mActionSets.emplace(ActionSet::GUI, "GUI"); }; - void - OpenXRInput::createPoseAction( - TrackedLimb limb, - const std::string& actionName, - const std::string& localName) + OpenXRActionSet& OpenXRInput::getActionSet(ActionSet actionSet) { - mTrackerMap.emplace(limb, new PoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, actionName, localName)))); + auto it = mActionSets.find(actionSet); + if (it == mActionSets.end()) + throw std::logic_error("No such action set"); + return it->second; } - void - OpenXRInput::createHapticsAction( - TrackedLimb limb, - const std::string& actionName, - const std::string& localName) + void OpenXRInput::suggestBindings(ActionSet actionSet, std::string profile, const SuggestedBindings& mwSuggestedBindings) { - mHapticsMap.emplace(limb, new HapticsAction(std::move(createXRAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, actionName, localName)))); + getActionSet(actionSet).suggestBindings(mSuggestedBindings[profile], mwSuggestedBindings); } - template - void - OpenXRInput::createMWAction( - int openMWAction, - const std::string& actionName, - const std::string& localName) - { - mActionMap.emplace(openMWAction, new A(openMWAction, std::move(createXRAction(AT, actionName, localName)))); - } - - XrActionSet - OpenXRInput::createActionSet() + void OpenXRInput::attachActionSets() { auto* xr = Environment::get().getManager(); - XrActionSet actionSet = XR_NULL_HANDLE; - XrActionSetCreateInfo createInfo{ XR_TYPE_ACTION_SET_CREATE_INFO }; - strcpy_s(createInfo.actionSetName, "gameplay"); - strcpy_s(createInfo.localizedActionSetName, "Gameplay"); - createInfo.priority = 0; - CHECK_XRCMD(xrCreateActionSet(xr->impl().xrInstance(), &createInfo, &actionSet)); - return actionSet; - } - void OpenXRInput::suggestBindings(const SuggestedBindings& mwSuggestedBindings) - { - auto* xr = Environment::get().getManager(); - XrPath oculusTouchInteractionProfilePath; - CHECK_XRCMD( - xrStringToPath(xr->impl().xrInstance(), mwSuggestedBindings.controllerPath.c_str(), &oculusTouchInteractionProfilePath)); - - std::vector suggestedBindings = + // Suggest bindings before attaching + for (auto& profile : mSuggestedBindings) { - {*mTrackerMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Pose, Side::LEFT_SIDE)}, - {*mTrackerMap[TrackedLimb::RIGHT_HAND], getXrPath(ActionPath::Pose, Side::RIGHT_SIDE)}, - {*mHapticsMap[TrackedLimb::LEFT_HAND], getXrPath(ActionPath::Haptic, Side::LEFT_SIDE)}, - {*mHapticsMap[TrackedLimb::RIGHT_HAND], getXrPath(ActionPath::Haptic, Side::RIGHT_SIDE)}, - }; - - for (auto& mwSuggestedBinding : mwSuggestedBindings.bindings) - { - auto xrAction = mActionMap.find(mwSuggestedBinding.action); - if (xrAction == mActionMap.end()) - { - Log(Debug::Error) << "OpenXRInput: Unknown action " << mwSuggestedBinding.action; - continue; - } - suggestedBindings.push_back({ *xrAction->second, getXrPath(mwSuggestedBinding.path, mwSuggestedBinding.side) }); + XrPath profilePath; + CHECK_XRCMD( + xrStringToPath(xr->impl().xrInstance(), profile.first.c_str(), &profilePath)); + XrInteractionProfileSuggestedBinding xrProfileSuggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING }; + xrProfileSuggestedBindings.interactionProfile = profilePath; + xrProfileSuggestedBindings.suggestedBindings = profile.second.data(); + xrProfileSuggestedBindings.countSuggestedBindings = (uint32_t)profile.second.size(); + CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrProfileSuggestedBindings)); } - XrInteractionProfileSuggestedBinding xrSuggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING }; - xrSuggestedBindings.interactionProfile = oculusTouchInteractionProfilePath; - xrSuggestedBindings.suggestedBindings = suggestedBindings.data(); - xrSuggestedBindings.countSuggestedBindings = (uint32_t)suggestedBindings.size(); - CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().xrInstance(), &xrSuggestedBindings)); - } + // OpenXR requires that xrAttachSessionActionSets be called at most once per session. + // So collect all action sets + std::vector actionSets; + for (auto& actionSet : mActionSets) + actionSets.push_back(actionSet.second.xrActionSet()); - void - OpenXRInput::generateControllerActionPaths( - ActionPath actionPath, - const std::string& controllerAction) - { - auto* xr = Environment::get().getManager(); - ControllerActionPaths actionPaths; - - std::string left = std::string("/user/hand/left") + controllerAction; - std::string right = std::string("/user/hand/right") + controllerAction; - - CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), left.c_str(), &actionPaths[(int)Side::LEFT_SIDE])); - CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), right.c_str(), &actionPaths[(int)Side::RIGHT_SIDE])); - - mPathMap[actionPath] = actionPaths; - } - - - std::unique_ptr - OpenXRInput::createXRAction( - XrActionType actionType, - const std::string& actionName, - const std::string& localName) - { - std::vector subactionPaths; - XrActionCreateInfo createInfo{ XR_TYPE_ACTION_CREATE_INFO }; - createInfo.actionType = actionType; - strcpy_s(createInfo.actionName, actionName.c_str()); - strcpy_s(createInfo.localizedActionName, localName.c_str()); - - XrAction action = XR_NULL_HANDLE; - CHECK_XRCMD(xrCreateAction(mActionSet, &createInfo, &action)); - return std::unique_ptr{new OpenXRAction{ action, actionType, actionName, localName }}; - } - - void - OpenXRInput::updateControls() - { - auto* xr = Environment::get().getManager(); - if (!xr->impl().xrSessionRunning()) - return; - - - const XrActiveActionSet activeActionSet{ mActionSet, XR_NULL_PATH }; - XrActionsSyncInfo syncInfo{ XR_TYPE_ACTIONS_SYNC_INFO }; - syncInfo.countActiveActionSets = 1; - syncInfo.activeActionSets = &activeActionSet; - CHECK_XRCMD(xrSyncActions(xr->impl().xrSession(), &syncInfo)); - - - // Note on update order: - // Actions are queued FIFO. - // For most actions this does not matter. - // However mMenuBack may end GuiMode. If it shares a key with a non-gui-mode action - // and were processed before that action, they would both be activated which is - // clearly not desired. - for (auto& action : mActionMap) - action.second->updateAndQueue(mActionQueue); - } - - XrPath OpenXRInput::generateXrPath(const std::string& path) - { - auto* xr = Environment::get().getManager(); - XrPath xrpath = 0; - CHECK_XRCMD(xrStringToPath(xr->impl().xrInstance(), path.c_str(), &xrpath)); - return xrpath; - } - - const Action* OpenXRInput::nextAction() - { - if (mActionQueue.empty()) - return nullptr; - - const auto* action = mActionQueue.front(); - mActionQueue.pop_front(); - return action; - - } - - Pose - OpenXRInput::getLimbPose( - int64_t time, - TrackedLimb limb) - { - auto it = mTrackerMap.find(limb); - if (it == mTrackerMap.end()) - { - Log(Debug::Error) << "OpenXRInput: No such tracker: " << limb; - return Pose{}; - } - - it->second->update(time); - return it->second->value(); - } - - void OpenXRInput::applyHaptics(TrackedLimb limb, float intensity) - { - auto it = mHapticsMap.find(limb); - if (it == mHapticsMap.end()) - { - Log(Debug::Error) << "OpenXRInput: No such tracker: " << limb; - return; - } - - it->second->apply(intensity); - } - XrPath OpenXRInput::getXrPath(ActionPath actionPath, Side side) - { - auto it = mPathMap.find(actionPath); - if (it == mPathMap.end()) - { - Log(Debug::Error) << "OpenXRInput: No such path: " << (int)actionPath; - } - return it->second[(int)side]; + // Attach + XrSessionActionSetsAttachInfo attachInfo{ XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO }; + attachInfo.countActionSets = actionSets.size(); + attachInfo.actionSets = actionSets.data(); + CHECK_XRCMD(xrAttachSessionActionSets(xr->impl().xrSession(), &attachInfo)); } } diff --git a/apps/openmw/mwvr/openxrinput.hpp b/apps/openmw/mwvr/openxrinput.hpp index f8fe9ded3..14fb1f2ea 100644 --- a/apps/openmw/mwvr/openxrinput.hpp +++ b/apps/openmw/mwvr/openxrinput.hpp @@ -2,6 +2,7 @@ #define OPENXR_INPUT_HPP #include "vrinput.hpp" +#include "openxractionset.hpp" #include #include @@ -12,43 +13,25 @@ namespace MWVR class OpenXRInput { public: - using Actions = MWInput::Actions; - using ControllerActionPaths = std::array; + using XrSuggestedBindings = std::vector; + using XrProfileSuggestedBindings = std::map; - OpenXRInput(const std::vector& suggestedBindings); + //! Default constructor, creates two ActionSets: Gameplay and GUI + OpenXRInput(); - //! Update all controls and queue any actions - void updateControls(); + //! Get the specified actionSet. + OpenXRActionSet& getActionSet(ActionSet actionSet); - //! Get next action from queue (repeat until null is returned) - const Action* nextAction(); + //! Suggest bindings for the specific actionSet and profile pair. Call things after calling attachActionSets is an error. + void suggestBindings(ActionSet actionSet, std::string profile, const SuggestedBindings& mwSuggestedBindings); - //! Get current pose of limb in space. - Pose getLimbPose(int64_t time, TrackedLimb limb); - - //! Apply haptics of the given intensity to the given limb - void applyHaptics(TrackedLimb limb, float intensity); + //! Set bindings and attach actionSets to the session. + void attachActionSets(); protected: - template - void createMWAction(int openMWAction, const std::string& actionName, const std::string& localName); - void createPoseAction(TrackedLimb limb, const std::string& actionName, const std::string& localName); - void createHapticsAction(TrackedLimb limb, const std::string& actionName, const std::string& localName); - std::unique_ptr createXRAction(XrActionType actionType, const std::string& actionName, const std::string& localName); - XrPath generateXrPath(const std::string& path); - void generateControllerActionPaths(ActionPath actionPath, const std::string& controllerAction); - XrActionSet createActionSet(void); - void suggestBindings(const SuggestedBindings& suggestedBindings); - XrPath getXrPath(ActionPath actionPath, Side side); - - XrActionSet mActionSet{ nullptr }; - - std::map mPathMap; - std::map> mActionMap; - std::map> mTrackerMap; - std::map> mHapticsMap; - - std::deque mActionQueue{}; + std::map mActionSets{}; + XrProfileSuggestedBindings mSuggestedBindings{}; + bool mAttached = false; }; } diff --git a/apps/openmw/mwvr/vrinput.hpp b/apps/openmw/mwvr/vrinput.hpp index 53f3c1f80..da6faf1e5 100644 --- a/apps/openmw/mwvr/vrinput.hpp +++ b/apps/openmw/mwvr/vrinput.hpp @@ -50,17 +50,20 @@ namespace MWVR }; /// \brief Suggest a binding by binding an action to a path on a given hand (left or right). - struct SuggestedBindings + struct SuggestedBinding { - struct Binding - { - int action; - ActionPath path; - Side side; - }; + int action; + ActionPath path; + Side side; + }; - std::string controllerPath; - std::vector bindings; + using SuggestedBindings = std::vector; + + /// \brief Enumeration of action sets + enum class ActionSet + { + GUI = 0, + Gameplay = 1, }; /// \brief Action for applying haptics diff --git a/apps/openmw/mwvr/vrinputmanager.cpp b/apps/openmw/mwvr/vrinputmanager.cpp index 84d506983..0e97e0b55 100644 --- a/apps/openmw/mwvr/vrinputmanager.cpp +++ b/apps/openmw/mwvr/vrinputmanager.cpp @@ -42,7 +42,18 @@ namespace MWVR Pose VRInputManager::getLimbPose(int64_t time, TrackedLimb limb) { - return mXRInput->getLimbPose(time, limb); + return activeActionSet().getLimbPose(time, limb); + } + + OpenXRActionSet& VRInputManager::activeActionSet() + { + bool guiMode = MWBase::Environment::get().getWindowManager()->isGuiMode(); + guiMode = guiMode || (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame); + if (guiMode) + { + return mXRInput->getActionSet(ActionSet::GUI); + } + return mXRInput->getActionSet(ActionSet::Gameplay); } void VRInputManager::updateActivationIndication(void) @@ -51,7 +62,9 @@ namespace MWVR bool show = guiMode | mActivationIndication; auto* playerAnimation = Environment::get().getPlayerAnimation(); if (playerAnimation) + { playerAnimation->setFingerPointingMode(show); + } } @@ -145,13 +158,13 @@ namespace MWVR void VRInputManager::applyHapticsLeftHand(float intensity) { if (mHapticsEnabled) - mXRInput->applyHaptics(TrackedLimb::LEFT_HAND, intensity); + activeActionSet().applyHaptics(TrackedLimb::LEFT_HAND, intensity); } void VRInputManager::applyHapticsRightHand(float intensity) { if (mHapticsEnabled) - mXRInput->applyHaptics(TrackedLimb::RIGHT_HAND, intensity); + activeActionSet().applyHaptics(TrackedLimb::RIGHT_HAND, intensity); } void VRInputManager::requestRecenter() @@ -179,10 +192,10 @@ namespace MWVR userControllerBindingsFile, controllerBindingsFile, grab) - , mXRInput(nullptr) + , mXRInput(new OpenXRInput) , mHapticsEnabled{Settings::Manager::getBool("haptics enabled", "VR")} { - std::vector suggestedBindings; + std::string oculusTouchProfilePath = "/interaction_profiles/oculus/touch_controller"; // Set up default bindings for the oculus /* @@ -236,43 +249,52 @@ namespace MWVR Long: Recenter on player and reset GUI */ - SuggestedBindings oculusTouchBindings{ - "/interaction_profiles/oculus/touch_controller", - { - {A_MenuUpDown, ActionPath::ThumbstickY, Side::RIGHT_SIDE}, - {A_MenuLeftRight, ActionPath::ThumbstickX, Side::RIGHT_SIDE}, - {A_MenuSelect, ActionPath::A, Side::RIGHT_SIDE}, - {A_MenuBack, ActionPath::B, Side::RIGHT_SIDE}, - {MWInput::A_LookLeftRight, ActionPath::ThumbstickX, Side::RIGHT_SIDE}, - {MWInput::A_MoveLeftRight, ActionPath::ThumbstickX, Side::LEFT_SIDE}, - {MWInput::A_MoveForwardBackward, ActionPath::ThumbstickY, Side::LEFT_SIDE}, - {MWInput::A_Activate, ActionPath::Squeeze, Side::RIGHT_SIDE}, - {MWInput::A_Use, ActionPath::Trigger, Side::RIGHT_SIDE}, - {MWInput::A_Jump, ActionPath::Trigger, Side::LEFT_SIDE}, - {MWInput::A_ToggleWeapon, ActionPath::A, Side::RIGHT_SIDE}, - {MWInput::A_ToggleSpell, ActionPath::X, Side::LEFT_SIDE}, - //{*mCycleSpellLeft, mThumbstickClickPath, Side::LEFT_SIDE}, - //{*mCycleSpellRight, mThumbstickClickPath, Side::RIGHT_SIDE}, - //{*mCycleWeaponLeft, mThumbstickClickPath, Side::LEFT_SIDE}, - //{*mCycleWeaponRight, mThumbstickClickPath, Side::RIGHT_SIDE}, - {MWInput::A_AlwaysRun, ActionPath::ThumbstickClick, Side::LEFT_SIDE}, - {MWInput::A_AutoMove, ActionPath::ThumbstickClick, Side::RIGHT_SIDE}, - {MWInput::A_ToggleHUD, ActionPath::ThumbstickClick, Side::LEFT_SIDE}, - {MWInput::A_ToggleDebug, ActionPath::ThumbstickClick, Side::RIGHT_SIDE}, - {MWInput::A_Sneak, ActionPath::Squeeze, Side::LEFT_SIDE}, - {MWInput::A_Inventory, ActionPath::B, Side::RIGHT_SIDE}, - {MWInput::A_Rest, ActionPath::Y, Side::LEFT_SIDE}, - {MWInput::A_Journal, ActionPath::B, Side::RIGHT_SIDE}, - {MWInput::A_QuickSave, ActionPath::Y, Side::LEFT_SIDE}, - {MWInput::A_GameMenu, ActionPath::Menu, Side::LEFT_SIDE}, - {A_Recenter, ActionPath::Menu, Side::LEFT_SIDE}, - {A_ActivateTouch, ActionPath::Squeeze, Side::RIGHT_SIDE}, - } - }; + { + // In-game character controls + SuggestedBindings oculusTouchGameplayBindings{ + {MWInput::A_LookLeftRight, ActionPath::ThumbstickX, Side::RIGHT_SIDE}, + {MWInput::A_MoveLeftRight, ActionPath::ThumbstickX, Side::LEFT_SIDE}, + {MWInput::A_MoveForwardBackward, ActionPath::ThumbstickY, Side::LEFT_SIDE}, + {MWInput::A_Activate, ActionPath::Squeeze, Side::RIGHT_SIDE}, + {MWInput::A_Use, ActionPath::Trigger, Side::RIGHT_SIDE}, + {MWInput::A_Jump, ActionPath::Trigger, Side::LEFT_SIDE}, + {MWInput::A_ToggleWeapon, ActionPath::A, Side::RIGHT_SIDE}, + {MWInput::A_ToggleSpell, ActionPath::X, Side::LEFT_SIDE}, + //{*mCycleSpellLeft, mThumbstickClickPath, Side::LEFT_SIDE}, + //{*mCycleSpellRight, mThumbstickClickPath, Side::RIGHT_SIDE}, + //{*mCycleWeaponLeft, mThumbstickClickPath, Side::LEFT_SIDE}, + //{*mCycleWeaponRight, mThumbstickClickPath, Side::RIGHT_SIDE}, + {MWInput::A_AlwaysRun, ActionPath::ThumbstickClick, Side::LEFT_SIDE}, + {MWInput::A_AutoMove, ActionPath::ThumbstickClick, Side::RIGHT_SIDE}, + {MWInput::A_ToggleHUD, ActionPath::ThumbstickClick, Side::LEFT_SIDE}, + {MWInput::A_ToggleDebug, ActionPath::ThumbstickClick, Side::RIGHT_SIDE}, + {MWInput::A_Sneak, ActionPath::Squeeze, Side::LEFT_SIDE}, + {MWInput::A_Inventory, ActionPath::B, Side::RIGHT_SIDE}, + {MWInput::A_Rest, ActionPath::Y, Side::LEFT_SIDE}, + {MWInput::A_Journal, ActionPath::B, Side::RIGHT_SIDE}, + {MWInput::A_QuickSave, ActionPath::Y, Side::LEFT_SIDE}, + {MWInput::A_GameMenu, ActionPath::Menu, Side::LEFT_SIDE}, + {A_Recenter, ActionPath::Menu, Side::LEFT_SIDE}, + {A_ActivateTouch, ActionPath::Squeeze, Side::RIGHT_SIDE}, + }; - suggestedBindings.push_back(oculusTouchBindings); + mXRInput->suggestBindings(ActionSet::Gameplay, oculusTouchProfilePath, oculusTouchGameplayBindings); - mXRInput.reset(new OpenXRInput(suggestedBindings)); + // GUI controls + SuggestedBindings oculusTouchGUIBindings{ + {A_MenuUpDown, ActionPath::ThumbstickY, Side::RIGHT_SIDE}, + {A_MenuLeftRight, ActionPath::ThumbstickX, Side::RIGHT_SIDE}, + {A_MenuSelect, ActionPath::A, Side::RIGHT_SIDE}, + {A_MenuBack, ActionPath::B, Side::RIGHT_SIDE}, + {MWInput::A_GameMenu, ActionPath::Menu, Side::LEFT_SIDE}, + {MWInput::A_Use, ActionPath::Trigger, Side::RIGHT_SIDE}, + {A_Recenter, ActionPath::Menu, Side::LEFT_SIDE}, + }; + + mXRInput->suggestBindings(ActionSet::GUI, oculusTouchProfilePath, oculusTouchGUIBindings); + } + + mXRInput->attachActionSets(); } VRInputManager::~VRInputManager() @@ -294,8 +316,8 @@ namespace MWVR bool disableEvents) { auto begin = std::chrono::steady_clock::now(); - mXRInput->updateControls(); - + auto& actionSet = activeActionSet(); + actionSet.updateControls(); auto* vrGuiManager = Environment::get().getGUIManager(); if (vrGuiManager) @@ -308,7 +330,7 @@ namespace MWVR } } - while (auto* action = mXRInput->nextAction()) + while (auto* action = actionSet.nextAction()) { processAction(action, dt, disableControls); } @@ -316,7 +338,6 @@ namespace MWVR updateActivationIndication(); MWInput::InputManager::update(dt, disableControls, disableEvents); - // This is the first update that needs openxr tracking, so i begin the next frame here. auto* session = Environment::get().getSession(); if (session) @@ -514,8 +535,9 @@ namespace MWVR mActionManager->screenshot(); break; case MWInput::A_Inventory: - //mActionManager->toggleInventory(); - injectMousePress(SDL_BUTTON_RIGHT, true); + mActionManager->toggleInventory(); + //injectMousePress(SDL_BUTTON_RIGHT, true); + //mBindingsManager->ics().getChannel(MWInput::A_Inventory)->setValue(0.f); break; case MWInput::A_Console: mActionManager->toggleConsole(); @@ -632,7 +654,8 @@ namespace MWVR mActionManager->toggleSneaking(); break; case MWInput::A_Inventory: - injectMousePress(SDL_BUTTON_RIGHT, false); + //injectMousePress(SDL_BUTTON_RIGHT, false); + //mBindingsManager->ics().getChannel(MWInput::A_Inventory)->setValue(0.f); default: break; } diff --git a/apps/openmw/mwvr/vrinputmanager.hpp b/apps/openmw/mwvr/vrinputmanager.hpp index 944e36046..8beb64d6b 100644 --- a/apps/openmw/mwvr/vrinputmanager.hpp +++ b/apps/openmw/mwvr/vrinputmanager.hpp @@ -14,6 +14,7 @@ namespace MWVR { struct OpenXRInput; + struct OpenXRActionSet; namespace RealisticCombat { class StateMachine; @@ -55,6 +56,9 @@ namespace MWVR /// Tracking pose of the given limb at the given predicted time Pose getLimbPose(int64_t time, TrackedLimb limb); + /// Currently active action set + OpenXRActionSet& activeActionSet(); + protected: void updateHead(); diff --git a/apps/openmw/mwvr/vrviewer.cpp b/apps/openmw/mwvr/vrviewer.cpp index bfd565255..e42cf7527 100644 --- a/apps/openmw/mwvr/vrviewer.cpp +++ b/apps/openmw/mwvr/vrviewer.cpp @@ -74,6 +74,7 @@ namespace MWVR { return max; } + return recommended; } void VRViewer::realize(osg::GraphicsContext* context)