1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-07-21 18:14:04 +00:00

Rewrote action system to be more flexible. Makes writing rebindable actions easier, and lets me disambiguate press vs long-press actions on the same key.

This commit is contained in:
Mads Buvik Sandvei 2020-05-15 00:53:49 +02:00
parent 6f9c405afd
commit cad6468518
2 changed files with 438 additions and 431 deletions

View file

@ -49,16 +49,16 @@
namespace MWVR
{
struct OpenXRActionEvent
{
int action;
bool onPress;
float value = 0.f;
};
// TODO: Make part of settings (is there already a setting like this?)
//! Delay before a long-press action is activated
static std::chrono::milliseconds gActionTime{ 1000 };
//! Magnitude above which an axis action is considered active
static float gAxisEpsilon{ 0.01f };
struct OpenXRAction
{
public:
// I want these to manage XrAction objects but i also didn't want to wrap these in unique pointers (useless dynamic allocation).
// So i implemented move for them instead.
OpenXRAction() = delete;
@ -71,9 +71,10 @@ public:
OpenXRAction(XrAction action, XrActionType actionType, const std::string& actionName, const std::string& localName);
~OpenXRAction();
//! Convenience / syntactic sugar
operator XrAction() { return mAction; }
// Note that although these take a subaction path, we never use it. We have no subaction-ambiguous actions.
bool getFloat(XrPath subactionPath, float& value);
bool getBool(XrPath subactionPath, bool& value);
bool getPose(XrPath subactionPath);
@ -85,68 +86,193 @@ public:
std::string mLocalName;
};
//! Wrapper around bool type input actions, ignoring all subactions
struct OpenXRAction_Bool
//! Generic action
class Action
{
OpenXRAction_Bool(OpenXRAction action);
operator XrAction() { return mAction; }
public:
Action(int openMWAction, OpenXRAction xrAction) : mXRAction(std::move(xrAction)), mOpenMWAction(openMWAction){}
virtual ~Action() {};
void update();
//! True if action changed to being pressed in the last update
bool actionOnPress() { return actionIsPressed() && actionChanged(); }
//! True if action changed to being released in the last update
bool actionOnRelease() { return actionIsReleased() && actionChanged(); };
//! True if the action is currently being pressed
bool actionIsPressed() { return mPressed; }
//! True if the action is currently not being pressed
bool actionIsReleased() { return !mPressed; }
//! True if the action changed state in the last update
bool actionChanged() { return mChanged; }
bool isActive() const { return mActive; };
OpenXRAction mAction;
bool mPressed = false;
bool mChanged = false;
//! True if activation turned on this update (i.e. onPress)
bool onActivate() const { return mOnActivate; }
//! True if activation turned off this update (i.e. onRelease)
bool onDeactivate() const { return mOnDeactivate; }
//! OpenMW Action code of this action
int openMWActionCode() const { return mOpenMWAction; }
//! Current value of an axis or lever action
float value() const { return mValue; }
//! Update internal states. Note that subclasses maintain both mValue and mActivate to allow
//! axis and press to subtitute one another.
virtual void update() = 0;
//! Determines if an action update should be queued
virtual bool shouldQueue() const = 0;
//! Convenience / syntactic sugar
operator XrAction() { return mXRAction; }
//! Update and queue action if applicable
void updateAndQueue(std::deque<const Action*>& queue)
{
bool old = mActive;
update();
bool changed = old != mActive;
mOnActivate = changed && mActive;
mOnDeactivate = changed && !mActive;
//if(openMWActionCode() == MWInput::InputManager::A_Journal)
Log(Debug::Verbose) << "Action[" << mXRAction.mName << "]: old=" << old << " shouldQueue=" << shouldQueue() << ", active=" << mActive << ", value=" << mValue << " onActivate=" << mOnActivate << ", mOnDeactivate=" << mOnDeactivate;
if (shouldQueue())
{
queue.push_back(this);
}
}
protected:
OpenXRAction mXRAction;
int mOpenMWAction;
float mValue{ 0.f };
bool mActive{ false };
bool mOnActivate{ false };
bool mOnDeactivate{ false };
};
//! Wrapper around float type input actions, ignoring all subactions
struct OpenXRAction_Float
//! Convenience
using ActionPtr = std::unique_ptr<Action>;
//! Action that activates once on release.
//! Times out if the button is held longer than gHoldDelay.
class ButtonPressAction : public Action
{
OpenXRAction_Float(OpenXRAction action);
operator XrAction() { return mAction; }
public:
using Action::Action;
void update();
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
//! Current value of the control, from -1.f to 1.f for sticks or 0.f to 1.f for controls
float value() { return mValue; }
void update() override
{
mActive = false;
bool old = mPressed;
mXRAction.getBool(0, mPressed);
bool changed = old != mPressed;
if (changed && mPressed)
{
mPressTime = std::chrono::steady_clock::now();
mTimeout = mPressTime + gActionTime;
}
if (changed && !mPressed)
{
if (std::chrono::steady_clock::now() < mTimeout)
{
mActive = true;
}
}
OpenXRAction mAction;
float mValue = 0.f;
mValue = mPressed ? 1.f : 0.f;
}
virtual bool shouldQueue() const override { return onActivate() || onDeactivate(); }
bool mPressed{ false };
std::chrono::steady_clock::time_point mPressTime{};
std::chrono::steady_clock::time_point mTimeout{};
};
//! Wrapper around float type input actions, converting them to bools above a specified value
struct OpenXRAction_FloatToBool
//! Action that activates once on a long press
//! The press time is the same as the timeout for a regular press, allowing keys with double roles.
class ButtonLongPressAction : public Action
{
OpenXRAction_FloatToBool(OpenXRAction action, float threshold);
operator XrAction() { return mAction; }
public:
using Action::Action;
void update();
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
//! True if action changed to being pressed in the last update
bool actionOnPress() { return actionIsPressed() && actionChanged(); }
//! True if action changed to being released in the last update
bool actionOnRelease() { return actionIsReleased() && actionChanged(); };
//! True if the action is currently being pressed
bool actionIsPressed() { return mPressed; }
//! True if the action is currently not being pressed
bool actionIsReleased() { return !mPressed; }
//! True if the action changed state in the last update
bool actionChanged() { return mChanged; }
void update() override
{
mActive = false;
bool old = mPressed;
mXRAction.getBool(0, mPressed);
bool changed = old != mPressed;
if (changed && mPressed)
{
mPressTime = std::chrono::steady_clock::now();
mTimein = mPressTime + gActionTime;
mActivated = false;
}
if (mPressed && !mActivated)
{
if (std::chrono::steady_clock::now() >= mTimein)
{
mActive = mActivated = true;
mValue = 1.f;
}
}
if (changed && !mPressed)
{
mValue = 0.f;
}
}
OpenXRAction_Float mAction;
float mThreshold;
bool mPressed = false;
bool mChanged = false;
virtual bool shouldQueue() const override { return onActivate() || onDeactivate(); }
bool mPressed{ false };
bool mActivated{ false };
std::chrono::steady_clock::time_point mPressTime{};
std::chrono::steady_clock::time_point mTimein{};
};
//! Action that is active whenever the button is pressed down.
//! Useful for e.g. non-toggling sneak and automatically repeating actions
class ButtonHoldAction : public Action
{
public:
using Action::Action;
static const XrActionType ActionType = XR_ACTION_TYPE_BOOLEAN_INPUT;
void update() override
{
mXRAction.getBool(0, mPressed);
mActive = mPressed;
mValue = mPressed ? 1.f : 0.f;
}
virtual bool shouldQueue() const override { return mActive || onDeactivate(); }
bool mPressed{ false };
};
//! Action for axis actions, such as thumbstick axes or certain triggers/squeeze levers.
//! Float axis are considered active whenever their magnitude is greater than gAxisEpsilon. This is useful
//! as a touch subtitute on levers without touch.
class AxisAction : public Action
{
public:
using Action::Action;
static const XrActionType ActionType = XR_ACTION_TYPE_FLOAT_INPUT;
void update() override
{
mActive = false;
mXRAction.getFloat(0, mValue);
if (std::fabs(mValue) > gAxisEpsilon)
mActive = true;
else
mValue = 0.f;
}
virtual bool shouldQueue() const override { return mActive || onDeactivate(); }
};
struct OpenXRInput
@ -166,7 +292,7 @@ struct OpenXRInput
enum SubAction : signed
{
NONE = -1, //!< Used to ignore subaction or when action has no subaction. Not a valid input to createAction().
NONE = -1, //!< Used to ignore subaction or when action has no subaction. Not a valid input to createMWAction().
// hands should be 0 and 1 as they are the typical use case of needing to index between 2 actions or subactions.
LEFT_HAND = 0, //!< Read action from left-hand controller
RIGHT_HAND = 1, //!< Read action from right-hand controller
@ -188,14 +314,16 @@ struct OpenXRInput
OpenXRInput();
OpenXRAction createAction(XrActionType actionType, const std::string& actionName, const std::string& localName, const std::vector<SubAction>& subActions = {});
template<typename A, XrActionType AT = A::ActionType>
ActionPtr createMWAction(int openMWAction, const std::string& actionName, const std::string& localName, const std::vector<SubAction>& subActions = {});
OpenXRAction createXRAction(XrActionType actionType, const std::string& actionName, const std::string& localName, const std::vector<SubAction>& subActions = {});
XrActionSet createActionSet(void);
XrPath subactionPath(SubAction subAction);
void updateControls();
bool nextActionEvent(OpenXRActionEvent& action);
const Action* nextAction();
PoseSet getHandPoses(int64_t time, TrackedSpace space);
SubActionPaths mSubactionPath;
@ -216,29 +344,29 @@ struct OpenXRInput
ControllerActionPaths mBPath;
ControllerActionPaths mTriggerValuePath;
OpenXRAction_Bool mGameMenu;
OpenXRAction_Bool mInventory;
OpenXRAction_Bool mActivate;
OpenXRAction_Bool mUse;
OpenXRAction_Bool mJump;
OpenXRAction_Bool mWeapon;
OpenXRAction_Bool mSpell;
OpenXRAction_Bool mCycleSpellLeft;
OpenXRAction_Bool mCycleSpellRight;
OpenXRAction_Bool mCycleWeaponLeft;
OpenXRAction_Bool mCycleWeaponRight;
OpenXRAction_Bool mSneak;
OpenXRAction_Bool mQuickMenu;
OpenXRAction_Float mLookLeftRight;
OpenXRAction_Float mMoveForwardBackward;
OpenXRAction_Float mMoveLeftRight;
ActionPtr mGameMenu;
ActionPtr mInventory;
ActionPtr mActivate;
ActionPtr mUse;
ActionPtr mJump;
ActionPtr mToggleWeapon;
ActionPtr mToggleSpell;
ActionPtr mCycleSpellLeft;
ActionPtr mCycleSpellRight;
ActionPtr mCycleWeaponLeft;
ActionPtr mCycleWeaponRight;
ActionPtr mToggleSneak;
ActionPtr mQuickMenu;
ActionPtr mLookLeftRight;
ActionPtr mMoveForwardBackward;
ActionPtr mMoveLeftRight;
ActionPtr mJournal;
//OpenXRAction_Delayed mQuickSave;
// Needed to access all the actions that don't fit on the controllers
OpenXRAction_Bool mActionsMenu;
// Economize buttons by accessing spell actions and weapon actions on the same keys, but with/without this modifier
OpenXRAction_Bool mSpellModifier;
ActionPtr mQuickKeysMenu;
OpenXRAction_FloatToBool mActivateTouch;
ActionPtr mActivateTouch;
// Hand tracking
OpenXRAction mHandPoseAction;
@ -259,7 +387,7 @@ struct OpenXRInput
osg::Quat mHeadWorldOrientation;
std::deque<OpenXRActionEvent> mActionEvents{};
std::deque<const Action*> mActionQueue{};
};
XrActionSet
@ -365,51 +493,6 @@ bool OpenXRAction::applyHaptics(XrPath subactionPath, float amplitude)
return true;
}
OpenXRAction_FloatToBool::OpenXRAction_FloatToBool(
OpenXRAction action, float threshold)
: mAction(std::move(action))
, mThreshold(threshold)
{
}
void OpenXRAction_FloatToBool::update()
{
mAction.update();
bool old = mPressed;
float value = mAction.value();
mPressed = value > mThreshold;
mChanged = mPressed != old;
}
OpenXRAction_Bool::OpenXRAction_Bool(
OpenXRAction action)
: mAction(std::move(action))
{
}
void
OpenXRAction_Bool::update()
{
bool old = mPressed;
mAction.getBool(0, mPressed);
mChanged = mPressed != old;
}
OpenXRAction_Float::OpenXRAction_Float(
OpenXRAction action)
: mAction(std::move(action))
{
}
void
OpenXRAction_Float::update()
{
mAction.getFloat(0, mValue);
}
OpenXRInput::ControllerActionPaths
OpenXRInput::generateControllerActionPaths(
const std::string& controllerAction)
@ -451,27 +534,28 @@ OpenXRInput::OpenXRInput()
, mAPath(generateControllerActionPaths("/input/a/click"))
, mBPath(generateControllerActionPaths("/input/b/click"))
, mTriggerValuePath(generateControllerActionPaths("/input/trigger/value"))
, mGameMenu(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "game_menu", "GameMenu", { })))
, mInventory(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "inventory", "Inventory", { })))
, mActivate(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "activate", "Activate", { })))
, mUse(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "use", "Use", { })))
, mJump(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "jump", "Jump", { })))
, mWeapon(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "weapon", "Weapon", { })))
, mSpell(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "spell", "Spell", { })))
, mCycleSpellLeft(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_spell_left", "CycleSpellLeft", { })))
, mCycleSpellRight(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_spell_right", "CycleSpellRight", { })))
, mCycleWeaponLeft(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_weapon_left", "CycleWeaponLeft", { })))
, mCycleWeaponRight(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "cycle_weapon_right", "CycleWeaponRight", { })))
, mSneak(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "sneak", "Sneak", { })))
, mQuickMenu(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "quick_menu", "QuickMenu", { })))
, mLookLeftRight(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "look_left_right", "LookLeftRight", { })))
, mMoveForwardBackward(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "move_forward_backward", "MoveForwardBackward", { })))
, mMoveLeftRight(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "move_left_right", "MoveLeftRight", { })))
, mActionsMenu(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "actions_menu", "Actions Menu", { })))
, mSpellModifier(std::move(createAction(XR_ACTION_TYPE_BOOLEAN_INPUT, "spell_modifier", "Spell Modifier", { })))
, mActivateTouch(std::move(createAction(XR_ACTION_TYPE_FLOAT_INPUT, "activate_touched", "Activate Touch", { RIGHT_HAND })), 0.01f)
, mHandPoseAction(std::move(createAction(XR_ACTION_TYPE_POSE_INPUT, "hand_pose", "Hand Pose", { LEFT_HAND, RIGHT_HAND })))
, mHapticsAction(std::move(createAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_hand", "Vibrate Hand", { LEFT_HAND, RIGHT_HAND })))
, mGameMenu(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_GameMenu, "game_menu", "GameMenu", { })))
, mInventory(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_Inventory, "inventory", "Inventory", { })))
, mActivate(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_Activate, "activate", "Activate", { })))
, mUse(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_Use, "use", "Use", { })))
, mJump(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_Jump, "jump", "Jump", { })))
, mToggleWeapon(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_ToggleWeapon, "weapon", "Weapon", { })))
, mToggleSpell(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_ToggleSpell, "spell", "Spell", { })))
, mCycleSpellLeft(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_CycleSpellLeft, "cycle_spell_left", "CycleSpellLeft", { })))
, mCycleSpellRight(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_CycleSpellRight, "cycle_spell_right", "CycleSpellRight", { })))
, mCycleWeaponLeft(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_CycleWeaponLeft, "cycle_weapon_left", "CycleWeaponLeft", { })))
, mCycleWeaponRight(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_CycleWeaponRight, "cycle_weapon_right", "CycleWeaponRight", { })))
, mToggleSneak(std::move(createMWAction<ButtonHoldAction>(MWInput::InputManager::A_ToggleSneak, "sneak", "Sneak", { })))
, mQuickMenu(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_QuickMenu, "quick_menu", "QuickMenu", { })))
, mLookLeftRight(std::move(createMWAction<AxisAction>(MWInput::InputManager::A_LookLeftRight, "look_left_right", "LookLeftRight", { })))
, mMoveForwardBackward(std::move(createMWAction<AxisAction>(MWInput::InputManager::A_MoveForwardBackward, "move_forward_backward", "MoveForwardBackward", { })))
, mMoveLeftRight(std::move(createMWAction<AxisAction>(MWInput::InputManager::A_MoveLeftRight, "move_left_right", "MoveLeftRight", { })))
, mJournal(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_Journal, "journal_book", "Journal Book", { })))
//, mQuickSave(std::move(createMWAction<ButtonHoldAction>(MWInput::InputManager::A_QuickSave, "quick_save", "Quick Save", { })))
, mQuickKeysMenu(std::move(createMWAction<ButtonPressAction>(MWInput::InputManager::A_QuickKeysMenu, "quick_keys_menu", "Quick Keys Menu", { })))
, mActivateTouch(std::move(createMWAction<AxisAction>(A_ActivateTouch, "activate_touched", "Activate Touch", { RIGHT_HAND })))
, mHandPoseAction(std::move(createXRAction(XR_ACTION_TYPE_POSE_INPUT, "hand_pose", "Hand Pose", { LEFT_HAND, RIGHT_HAND })))
, mHapticsAction(std::move(createXRAction(XR_ACTION_TYPE_VIBRATION_OUTPUT, "vibrate_hand", "Vibrate Hand", { LEFT_HAND, RIGHT_HAND })))
{
auto* xr = Environment::get().getManager();
{ // Set up default bindings for the oculus
@ -483,24 +567,24 @@ OpenXRInput::OpenXRInput()
{mHandPoseAction, mPosePath[RIGHT_HAND]},
{mHapticsAction, mHapticPath[LEFT_HAND]},
{mHapticsAction, mHapticPath[RIGHT_HAND]},
{mLookLeftRight, mThumbstickXPath[RIGHT_HAND]},
{mMoveLeftRight, mThumbstickXPath[LEFT_HAND]},
{mMoveForwardBackward, mThumbstickYPath[LEFT_HAND]},
{mActivate, mSqueezeValuePath[RIGHT_HAND]},
{mUse, mTriggerValuePath[RIGHT_HAND]},
{mJump, mTriggerValuePath[LEFT_HAND]},
{mWeapon, mAPath[RIGHT_HAND]},
{mSpell, mAPath[RIGHT_HAND]},
{mCycleSpellLeft, mThumbstickClickPath[LEFT_HAND]},
{mCycleSpellRight, mThumbstickClickPath[RIGHT_HAND]},
{mCycleWeaponLeft, mThumbstickClickPath[LEFT_HAND]},
{mCycleWeaponRight, mThumbstickClickPath[RIGHT_HAND]},
{mSneak, mXPath[LEFT_HAND]},
{mInventory, mBPath[RIGHT_HAND]},
{mQuickMenu, mYPath[LEFT_HAND]},
{mSpellModifier, mSqueezeValuePath[LEFT_HAND]},
{mGameMenu, mMenuClickPath[LEFT_HAND]},
{mActivateTouch, mSqueezeValuePath[RIGHT_HAND]},
{*mLookLeftRight, mThumbstickXPath[RIGHT_HAND]},
{*mMoveLeftRight, mThumbstickXPath[LEFT_HAND]},
{*mMoveForwardBackward, mThumbstickYPath[LEFT_HAND]},
{*mActivate, mSqueezeValuePath[RIGHT_HAND]},
{*mUse, mTriggerValuePath[RIGHT_HAND]},
{*mJump, mTriggerValuePath[LEFT_HAND]},
{*mToggleWeapon, mAPath[RIGHT_HAND]},
{*mToggleSpell, mAPath[RIGHT_HAND]},
{*mCycleSpellLeft, mThumbstickClickPath[LEFT_HAND]},
{*mCycleSpellRight, mThumbstickClickPath[RIGHT_HAND]},
{*mCycleWeaponLeft, mThumbstickClickPath[LEFT_HAND]},
{*mCycleWeaponRight, mThumbstickClickPath[RIGHT_HAND]},
{*mToggleSneak, mXPath[LEFT_HAND]},
{*mInventory, mBPath[RIGHT_HAND]},
{*mJournal, mYPath[LEFT_HAND]},
//{*mQuickSave, mYPath[LEFT_HAND]},
{*mGameMenu, mMenuClickPath[LEFT_HAND]},
{*mActivateTouch, mSqueezeValuePath[RIGHT_HAND]},
} };
XrInteractionProfileSuggestedBinding suggestedBindings{ XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING };
suggestedBindings.interactionProfile = oculusTouchInteractionProfilePath;
@ -509,7 +593,6 @@ OpenXRInput::OpenXRInput()
CHECK_XRCMD(xrSuggestInteractionProfileBindings(xr->impl().mInstance, &suggestedBindings));
/*
mSpellModifier; // L-Squeeze
mActivate; // R-Squeeze
mUse; // R-Trigger
mJump; // L-Trigger. L-trigger has value, could use this to make measured jumps ?
@ -520,7 +603,7 @@ OpenXRInput::OpenXRInput()
mCycleSpellRight; // R-ThumbstickClick + SpellModifier
mCycleWeaponLeft; // L-ThumbstickClick
mCycleWeaponRight; // R-ThumbstickClick
mSneak; // X
mToggleSneak; // X
mInventory; // B
mQuickMenu; // Y
mGameMenu; // Menu
@ -547,12 +630,13 @@ OpenXRInput::OpenXRInput()
};
OpenXRAction
OpenXRInput::createAction(
OpenXRInput::createXRAction(
XrActionType actionType,
const std::string& actionName,
const std::string& localName,
const std::vector<SubAction>& subActions)
{
ActionPtr actionPtr = nullptr;
std::vector<XrPath> subactionPaths;
XrActionCreateInfo createInfo{ XR_TYPE_ACTION_CREATE_INFO };
createInfo.actionType = actionType;
@ -569,10 +653,20 @@ OpenXRInput::createAction(
XrAction action = XR_NULL_HANDLE;
CHECK_XRCMD(xrCreateAction(mActionSet, &createInfo, &action));
return OpenXRAction(action, actionType, actionName, localName);
}
template<typename A, XrActionType AT>
ActionPtr
OpenXRInput::createMWAction(
int openMWAction,
const std::string& actionName,
const std::string& localName,
const std::vector<SubAction>& subActions)
{
return ActionPtr(new A(openMWAction, std::move(createXRAction(AT, actionName, localName, subActions))));
}
XrPath
OpenXRInput::subactionPath(
SubAction subAction)
@ -596,102 +690,26 @@ OpenXRInput::updateControls()
syncInfo.activeActionSets = &activeActionSet;
CHECK_XRCMD(xrSyncActions(xr->impl().mSession, &syncInfo));
mGameMenu.update();
mInventory.update();
mActivate.update();
mUse.update();
mJump.update();
mWeapon.update();
mSpell.update();
mCycleSpellLeft.update();
mCycleSpellRight.update();
mCycleWeaponLeft.update();
mCycleWeaponRight.update();
mSneak.update();
mQuickMenu.update();
mLookLeftRight.update();
mMoveForwardBackward.update();
mMoveLeftRight.update();
mActionsMenu.update();
mSpellModifier.update();
mActivateTouch.update();
// Simple fire-and-forget on-press actions
if (mActionsMenu.actionOnPress())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_QuickKeysMenu, true });
}
if (mGameMenu.actionOnPress())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_GameMenu, true });
}
if (mInventory.actionOnPress())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_Inventory, true });
}
if (mJump.actionOnPress())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_Jump, true });
}
if (mQuickMenu.actionOnPress())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_QuickMenu, true });
}
// Actions that activate on release
if (mActivate.actionChanged())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_Activate, mActivate.actionOnPress() });
}
// Actions that hold with the button
if (mUse.actionChanged())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_Use, mUse.actionOnPress() });
}
if (mSneak.actionChanged())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_Sneak, mSneak.actionOnPress() });
}
if (mActivateTouch.actionChanged())
{
mActionEvents.emplace_back(OpenXRActionEvent{ A_ActivateTouch, mActivateTouch.actionOnPress() });
}
// Weapon/Spell actions
if (mWeapon.actionOnPress() && !mSpellModifier.actionIsPressed())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_ToggleWeapon, true });
}
if (mCycleWeaponLeft.actionOnPress() && !mSpellModifier.actionIsPressed())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_CycleWeaponLeft, true });
}
if (mCycleWeaponRight.actionOnPress() && !mSpellModifier.actionIsPressed())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_CycleWeaponRight, true });
}
if (mSpell.actionOnPress() && mSpellModifier.actionIsPressed())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_ToggleSpell, true });
}
if (mCycleSpellLeft.actionOnPress() && mSpellModifier.actionIsPressed())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_CycleSpellLeft, true });
}
if (mCycleSpellRight.actionOnPress() && mSpellModifier.actionIsPressed())
{
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_CycleSpellRight, true });
}
float lookLeftRight = mLookLeftRight.value();
float moveLeftRight = mMoveLeftRight.value();
float moveForwardBackward = mMoveForwardBackward.value();
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_LookLeftRight, false, lookLeftRight });
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_MoveLeftRight, false, moveLeftRight });
mActionEvents.emplace_back(OpenXRActionEvent{ MWInput::InputManager::A_MoveForwardBackward, false, moveForwardBackward });
mGameMenu->updateAndQueue(mActionQueue);
mInventory->updateAndQueue(mActionQueue);
mActivate->updateAndQueue(mActionQueue);
mUse->updateAndQueue(mActionQueue);
mJump->updateAndQueue(mActionQueue);
mToggleWeapon->updateAndQueue(mActionQueue);
mToggleSpell->updateAndQueue(mActionQueue);
mCycleSpellLeft->updateAndQueue(mActionQueue);
mCycleSpellRight->updateAndQueue(mActionQueue);
mCycleWeaponLeft->updateAndQueue(mActionQueue);
mCycleWeaponRight->updateAndQueue(mActionQueue);
mToggleSneak->updateAndQueue(mActionQueue);
mQuickMenu->updateAndQueue(mActionQueue);
mLookLeftRight->updateAndQueue(mActionQueue);
mMoveForwardBackward->updateAndQueue(mActionQueue);
mMoveLeftRight->updateAndQueue(mActionQueue);
mJournal->updateAndQueue(mActionQueue);
//mQuickSave->updateAndQueue(mActionQueue);
mQuickKeysMenu->updateAndQueue(mActionQueue);
mActivateTouch->updateAndQueue(mActionQueue);
}
XrPath OpenXRInput::generateXrPath(const std::string& path)
@ -702,16 +720,14 @@ XrPath OpenXRInput::generateXrPath(const std::string& path)
return xrpath;
}
bool OpenXRInput::nextActionEvent(OpenXRActionEvent& action)
const Action* OpenXRInput::nextAction()
{
action = {};
if (mActionQueue.empty())
return nullptr;
if (mActionEvents.empty())
return false;
action = mActionEvents.front();
mActionEvents.pop_front();
return true;
const auto* action = mActionQueue.front();
mActionQueue.pop_front();
return action;
}
@ -734,7 +750,7 @@ OpenXRInput::getHandPoses(
CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Side::LEFT_HAND], referenceSpace, time, &location));
if (!location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)
// Quat must have a magnitude of 1 but openxr sets it to 0 when tracking is unavailable.
// I want a no-track pose to still be valid
// I want a no-track pose to still be a valid quat so osg won't throw errors
location.pose.orientation.w = 1;
handPoses[(int)Side::LEFT_HAND] = MWVR::Pose{
@ -746,7 +762,7 @@ OpenXRInput::getHandPoses(
CHECK_XRCMD(xrLocateSpace(mHandSpace[(int)Side::RIGHT_HAND], referenceSpace, time, &location));
if (!location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)
// Quat must have a magnitude of 1 but openxr sets it to 0 when tracking is unavailable.
// I want a no-track pose to still be valid
// I want a no-track pose to still be a valid quat so osg won't throw errors
location.pose.orientation.w = 1;
handPoses[(int)Side::RIGHT_HAND] = MWVR::Pose{
@ -830,11 +846,11 @@ private:
{
injectMousePress(SDL_BUTTON_LEFT, onPress);
}
else if (onPress)
{
// Other actions should only happen on release;
return;
}
//else if (onPress)
//{
// // Other actions should only happen on release;
// return;
//}
else if (dnd.mIsOnDragAndDrop)
{
// Intersected with the world while drag and drop is active
@ -916,10 +932,9 @@ private:
mGuiCursorX = guiCursor.x();
mGuiCursorY = guiCursor.y();
OpenXRActionEvent event{};
while (mXRInput->nextActionEvent(event))
while (auto* action = mXRInput->nextAction())
{
processEvent(event);
processAction(action);
}
updateActivationIndication();
@ -945,11 +960,35 @@ private:
updateHead();
}
void OpenXRInputManager::processEvent(const OpenXRActionEvent& event)
void OpenXRInputManager::processAction(const Action* action)
{
//auto* session = Environment::get().getSession();
auto* xrGUIManager = Environment::get().getGUIManager();
switch (event.action)
// Hold actions
switch (action->openMWActionCode())
{
case OpenXRInput::A_ActivateTouch:
resetIdleTime();
mActivationIndication = action->isActive();
break;
case A_LookLeftRight:
mYaw += osg::DegreesToRadians(action->value()) * 2.f;
break;
case A_MoveLeftRight:
mInputBinder->getChannel(A_MoveLeftRight)->setValue(action->value() / 2.f + 0.5f);
break;
case A_MoveForwardBackward:
mInputBinder->getChannel(A_MoveForwardBackward)->setValue(-action->value() / 2.f + 0.5f);
break;
default:
break;
}
// OnActivate actions
if (action->onActivate())
{
switch (action->openMWActionCode())
{
case A_GameMenu:
toggleMainMenu();
@ -967,25 +1006,6 @@ private:
case A_Console:
toggleConsole();
break;
case A_Activate:
resetIdleTime();
//{
// if (MWBase::Environment::get().getWindowManager()->isGuiMode())
// {
// // Do nothing
// }
// else if(!event.onPress)
// activate();
//}
mActivationIndication = event.onPress;
break;
// TODO: Movement
//case A_MoveLeft:
//case A_MoveRight:
//case A_MoveForward:
//case A_MoveBackward:
// handleGuiArrowKey(action);
// break;
case A_Journal:
toggleJournal();
break;
@ -1043,15 +1063,6 @@ private:
case A_ToggleDebug:
MWBase::Environment::get().getWindowManager()->toggleDebugWindow();
break;
// Does not apply in VR
//case A_ZoomIn:
// if (mControlSwitch["playerviewswitch"] && mControlSwitch["playercontrols"] && !MWBase::Environment::get().getWindowManager()->isGuiMode())
// MWBase::Environment::get().getWorld()->setCameraDistance(ZOOM_SCALE, true, true);
// break;
//case A_ZoomOut:
// if (mControlSwitch["playerviewswitch"] && mControlSwitch["playercontrols"] && !MWBase::Environment::get().getWindowManager()->isGuiMode())
// MWBase::Environment::get().getWorld()->setCameraDistance(-ZOOM_SCALE, true, true);
// break;
case A_QuickSave:
quickSave();
break;
@ -1074,35 +1085,31 @@ private:
if (checkAllowedToUseItems() && MWBase::Environment::get().getWindowManager()->isAllowed(MWGui::GW_Inventory))
MWBase::Environment::get().getWindowManager()->cycleWeapon(true);
break;
case A_Sneak:
if (mSneakToggles)
{
case A_ToggleSneak:
toggleSneaking();
}
break;
case A_LookLeftRight:
mYaw += osg::DegreesToRadians(event.value) * 2.f;
break;
case A_MoveLeftRight:
mInputBinder->getChannel(A_MoveLeftRight)->setValue(event.value / 2.f + 0.5f);
break;
case A_MoveForwardBackward:
mInputBinder->getChannel(A_MoveForwardBackward)->setValue(-event.value / 2.f + 0.5f);
break;
case A_Jump:
mAttemptJump = true;
break;
default:
break;
}
}
// A few actions need both activate and deactivate
if (action->onDeactivate())
{
switch (action->openMWActionCode())
{
case A_Use:
if (mActivationIndication || MWBase::Environment::get().getWindowManager()->isGuiMode())
pointActivation(event.onPress);
pointActivation(action->onActivate());
else
mInputBinder->getChannel(A_Use)->setValue(event.onPress);
mInputBinder->getChannel(A_Use)->setValue(action->isActive());
break;
//case OpenXRInput::A_ActivateTouch:
// mActivationIndication = event.onPress;
// break;
default:
Log(Debug::Warning) << "Unhandled XR action " << event.action;
break;
}
}
}

View file

@ -40,7 +40,7 @@ namespace MWVR
void updateHead();
void processEvent(const OpenXRActionEvent& event);
void processAction(const class Action* action);
PoseSet getHandPoses(int64_t time, TrackedSpace space);